当前位置: 首页 > news >正文

php可观测 SDK + 示例平台开源完整流程(从 0 到持续维护)=写一个开源项目全流程

1)目标和边界 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 这套开源项目分两部分:1. SDK 包(packages/observability-sdk)- 提供 tracing、metrics、structured logging 三件套 - 无侵入接入 Hyperf 应用(中间件 + 配置)2. 示例平台(apps/demo-platform)- 一个最小业务 API(如订单接口) - 接入 SDK 后,能在 Grafana/Jaeger/Prometheus 看到完整观测数据 ---2)推荐仓库结构(Monorepo) hyperf-observability/ ├─ packages/ │ └─ observability-sdk/ │ ├─ composer.json │ ├─ src/ │ │ ├─ Config/ObservabilityConfig.php │ │ ├─ Context/TraceContext.php │ │ ├─ Middleware/ObservabilityMiddleware.php │ │ ├─ Logging/JsonTraceProcessor.php │ │ ├─ Metrics/RequestMetrics.php │ │ ├─ Tracing/TracerFactory.php │ │ └─ Provider/ObservabilityServiceProvider.php │ └─ tests/ ├─ apps/ │ └─ demo-platform/ │ ├─ app/Controller/OrderController.php │ ├─ config/autoload/observability.php │ ├─ config/autoload/middlewares.php │ └─ routes.php ├─ deploy/ │ ├─ docker-compose.yml │ ├─ otel-collector-config.yaml │ ├─ prometheus.yml │ └─ grafana/provisioning/... ├─ .github/workflows/ci.yml ├─ .github/workflows/release.yml ├─ LICENSE └─ README.md ---3)0初始化(命令)# 1) 创建 demo 平台composercreate-project hyperf/hyperf-skeleton apps/demo-platform# 2) 创建 SDK 包目录mkdir-ppackages/observability-sdk/src packages/observability-sdk/tests# 3) SDK 依赖(在 packages/observability-sdk 下)composerinitcomposerrequire open-telemetry/api open-telemetry/sdk open-telemetry/exporter-otlpcomposerrequire promphp/prometheus_client_phpcomposerrequire monolog/monolog# 4) demo 平台依赖(在 apps/demo-platform 下)composerrequire hyperf/http-server hyperf/di hyperf/loggercomposerrequire monolog/monolog ---4)SDK 核心代码4.1配置对象 packages/observability-sdk/src/Config/ObservabilityConfig.php<?php declare(strict_types=1);namespace Acme\Observability\Config;final class ObservabilityConfig{publicfunction__construct(public string$serviceName='demo-platform', public string$environment='dev', public string$otlpEndpoint='http://otel-collector:4318', public bool$enableTracing=true, public bool$enableMetrics=true, public bool$enableLogging=true,){}}4.2Trace 上下文 packages/observability-sdk/src/Context/TraceContext.php<?php declare(strict_types=1);namespace Acme\Observability\Context;use Hyperf\Context\Context;final class TraceContext{private const KEY_TRACE_ID='obs.trace_id';private const KEY_SPAN_ID='obs.span_id';public staticfunctionset(string$traceId, string$spanId): void{Context::set(self::KEY_TRACE_ID,$traceId);Context::set(self::KEY_SPAN_ID,$spanId);}public staticfunctiontraceId(): ?string{returnContext::get(self::KEY_TRACE_ID);}public staticfunctionspanId(): ?string{returnContext::get(self::KEY_SPAN_ID);}public staticfunctionclear(): void{Context::set(self::KEY_TRACE_ID, null);Context::set(self::KEY_SPAN_ID, null);}}4.3Tracer 工厂 packages/observability-sdk/src/Tracing/TracerFactory.php<?php declare(strict_types=1);namespace Acme\Observability\Tracing;use Acme\Observability\Config\ObservabilityConfig;use OpenTelemetry\API\Trace\TracerInterface;use OpenTelemetry\SDK\Resource\ResourceInfo;use OpenTelemetry\SDK\Resource\ResourceInfoFactory;use OpenTelemetry\SDK\Trace\TracerProvider;use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;use OpenTelemetry\Contrib\Otlp\SpanExporter;use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;use OpenTelemetry\SemConv\ResourceAttributes;final class TracerFactory{public staticfunctionmake(ObservabilityConfig$cfg): TracerInterface{$transport=(new OtlpHttpTransportFactory())->create($cfg->otlpEndpoint.'/v1/traces','application/json');$resource=ResourceInfoFactory::defaultResource()->merge(ResourceInfo::create([ResourceAttributes::SERVICE_NAME=>$cfg->serviceName, ResourceAttributes::DEPLOYMENT_ENVIRONMENT_NAME=>$cfg->environment,]));$provider=new TracerProvider(new BatchSpanProcessor(new SpanExporter($transport)), null,$resource);return$provider->getTracer('acme.observability');}}4.4Hyperf 中间件(HTTP Span + 请求时延 + trace 注入) packages/observability-sdk/src/Middleware/ObservabilityMiddleware.php<?php declare(strict_types=1);namespace Acme\Observability\Middleware;use Acme\Observability\Context\TraceContext;use Acme\Observability\Metrics\RequestMetrics;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\TracerInterface;use Psr\Http\Message\ResponseInterface;use Psr\Http\Message\ServerRequestInterface;use Psr\Http\Server\RequestHandlerInterface;final class ObservabilityMiddleware{publicfunction__construct(privatereadonlyTracerInterface$tracer, privatereadonlyRequestMetrics$metrics,){}publicfunctionprocess(ServerRequestInterface$request, RequestHandlerInterface$handler): ResponseInterface{$path=$request->getUri()->getPath();$method=$request->getMethod();$start=microtime(true);$span=$this->tracer->spanBuilder($method.' '.$path)->setSpanKind(SpanKind::KIND_SERVER)->startSpan();$scope=$span->activate();TraceContext::set($span->getContext()->getTraceId(),$span->getContext()->getSpanId());try{$response=$handler->handle($request);$status=$response->getStatusCode();$span->setAttribute('http.method',$method);$span->setAttribute('http.target',$path);$span->setAttribute('http.status_code',$status);$this->metrics->observeHttpRequest(method:$method, route:$path, status:(string)$status, seconds: microtime(true)-$start);return$response->withHeader('x-trace-id', TraceContext::traceId()??'');}catch(\Throwable$e){$span->recordException($e);$span->setAttribute('error',true);$this->metrics->observeHttpRequest($method,$path,'500', microtime(true)-$start);throw$e;}finally{$span->end();$scope->detach();TraceContext::clear();}}}4.5Metrics 采集(Prometheus) packages/observability-sdk/src/Metrics/RequestMetrics.php<?php declare(strict_types=1);namespace Acme\Observability\Metrics;use Prometheus\CollectorRegistry;use Prometheus\Storage\InMemory;final class RequestMetrics{private CollectorRegistry$registry;private\Prometheus\Histogram$httpLatency;private\Prometheus\Counter$httpTotal;publicfunction__construct(){$this->registry=new CollectorRegistry(new InMemory());$this->httpLatency=$this->registry->getOrRegisterHistogram('app','http_request_duration_seconds','HTTP request latency',['method','route','status'],[0.01,0.05,0.1,0.3,1,3,10]);$this->httpTotal=$this->registry->getOrRegisterCounter('app','http_requests_total','HTTP requests count',['method','route','status']);}publicfunctionobserveHttpRequest(string$method, string$route, string$status, float$seconds): void{$labels=[$method,$route,$status];$this->httpLatency->observe($seconds,$labels);$this->httpTotal->inc($labels);}publicfunctionrenderPrometheus(): string{$renderer=new\Prometheus\RenderTextFormat();return$renderer->render($this->registry->getMetricFamilySamples());}}4.6日志处理器(自动注入 trace_id/span_id) packages/observability-sdk/src/Logging/JsonTraceProcessor.php<?php declare(strict_types=1);namespace Acme\Observability\Logging;use Acme\Observability\Context\TraceContext;final class JsonTraceProcessor{publicfunction__invoke(array$record): array{$record['extra']['trace_id']=TraceContext::traceId();$record['extra']['span_id']=TraceContext::spanId();return$record;}}---5)在 demo-platform 接入 SDK5.1配置文件 apps/demo-platform/config/autoload/observability.php<?phpreturn['service_name'=>env('OBS_SERVICE_NAME','demo-platform'),'environment'=>env('APP_ENV','dev'),'otlp_endpoint'=>env('OBS_OTLP_ENDPOINT','http://otel-collector:4318'),];5.2中间件注册 apps/demo-platform/config/autoload/middlewares.php<?phpreturn['http'=>[Acme\Observability\Middleware\ObservabilityMiddleware::class,],];5.3示例业务接口 apps/demo-platform/app/Controller/OrderController.php<?php declare(strict_types=1);namespace App\Controller;use Hyperf\HttpServer\Contract\ResponseInterface;use Psr\Log\LoggerInterface;final class OrderController{publicfunction__construct(privatereadonlyLoggerInterface$logger){}publicfunctionshow(ResponseInterface$response, int$id){$this->logger->info('query order',['order_id'=>$id]);return$response->json(['id'=>$id,'status'=>'paid','amount'=>1999]);}}5.4暴露 /metrics // routes.php use Hyperf\HttpServer\Router\Router;use Acme\Observability\Metrics\RequestMetrics;use Psr\Container\ContainerInterface;Router::get('/orders/{id:\d+}',[App\Controller\OrderController::class,'show']);Router::get('/metrics',function(ContainerInterface$container){/** @var RequestMetrics$metrics*/$metrics=$container->get(RequestMetrics::class);return$metrics->renderPrometheus();});---6)可观测基础设施(本地一键跑) deploy/docker-compose.yml(最小组合) version:"3.8"services: otel-collector: image: otel/opentelemetry-collector-contrib:0.100.0 command:["--config=/etc/otelcol/config.yaml"]volumes: - ./otel-collector-config.yaml:/etc/otelcol/config.yaml ports: -"4318:4318"-"8889:8889"jaeger: image: jaegertracing/all-in-one:1.57 ports: -"16686:16686"prometheus: image: prom/prometheus:v2.52.0 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: -"9090:9090"grafana: image: grafana/grafana:11.0.0 ports: -"3000:3000"deploy/otel-collector-config.yaml(核心) receivers: otlp: protocols: http: exporters: jaeger: endpoint: jaeger:14250 tls: insecure:trueprometheus: endpoint:0.0.0.0:8889 service: pipelines: traces: receivers:[otlp]exporters:[jaeger]---7)开源发布流程(完整)1. License: MIT 或 Apache-2.02. README 必须含: -5分钟快速开始 - 架构图 - 指标清单(QPS、P95、错误率) - 常见问题(trace 丢失、标签爆炸)3. SECURITY.md: 漏洞提交邮箱和响应 SLA4. Packagist 发布 SDK: - packages/observability-sdk 独立 composer.json - 打 tag: v0.1.05. 示例平台独立运行: -dockercompose up-d- php bin/hyperf.php start ---8)CI/CD(最少要有) .github/workflows/ci.yml 建议包含: - PHPStan - PHPUnit - Infection(可选,做变异测试) -composervalidate - SDK 与 demo 的集成测试(发请求后校验 /metrics 有数据) ---9)版本策略(持续维护) - v0.x: 快速迭代 API,不承诺稳定 - v1.0: 冻结公共接口 - 每次发布输出: - Breaking changes - 新增指标 - 性能变化(例如中间件耗时 +0.2ms) ---10)你最容易踩的6个坑1. 把 route 直接用原始 URL(会导致高基数,Prometheus 爆炸)2. 日志没带 trace_id(排障断链)3. 异步任务没传递上下文(trace 断裂)4. /metrics 混入业务鉴权中间件(被拦截)5. 观测 SDK 抛异常影响主流程(SDK 必须“失败隔离”)6. 指标标签过多(method/route/status 以外谨慎加) --- 这套骨架可以直接作为开源项目第一版: 先把 Middleware + Metrics + Log Processor + Docker 观测栈 + CI 发出来,社区就能跑通端到端链路,再逐步补齐 SQL span、队列 span、告警规则和 Grafana dashboard 模板。
http://www.jsqmd.com/news/701869/

