Context providers

Context providers allow you to automatically enrich all log entries with additional context information from various sources. This is useful for adding consistent metadata to all logs, such as request IDs, user information, environment details, etc.

What are context providers?

A context provider is a class that implements the LoggerContextInterface and provides additional context data to be merged with the context passed to any log method. This allows you to inject contextual information into all log entries without having to manually pass it every time you log something.

Common use cases include:

  • Adding request ID to all logs for request tracing

  • Including authenticated user information

  • Adding environment details (hostname, instance ID, etc.)

  • Including application version or build number

  • Adding correlation IDs for distributed systems

The LoggerContextInterface

To create a context provider, implement the LoggerContextInterface:

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace RunOpenCode\Component\Logger\Contract;
 6
 7interface LoggerContextInterface
 8{
 9    /**
10     * Get context data to append to the existing context.
11     *
12     * @param array<mixed> $current Current context passed to the logger method.
13     *
14     * @return array<mixed> Context data to append to the current context.
15     */
16    public function get(array $current): array;
17}

The get() method receives the current context (the context that was passed to the log method) and returns additional context data to be merged with it.

Creating a context provider

Here’s an example of a simple context provider that adds request ID to all logs:

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace App\Logger\Context;
 6
 7use RunOpenCode\Component\Logger\Contract\LoggerContextInterface;
 8
 9final readonly class RequestIdContextProvider implements LoggerContextInterface
10{
11    public function __construct(private string $requestId)
12    {
13        // noop.
14    }
15
16    public function get(array $current): array
17    {
18        return ['request_id' => $this->requestId];
19    }
20}

Using context providers

To use context providers, pass them to the Logger constructor:

 1<?php
 2
 3use RunOpenCode\Component\Logger\Logger;
 4use App\Logger\Context\RequestIdContextProvider;
 5use App\Logger\Context\UserContextProvider;
 6
 7$requestIdProvider = new RequestIdContextProvider($requestId);
 8
 9$logger = new Logger(
10    decorated: $psrLogger,
11    contextProviders: [
12        $requestIdProvider,
13        $userContextProvider,
14    ]
15);
16
17// Now all log entries will automatically include request_id and user information
18$logger->info('User action performed');
19
20// Resulting context will be something like:
21// [
22//     'request_id' => '96a101dd-c49a-4fea-aee2-a76510f32190',
23// ]

Context merging

Context from providers is merged with the context passed to log methods. The merge happens in this order:

  1. Context from each provider is collected (in the order they were registered)

  2. Each provider’s context is merged with the accumulated context

  3. The final context is merged with the context passed to the log method

If there are duplicate keys, later values override earlier ones:

 1<?php
 2
 3// Provider 1 returns: ['request_id' => 'abc', 'env' => 'prod']
 4// Provider 2 returns: ['user_id' => 42, 'env' => 'staging']
 5// Passed context: ['env' => 'dev', 'action' => 'create']
 6
 7// Final context will be:
 8// [
 9//     'request_id' => 'abc',
10//     'env' => 'dev',           // From passed context (overrides providers)
11//     'user_id' => 42,
12//     'action' => 'create',
13// ]

This means that the context passed directly to the log method always has the highest priority.

Access to current context

Context providers have access to the current context (including context from previous providers) through the $current parameter. This allows you to make decisions based on what’s already in the context:

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace App\Logger\Context;
 6
 7use RunOpenCode\Component\Logger\Contract\LoggerContextInterface;
 8
 9final class ConditionalContextProvider implements LoggerContextInterface
10{
11    public function get(array $current): array
12    {
13        // Only add debug info if debug flag is set
14        if (isset($current['debug']) && $current['debug'] === true) {
15            return [
16                'memory_usage' => memory_get_usage(true),
17                'peak_memory' => memory_get_peak_usage(true),
18                'time' => microtime(true),
19            ];
20        }
21
22        return [];
23    }
24}

Performance considerations

Context providers are called for every log entry, so keep their implementation lightweight. Avoid expensive operations like database queries or external API calls in context providers.

If you need to include data that’s expensive to compute, consider:

  • Caching the data in the provider instance

  • Making the computation lazy (only when actually needed)

  • Using a different approach for expensive context data

Good example (lightweight):

 1<?php
 2
 3final readonly class EnvironmentContextProvider implements LoggerContextInterface
 4{
 5    public function get(array $current): array
 6    {
 7        return [
 8            'env' => $_ENV['APP_ENV'] ?? 'unknown',
 9            'hostname' => gethostname(),
10        ];
11    }
12}

Bad example (expensive - avoid this):

 1<?php
 2
 3final readonly class BadContextProvider implements LoggerContextInterface
 4{
 5    public function __construct(private Database $db)
 6    {
 7        // noop.
 8    }
 9
10    public function get(array $current): array
11    {
12        // DON'T DO THIS - database query on every log entry!
13        return [
14            'active_users' => $this->db->query('SELECT COUNT(*) FROM users WHERE active = 1'),
15        ];
16    }
17}

See the Logger Bundle documentation for details on Symfony integration.