Skip to main content

Logging Package Cheatsheet

Code-verified reference for packages/logging.

Core Pipeline

Cognesy\Logging\Pipeline\LoggingPipeline:
  • LoggingPipeline::create(): self
  • filter(EventFilter $filter): self
  • enrich(EventEnricher $enricher): self
  • format(EventFormatter $formatter): self
  • write(LogWriter $writer): self
  • build(): callable (callable(Event): void)
  • __invoke(): callable (returns build())
build() snapshots the current pipeline configuration. Later builder mutations do not affect an already-built callable. Minimal usage:
use Cognesy\Events\Event;
use Cognesy\Logging\Enrichers\BaseEnricher;
use Cognesy\Logging\Formatters\DefaultFormatter;
use Cognesy\Logging\LogEntry;
use Cognesy\Logging\Pipeline\LoggingPipeline;
use Cognesy\Logging\Writers\CallableWriter;

$pipeline = LoggingPipeline::create()
    ->enrich(new BaseEnricher())
    ->format(new DefaultFormatter())
    ->write(CallableWriter::create(function (LogEntry $entry): void {}))
    ->build();

$pipeline(new Event(['scope' => 'demo']));

Contracts

  • EventFilter::__invoke(Event $event): bool
  • EventEnricher::__invoke(Event $event): LogContext
  • EventFormatter::__invoke(Event $event, LogContext $context): LogEntry
  • LogWriter::__invoke(LogEntry $entry): void
  • ContextProvider::getContext(): array

Value Objects

LogEntry:
  • LogEntry::create(string $level, string $message, array $context = [], ?DateTimeImmutable $timestamp = null, string $channel = 'default'): self
  • withContext(array $additionalContext): self
  • withLevel(string $level): self
  • withMessage(string $message): self
  • withChannel(string $channel): self
  • isLevel(string $level): bool
  • isLevelOrAbove(string $minimumLevel): bool
  • jsonSerialize(): array
LogContext:
  • LogContext::fromEvent(Event $event, array $additionalContext = []): self
  • withFrameworkContext(array $context): self
  • withPerformanceMetrics(array $metrics): self
  • withUserContext(array $context): self
  • toArray(): array
  • jsonSerialize(): array

Built-In Filters

  • LogLevelFilter(string $minimumLevel = LogLevel::DEBUG)
  • EventClassFilter(array $excludedClasses = [], array $includedClasses = [])
  • EventHierarchyFilter(array $excludedClasses = [], array $includedClasses = [])
  • CompositeFilter(EventFilter ...$filters) (AND logic)
EventHierarchyFilter helpers:
  • EventHierarchyFilter::httpEventsOnly(): self
  • EventHierarchyFilter::structuredOutputEventsOnly(): self
  • EventHierarchyFilter::excludeHttpDebug(): self

Built-In Enrichers

  • BaseEnricher
  • LazyEnricher(Closure $contextProvider, string $contextKey = 'framework')
  • LazyEnricher::framework(Closure $provider): self
  • LazyEnricher::metrics(Closure $provider): self
  • LazyEnricher::user(Closure $provider): self

Built-In Formatters

  • DefaultFormatter(string $messageTemplate = '{event_class}: {message}', string $channel = 'instructor')
  • MessageTemplateFormatter(array $templates = [], string $defaultTemplate = '{event_name}', string $channel = 'instructor')
Template placeholders supported by MessageTemplateFormatter:
  • {event_class}, {event_name}, {event_id}
  • event-data placeholders like {method}, {url}
  • framework placeholders like {framework.request_id}

Built-In Writers

  • PsrLoggerWriter(LoggerInterface $logger)
  • MonologChannelWriter(Logger $logger, bool $useEntryChannel = true)
  • CallableWriter(Closure $writer)
  • CallableWriter::create(callable $writer): self
  • CompositeWriter(LogWriter ...$writers)

Framework Factories

Config keys:
  • channel (string)
  • level (string)
  • exclude_events (array<class-string>)
  • include_events (array<class-string>)
  • templates (array<string, string>)
SymfonyLoggingFactory:
  • create(ContainerInterface $container, LoggerInterface $logger, array $config = []): callable
  • defaultSetup(ContainerInterface $container, LoggerInterface $logger): callable
  • productionSetup(ContainerInterface $container, LoggerInterface $logger): callable

Wiretap Integration

Cognesy\Logging\Integrations\EventPipelineWiretap:
  • EventPipelineWiretap(mixed $pipeline) — wraps a callable(Event): void pipeline
  • __invoke(object $event): void — forwards Event instances to the pipeline, ignores non-Event objects

Zero-Config JSONL Logging (EventLog)

Cognesy\Logging\EventLog is a factory that replaces new EventDispatcher(...) in runtime default paths. It automatically attaches a structured JSONL file sink when INSTRUCTOR_LOG_PATH is set in the environment.

Enabling

# Shell: set env var before running your script
INSTRUCTOR_LOG_PATH=/tmp/instructor.jsonl php my-script.php