相关文章:

  • AI编码助手技能库:233个专家技能赋能Claude、Cursor等工具
  • 2026年必逛!口碑爆棚的厦门特产网红店铺,究竟藏着啥美味?
  • 即插即用系列(代码实践) | CMPB PMFSNet:多尺度特征自注意力网络,打破轻量级医学图像分割的性能天花板
  • 杭州国际快递集运优质服务商推荐榜:杭州国际快递公司/杭州国际快递国际代理/杭州国际快递服务公司/杭州国际快递物流/选择指南 - 优质品牌商家
  • 基于RAG框架构建企业知识库:从原理到生产级实践
  • Pixel Aurora Engine基础教程:像素画网格对齐与游戏引擎像素完美匹配
  • 2026厦门旅游必买!这6家靠谱特产供应商本地人都在囤
  • 智能体开发框架agent-dev:从核心原理到实战构建AI助手
  • ARIMA模型时间序列预测区间实现与解析
  • Qwen3-14B辅助Visio绘图:根据文本描述自动生成系统架构图草图
  • C语言内存安全“最后一公里”突破:基于Control Flow Integrity + Memory Tagging Extension的2026双模防护实践(ARMv9/M1 Ultra实测数据)
  • Docker容器的常用操作
  • 基于vue的体育比赛系统[vue]-计算机毕业设计源码+LW文档
  • ERNIE Bot Agent智能体开发框架:从大模型API到复杂任务编排实战
  • 5步掌握暗黑2存档编辑:网页版d2s-editor的终极解决方案
  • 机器学习基础:从数据构成到模型评估全解析
  • Qianfan-OCR-4B处理扫描版电子书效果对比:高精度文本复原
  • AI驱动数据抓取实战:OxyLabs SDK重塑工作流
  • Docker 镜像的常用操作
  • 【VSCode 2026低代码革命】:3大拖拽组件插件实测对比,92%开发者已切换(附性能基准测试数据)
  • Real Anime Z技术解析:双层显存优化中CPU卸载策略对Turbo模型推理延迟的影响
  • 2026年q2山东发电机出租选型技术全指南:山东发电机租赁/山东发电车出租/山东发电车租赁/山东电源车出租/选择指南 - 优质品牌商家
  • 深入解析Azure Pipelines Agent:自托管部署与CI/CD自动化实践
  • Gymnasium强化学习环境接口:从核心概念到工程实践指南
  • 从零实现朴素贝叶斯分类器:原理与Python实战
  • 乐山地区排水管生产厂家综合实力排行2026版:钢筋混泥土排水管厂家/乐山排水管生产厂家/乐山检查井生产厂家/选择指南 - 优质品牌商家
  • 乐山驾培与无人机培训技术全解析:从合规到实操的参考指南 - 优质品牌商家
  • Pi0具身智能v1进阶使用:对接ROS/Mujoco的接口数据准备
  • Speech-AI-Forge:一站式集成主流开源语音AI模型的本地部署与API调用指南
  • PyTorch模型评估与性能优化实战指南