PHPackages                             gilbitron/canonical-context-logging - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. gilbitron/canonical-context-logging

ActiveLibrary

gilbitron/canonical-context-logging
===================================

PHP SDK for Canonical Context Logging - structured JSON logs with one wide event per request

0.2.0(4mo ago)02.8k↓21.1%1MITPHPPHP &gt;=8.1CI passing

Since Dec 22Pushed 4mo agoCompare

[ Source](https://github.com/gilbitron/canonical-context-logging)[ Packagist](https://packagist.org/packages/gilbitron/canonical-context-logging)[ RSS](/packages/gilbitron-canonical-context-logging/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (3)Versions (3)Used By (1)

Canonical Context Logging - PHP SDK
===================================

[](#canonical-context-logging---php-sdk)

PHP SDK for Canonical Context Logging - structured JSON logs with one wide event per request.

Overview
--------

[](#overview)

This package implements the [Canonical Context Logging](https://loggingsucks.com/) pattern:

1. **Emit structured logs only** - JSON, consistent keys, no free-text messages
2. **Log one "wide event" per request** - Capture all context in a single final log entry
3. **Accumulate context via middleware** - Build an event object at request start, enrich it throughout execution, emit once at the end
4. **Prefer context over volume** - Fewer logs, richer data
5. **Tail-sample aggressively** - Always keep errors + slow requests; sample the rest
6. **Store logs as queryable data** - Columnar DB + SQL &gt; grep
7. **OTel is transport, not design** - You decide what to log; business context is your job

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- OpenTelemetry SDK

Installation
------------

[](#installation)

```
composer require gilbitron/canonical-context-logging
```

Quick Start
-----------

[](#quick-start)

### Basic Usage (Framework-Agnostic)

[](#basic-usage-framework-agnostic)

```
use CanonicalContextLogging\Context\SingletonStorage;
use CanonicalContextLogging\Exporter\OtlpExporter;
use CanonicalContextLogging\Logger\CanonicalLogger;
use CanonicalContextLogging\Middleware\RequestMiddleware;

// Setup - OTLP exporter reads from environment variables by default
// Set OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector:4318
$storage = new SingletonStorage();
$exporter = new OtlpExporter(); // Reads config from environment
$logger = new CanonicalLogger(
    exporter: $exporter,
    slowRequestThreshold: 1.0, // Log requests slower than 1 second
    sampleRate: 0.1 // Sample 10% of normal requests
);
$middleware = new RequestMiddleware($storage, $logger);

// Start request
$context = $middleware->start();
$context->setService('my-service', '1.0.0');
$context->addContext('user_id', 123);
$context->addContext('org_id', 456);

// ... your application logic ...

// End request
$context->setStatus(200);
$context->setDuration(0.123);
$middleware->end($context);
```

### With Error Handling

[](#with-error-handling)

```
try {
    $context = $middleware->start();
    $context->setService('my-service', '1.0.0');

    // ... application logic ...

    $context->setStatus(200);
} catch (\Throwable $e) {
    $context->setError($e);
    $context->setStatus(500);
} finally {
    $context->setDuration($context->getDuration());
    $middleware->end($context);
}
```

Configuration
-------------

[](#configuration)

### Exporter Options

[](#exporter-options)

#### Console Exporter (Development)

[](#console-exporter-development)

For local development, you can use the console exporter to output logs to stdout/stderr:

```
use CanonicalContextLogging\Exporter\ConsoleExporter;

// Output to stderr with pretty printing
$exporter = new ConsoleExporter(useStderr: true, prettyPrint: true);

// Output to stdout, compact JSON
$exporter = new ConsoleExporter(useStderr: false, prettyPrint: false);
```

#### OTLP Exporter (Production)

[](#otlp-exporter-production)

The OTLP exporter uses the official OpenTelemetry SDK and reads configuration from environment variables:

```
use CanonicalContextLogging\Exporter\OtlpExporter;
use CanonicalContextLogging\Exporter\OtlpConfig;

// Option 1: Use environment variables (recommended)
$exporter = new OtlpExporter(); // Reads from environment

// Option 2: Explicit configuration
$config = OtlpConfig::create(
    endpoint: 'https://otel-collector:4318',
    protocol: 'http/protobuf',
    headers: ['Authorization' => 'Bearer ' . $apiKey],
    timeout: 10
);
$exporter = new OtlpExporter($config);
```

**Environment Variables:**

The exporter reads standard OpenTelemetry environment variables:

- `OTEL_EXPORTER_OTLP_ENDPOINT` - OTLP endpoint URL (default: `http://localhost:4318`)
- `OTEL_EXPORTER_OTLP_PROTOCOL` - Protocol to use: `http/protobuf` or `http/json` (default: `http/protobuf`)
- `OTEL_EXPORTER_OTLP_HEADERS` - Headers as comma-separated key=value pairs (e.g., `key1=value1,key2=value2`)
- `OTEL_EXPORTER_OTLP_TIMEOUT` - Timeout in seconds (default: `10`)

Example:

```
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer token123"
export OTEL_EXPORTER_OTLP_TIMEOUT=10
```

#### File Exporter

[](#file-exporter)

Write logs to a file in JSONL format (one JSON object per line):

```
use CanonicalContextLogging\Exporter\FileExporter;

// Compact JSON (default)
$exporter = new FileExporter('/var/log/app.jsonl');

// Pretty-printed JSON
$exporter = new FileExporter('/var/log/app.jsonl', prettyPrint: true);
```

The FileExporter automatically creates the directory structure if it doesn't exist and appends each log entry as a new line (JSONL format).

#### Custom Exporter

[](#custom-exporter)

Implement `ExporterInterface` to create your own exporter:

```
use CanonicalContextLogging\Exporter\ExporterInterface;

class CustomExporter implements ExporterInterface
{
    public function export(array $data): void
    {
        // Your custom export logic
    }
}
```

### Logger Configuration

[](#logger-configuration)

```
use CanonicalContextLogging\Logger\CanonicalLogger;

$logger = new CanonicalLogger(
    exporter: $exporter,
    slowRequestThreshold: 1.0, // Always log requests slower than 1 second
    sampleRate: 0.1 // Sample 10% of normal requests (0.0 to 1.0)
);
```

**Tail-Sampling Rules:**

- Errors are always logged
- Slow requests (above threshold) are always logged
- Other requests are sampled based on `sampleRate`
- If `sampleRate` is `null`, all requests are logged

Context Storage
---------------

[](#context-storage)

### Singleton Storage (Framework-Agnostic)

[](#singleton-storage-framework-agnostic)

```
use CanonicalContextLogging\Context\SingletonStorage;

$storage = new SingletonStorage();
```

### Container Storage (Framework-Specific)

[](#container-storage-framework-specific)

For framework integration, create a custom storage implementation that uses your framework's container.

EventContext API
----------------

[](#eventcontext-api)

The `EventContext` class accumulates all context for a single request:

```
$context = new EventContext();

// Initialize request
$context->startRequest($traceId, $spanId);

// Service metadata
$context->setService('my-service', '1.0.0');

// Business context (user, org, plan, feature flags, etc.)
$context->addContext('user_id', 123);
$context->addContext('org_id', 456);
$context->addContext('plan', 'premium');
$context->addContext('feature_flags', ['feature_a', 'feature_b']);

// Or add multiple at once
$context->addContexts([
    'user_id' => 123,
    'org_id' => 456,
    'plan' => 'premium',
]);

// Error handling
$context->setError($exception);

// HTTP status
$context->setStatus(200);

// Duration (auto-calculated if not set)
$context->setDuration(0.123);

// Export as array for JSON encoding
$data = $context->toArray();
```

Log Output Format
-----------------

[](#log-output-format)

The structured log output follows this schema:

```
{
  "timestamp": "2024-01-15T10:30:45+00:00",
  "trace_id": "a1b2c3d4e5f6...",
  "span_id": "1234567890abcdef",
  "service": {
    "name": "my-service",
    "version": "1.0.0"
  },
  "status": 200,
  "duration": 0.123,
  "context": {
    "user_id": 123,
    "org_id": 456,
    "plan": "premium",
    "feature_flags": ["feature_a", "feature_b"]
  },
  "error": {
    "type": "RuntimeException",
    "message": "Something went wrong",
    "file": "/path/to/file.php",
    "line": 42,
    "code": 0
  }
}
```

Best Practices
--------------

[](#best-practices)

1. **One log per request** - Use middleware to emit a single log at request end
2. **Accumulate context** - Add business context throughout request execution
3. **Structured data only** - No free-text messages, use consistent keys
4. **Tail-sample** - Configure sampling to reduce volume while keeping errors and slow requests
5. **Business context** - Include user, org, plan, feature flags, etc. - anything that helps answer questions
6. **Error handling** - Always capture exceptions in context, even if you re-throw them

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance74

Regular maintenance activity

Popularity23

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity34

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

147d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/20011de485eeea26b5582c75daaab217937832762b5d5f551d34d07f74138bb0?d=identicon)[gilbitron](/maintainers/gilbitron)

---

Top Contributors

[![gilbitron](https://avatars.githubusercontent.com/u/203882?v=4)](https://github.com/gilbitron "gilbitron (3 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/gilbitron-canonical-context-logging/health.svg)

```
[![Health](https://phpackages.com/badges/gilbitron-canonical-context-logging/health.svg)](https://phpackages.com/packages/gilbitron-canonical-context-logging)
```

###  Alternatives

[drupal/core-dev

require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.

2021.0M277](/packages/drupal-core-dev)[keepsuit/laravel-opentelemetry

OpenTelemetry integration for laravel

142347.8k](/packages/keepsuit-laravel-opentelemetry)[open-telemetry/symfony-sdk-bundle

OpenTelemetry Symfony integration

1182.6k4](/packages/open-telemetry-symfony-sdk-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