# Or from PHP (call once at bootstrap)
EventLog::enable('/tmp/instructor.jsonl');

# Or configure the whole default logging profile
EventLog::enable(new EventLogConfig(
    path: '/tmp/instructor.jsonl',
    level: LogLevel::DEBUG,
    excludeHttpDebug: true,
    stringClipLength: 2048,
));

Usage in runtimes (internal)

// Before
$events = $events ?? new EventDispatcher('instructor.structured-output.runtime');

// After
$events = $events ?? EventLog::root('instructor.structured-output.runtime');
EventLog::root() creates a plain dispatcher when no path is set — no I/O, zero cost in tests. When INSTRUCTOR_LOG_PATH is set, it attaches a logging wiretap filtered at INFO level (default). For child dispatchers that bubble to a root:
$childEvents = EventLog::child('child-component', $parentEvents);

API

Cognesy\Logging\EventLog:
  • EventLog::enable(string|EventLogConfig $config): void — programmatic opt-in; call at bootstrap
  • EventLog::disable(): void — reset programmatic opt-in
  • EventLog::root(string $name, ?LoggerInterface $logger = null): CanHandleEvents
  • EventLog::child(string $name, CanHandleEvents $parent): CanHandleEvents

File-backed defaults

Default values now come from packages/logging/resources/config/event_log.yaml. Override that file in app-level config by adding config/event_log.yaml. Available knobs:
  • path
  • level
  • includeEvents
  • excludeEvents
  • useHierarchyFilter
  • excludeHttpDebug
  • includePayload
  • includeCorrelation
  • includeEventMetadata
  • includeComponentMetadata
  • stringClipLength
Example:
path: /tmp/instructor.jsonl
level: debug
excludeHttpDebug: true
excludeEvents:
  - Cognesy\\Http\\Events\\DebugRequestBodyUsed
stringClipLength: 2048

Log level and overrides

Default minimum level: INFO. Override via env:
INSTRUCTOR_LOG_PATH=/tmp/instructor.jsonl INSTRUCTOR_LOG_LEVEL=debug php my-script.php

JSONL format

Each line is a JSON object:
{"timestamp":"2026-03-18T12:00:00+00:00","level":"info","channel":"instructor.structured-output.runtime","message":"StructuredOutputStarted","context":{"event_id":"...","event_class":"...","package":"instructor","component":"instructor.structured-output.runtime","correlation":{},"payload":{}}}

Off by default

  • No INSTRUCTOR_LOG_PATHEventLog::root() returns a plain dispatcher, no files written
  • No test detection heuristics — the empty path is the only gating signal
  • Framework injections (Laravel, Symfony) pass $events explicitly → EventLog::root() never runs

Callsite conventions (internal)

There are three situations and each has its own correct pattern: 1. Runtime/builder with optional user-provided dispatcher Use ?? — EventLog only runs when no dispatcher is provided:
// Correct: user's dispatcher takes precedence; EventLog only as default
$this->events = $events ?? EventLog::root('http.client.runtime');
2. Configurator/builder that accepts a parent dispatcher When a parent is provided, create a plain child dispatcher — EventLog must NOT be involved. When no parent is provided, use EventLog::root() as the standalone default:
// Correct: user-provided parent → plain child dispatcher; standalone → EventLog
$events = $parentEvents !== null
    ? new EventDispatcher('agent-builder', $parentEvents)
    : EventLog::root('agent-builder');
Do NOT use EventLog::child() here — that would attach EventLog to a dispatcher the user already controls, which may have its own logging configured. 3. Zero-config static factory (no dispatcher parameter) Always use EventLog::root() directly — there is no user-supplied dispatcher to respect:
// Correct: standalone factory, no parent
public static function default(): self {
    $events = EventLog::root('agent-loop');
    // ...
}
Rule of thumb: EventLog should be involved only when the code, not the user, is responsible for creating the dispatcher. If the user passes anything in, bypass EventLog entirely.

Observability components (internal)

  • EventLogConfig — loads event_log.yaml defaults and applies env overrides
  • FileJsonLogWriter — append-only JSONL sink; swallows write errors silently
  • StructuredEventFormatter — normalizes Event → LogEntry with correlation fields

Framework Integrations

Laravel:
  • Laravel-specific logging integration lives in packages/laravel.
Symfony:
  • Cognesy\Logging\Integrations\Symfony\InstructorLoggingBundle
  • InstructorLoggingExtension registers instructor_logging.pipeline_factory and instructor_logging.pipeline_listener.
  • WiretapEventBusPass adds one wiretap() method call to the configured event bus service.
Symfony config root (instructor_logging):
  • enabled (bool, default true)
  • preset (default|production|custom)
  • event_bus_service (string, default Cognesy\Events\Contracts\CanHandleEvents)
  • config.channel (string)
  • config.level (emergency|alert|critical|error|warning|notice|info|debug)
  • config.exclude_events (string[])
  • config.include_events (string[])
  • config.templates (array<string, string>)