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:
Context from each provider is collected (in the order they were registered)
Each provider’s context is merged with the accumulated context
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.