承接 phpdot/logs 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

phpdot/logs

Composer 安装命令:

composer require phpdot/logs

包简介

The phpdot observability engine: distributed tracing runtime, per-coroutine scope management, and W3C Trace Context value objects. Backend-agnostic; writers are peer packages.

README 文档

README

The observability engine for the PHPdot framework — distributed tracing and structured logging behind one interface.

A package holds one object — TracerInterface — and logs and opens spans against it. It never knows or cares where those records go. The application binds a backend (a Writer) once, and that single binding decides whether the same code writes rich encrypted files, forwards to Monolog, or is discarded. One trace_id per request ties every log line and span together — across packages and across separate channel files.

Where it fits

   your packages              the engine                 the backend (the app binds one)
   ─────────────              ──────────                 ───────────────────────────────
   $tracer->info(...)     →   phpdot/logs            →   ┌─ tracelog    → encrypted files
   $tracer->error(...)        builds a correlated        ├─ psr-bridge  → Monolog / PSR-3
   $tracer->span(...)         record per call,           └─ NullWriter  → discarded (default)
   $span->setAttribute(...)   hands it to WriterInterface
  • A package depends on phpdot/contracts only and injects TracerInterface.
  • The engine (phpdot/logs) builds the spans, the per-coroutine scope, and the request kernel.
  • The backend is chosen by the app in one line. Packages never change.

Install

composer require phpdot/logs

The engine ships with a NullWriter bound by default, so it works with nothing else installed (records are discarded). Add phpdot/tracelog or phpdot/psr-bridge and bind it to actually persist them.

Logging

Inject TracerInterface and log — anywhere, no setup required:

use PHPdot\Contracts\Logs\TracerInterface;

final class OrderService
{
    public function __construct(private readonly TracerInterface $tracer) {}

    public function place(int $id): void
    {
        $this->tracer->info('order placed', ['id' => $id]);
        $this->tracer->warning('low stock', ['sku' => 'A-1', 'left' => 2]);
    }
}

Every line is correlated to the current span's trace_id / span_id. If no span is active yet, the tracer mints a trace context on first use, so logging works in any context — services, jobs, a one-off script.

Encrypting a single line — ->secure()

A log method returns a pending handle; call secure() on it to mark that one record sensitive. A backend that supports encryption (tracelog) then encrypts its message and context together, fail-closed — dropped, never written in plaintext, if it cannot be protected:

$tracer->error('Password reset for ' . $email, ['email' => $email])->secure();  // encrypted
$tracer->info('GET /orders', ['status' => 200]);                                 // plaintext

The record is written when the statement ends (the handle is released), so ->secure() takes effect as long as it is on the same line. trace_id / span_id stay in plaintext so an encrypted line is still correlatable.

Spans

A span is a timed unit of work with attributes, events, and a status.

// trace() — preferred: the span is ALWAYS ended, even if the callback throws,
// and the callback's return value is returned straight back.
$rows = $tracer->trace('db.query', 'client', function ($span): array {
    $span->setAttribute('db.statement', 'SELECT * FROM users');
    $span->addEvent('cache.miss', ['key' => 'users']);
    $result = $db->run(...);
    $span->setAttribute('db.rows', count($result));
    return $result;
});

// manual span — you own end(); use when the work doesn't fit a callback
$span = $tracer->span('upload', 'internal');
$span->setAttribute('bytes', $size);
try {
    $this->store($file);
    $span->setStatus('ok');
} finally {
    $span->end();
}

Spans nest automatically — a span opened while another is active becomes its child, sharing the trace:

$tracer->trace('http.request', 'server', function () use ($tracer) {
    $tracer->trace('db.query', 'client', fn () => $this->query());   // child of http.request
});
span kinds:  internal | server | client | producer | consumer
status:      unset    | ok     | error

Logging on a span correlates the line to that span's id: $span->info('processing', ['step' => 2]).

Channels

Scope a package to its own stream with channel(); a backend routes each channel separately (tracelog writes {channel}.log):

$log = $tracer->channel('http');
$log->info('GET /orders', ['status' => 200]);   // channel "http", same request trace_id

channel() returns a clone — the trace identity is unchanged, so lines on different channels in one request still share one trace_id.

The request boundary

At the server entry point, the kernel opens one root span per request — continuing an inbound W3C traceparent when present — runs your work, marks the root error if it throws, and flushes on the way out. Your packages never call it:

$kernel = $container->get(PHPdot\Logs\TraceKernel::class);

$response = $kernel->handle(
    $request->method() . ' ' . $request->path(),   // root span name
    fn () => $dispatcher->handle($request),         // your request body
    $request->header('traceparent'),                // optional: continue a distributed trace
    $request->header('tracestate'),
);

There is no startRequest/endRequest/close in your code. The per-coroutine scope also auto-drains at coroutine end as a backstop, so a span left open is still exported. A malformed inbound traceparent never breaks the request — the kernel falls back to a fresh root.

The kernel is transport-agnostic — name the root after the unit of work for CLI commands or queue messages, too:

$kernel->handle('orders:reconcile', fn () => $command->run($input, $output));
$kernel->handle('queue:SendInvoice', fn () => $handler->handle($message), $message->header('traceparent'));

Distributed tracing

// inbound — continue an upstream trace (handle() does this for you)
$kernel->handle('GET /orders', $work, $req->header('traceparent'));

// outbound — propagate to a downstream service
$client->request('GET', $url, ['headers' => [
    'traceparent' => $tracer->context()->toTraceparent(),   // 00-<trace>-<span>-01
]]);

Choosing a backend

The app binds WriterInterface once — the only line that changes between "rich files", "Monolog", and "off":

No sampling, ever: if logging is enabled, every log and span is stored — production included.

Coroutine safety

The tracer and scope manager are process-wide #[Singleton]s, but trace identity is not on the instance: each coroutine's active-span stack lives in its own context, looked up on every call. Two coroutines get two stacks; neither can see the other's current span. This is what makes one shared tracer safe under Swoole without leaking state between concurrent requests.

Package structure

src/
├── CoreTracer.php        TracerInterface — channels, spans, correlated logging
├── CoreSpan.php          the span: attributes, events, status, log lines, end()
├── PendingLog.php        the deferred log handle behind ->secure()
├── ScopeManager.php      per-coroutine active-span stack (+ auto-drain)
├── SpanStack.php         the LIFO stack itself
├── TraceKernel.php       the per-request root span boundary
├── NullWriter.php        the default no-op backend
├── Trace/                TraceId (UUIDv7), SpanId, SpanContext, Traceparent (W3C)
└── Enum/                 SpanKind, SpanStatus

Development

composer test        # PHPUnit (368 tests)
composer analyse     # PHPStan level 10 + strict rules
composer cs-check    # PHP-CS-Fixer (@PER-CS2.0)
composer check       # all three

Requirements

  • PHP >= 8.4
  • phpdot/contracts ^1.10
  • phpdot/container ^1.0

License

MIT

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-30

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固