-
Notifications
You must be signed in to change notification settings - Fork 1
Enhanced context format #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 1.x
Are you sure you want to change the base?
Changes from all commits
a2aff82
93024f0
3ca8bb6
00ac89b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Processor for built-in PHP types (null, scalar, array). | ||
* | ||
* Handles null and scalar values by passing them through as-is since they are already | ||
* suitable for structured logging. Arrays are processed recursively to handle nested structures. | ||
* | ||
* This consolidates the functionality of the former NullProcessor, ScalarProcessor, and ArrayProcessor | ||
* for better performance and simpler architecture. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @implements ContextProcessorInterface<null|scalar|array<array-key, mixed>, null|scalar|array<array-key, mixed>> | ||
*/ | ||
class BuiltInTypeProcessor implements ContextProcessorInterface | ||
{ | ||
public function canProcess(mixed $value): bool | ||
{ | ||
return $value === null || \is_scalar($value) || \is_array($value); | ||
} | ||
|
||
/** | ||
* @param null|scalar|array<array-key, mixed> $value | ||
* @param callable(mixed): mixed $recursiveProcessor | ||
* @return null|scalar|array<array-key, mixed> | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed | ||
{ | ||
// Handle arrays recursively | ||
if (\is_array($value)) { | ||
/** @var array<array-key, mixed> $processed */ | ||
$processed = []; | ||
|
||
/** | ||
* @var array-key $key | ||
* @var mixed $item | ||
*/ | ||
foreach ($value as $key => $item) { | ||
/** @psalm-suppress MixedAssignment - Intentionally processing mixed types */ | ||
$processed[$key] = $recursiveProcessor($item); | ||
} | ||
|
||
return $processed; | ||
} | ||
|
||
// Null and scalar values are already suitable for logging | ||
return $value; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Interface for context data processors. | ||
* | ||
* Each processor handles a specific type of data and converts it to a | ||
* format suitable for structured logging. | ||
* | ||
* @internal This interface is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @template TValue The input value type | ||
* @template TProcessed The processed output type | ||
*/ | ||
interface ContextProcessorInterface | ||
{ | ||
/** | ||
* Check if this processor can handle the given value. | ||
*/ | ||
public function canProcess(mixed $value): bool; | ||
|
||
/** | ||
* Process the value and return a serializable representation. | ||
* | ||
* @param TValue $value The value to process | ||
* @param callable(mixed): mixed $recursiveProcessor Function to process nested values recursively | ||
* @return TProcessed Processed value suitable for logging | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Manager for context data processors. | ||
* | ||
* Coordinates multiple processors to handle different data types for structured logging. | ||
* Processors are executed in registration order, with the first matching processor handling the value. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
*/ | ||
class ContextProcessorManager | ||
{ | ||
/** @var array<ContextProcessorInterface> */ | ||
private array $processors = []; | ||
|
||
public function __construct() | ||
{ | ||
$this->registerDefaultProcessors(); | ||
} | ||
|
||
/** | ||
* Register a processor. | ||
* Processors are checked in the order they are added. | ||
*/ | ||
public function addProcessor(ContextProcessorInterface $processor): void | ||
{ | ||
$this->processors[] = $processor; | ||
} | ||
|
||
/** | ||
* Process context data recursively. | ||
* | ||
* @template TKey of array-key | ||
* @template TValue | ||
* @param array<TKey, TValue> $context | ||
* @return array<string, mixed> | ||
*/ | ||
public function processContext(array $context): array | ||
{ | ||
if (empty($context)) { | ||
return []; | ||
} | ||
|
||
/** @var array<string, mixed> $processed */ | ||
$processed = []; | ||
|
||
/** | ||
* @var TKey $key | ||
* @var TValue $value | ||
*/ | ||
foreach ($context as $key => $value) { | ||
$stringKey = (string) $key; | ||
/** @psalm-suppress MixedAssignment - Intentionally processing mixed types */ | ||
$processed[$stringKey] = $this->processValue($value); | ||
} | ||
|
||
return $processed; | ||
} | ||
|
||
/** | ||
* Process a single value using the appropriate processor. | ||
*/ | ||
public function processValue(mixed $value): mixed | ||
{ | ||
foreach ($this->processors as $processor) { | ||
if ($processor->canProcess($value)) { | ||
return $processor->process($value, [$this, 'processValue']); | ||
} | ||
} | ||
|
||
// This should never happen due to FallbackProcessor, but just in case | ||
return \gettype($value); | ||
} | ||
|
||
/** | ||
* Register the default set of processors in the correct order. | ||
* Order matters: more specific processors should be registered first. | ||
*/ | ||
private function registerDefaultProcessors(): void | ||
{ | ||
// Built-in PHP types (null, scalar, array) - most common and efficient | ||
$this->addProcessor(new BuiltInTypeProcessor()); | ||
|
||
// Specific object types (before generic object processor) | ||
$this->addProcessor(new DateTimeProcessor()); | ||
$this->addProcessor(new ThrowableProcessor()); | ||
$this->addProcessor(new StringableProcessor()); | ||
|
||
// Resources | ||
$this->addProcessor(new ResourceProcessor()); | ||
|
||
// Generic object processor (before fallback) | ||
$this->addProcessor(new ObjectProcessor()); | ||
|
||
// Fallback processor (last resort) | ||
$this->addProcessor(new FallbackProcessor()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Processor for DateTime objects. | ||
* | ||
* Converts DateTime and DateTimeImmutable objects to ISO 8601 format | ||
* for consistent structured logging. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @implements ContextProcessorInterface<\DateTimeInterface, string> | ||
*/ | ||
class DateTimeProcessor implements ContextProcessorInterface | ||
{ | ||
public function canProcess(mixed $value): bool | ||
{ | ||
return $value instanceof \DateTimeInterface; | ||
} | ||
|
||
/** | ||
* @param \DateTimeInterface $value | ||
* @param callable(mixed): mixed $recursiveProcessor | ||
* @return string | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed | ||
{ | ||
return $value->format(\DateTimeInterface::ATOM); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Fallback processor for unknown types. | ||
* | ||
* Returns the type name for any value that couldn't be processed | ||
* by more specific processors. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @implements ContextProcessorInterface<mixed, string> | ||
*/ | ||
class FallbackProcessor implements ContextProcessorInterface | ||
{ | ||
public function canProcess(mixed $value): bool | ||
{ | ||
// This processor can handle anything as a last resort | ||
return true; | ||
} | ||
|
||
/** | ||
* @param callable(mixed): mixed $recursiveProcessor | ||
* @return string | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed | ||
{ | ||
return \gettype($value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Processor for generic objects. | ||
* | ||
* Attempts to convert objects to array representation using public properties, | ||
* or falls back to class name if no public properties are available. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @implements ContextProcessorInterface<object, array<string, mixed>|string> | ||
*/ | ||
class ObjectProcessor implements ContextProcessorInterface | ||
{ | ||
public function canProcess(mixed $value): bool | ||
{ | ||
return \is_object($value); | ||
} | ||
|
||
/** | ||
* @param object $value | ||
* @param callable(mixed): mixed $recursiveProcessor | ||
* @return array<string, mixed>|string | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed | ||
{ | ||
// Try to convert to array (for objects with public properties) | ||
$objectVars = \get_object_vars($value); | ||
|
||
if (!empty($objectVars)) { | ||
/** @var array<string, mixed> $processed */ | ||
$processed = []; | ||
/** | ||
* @var string $property | ||
* @var mixed $propertyValue | ||
*/ | ||
foreach ($objectVars as $property => $propertyValue) { | ||
/** @psalm-suppress MixedAssignment - Intentionally processing mixed types */ | ||
$processed[$property] = $recursiveProcessor($propertyValue); | ||
} | ||
return $processed; | ||
} | ||
Comment on lines
+29
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify cycle handling and depth limits in the recursive path Processing object graphs via Run: Expected:
🏁 Script executed: #!/bin/bash
# Inspect manager for processor order, cycle detection, and limits
rg -nP 'class\s+ContextProcessorManager\b' -C3
rg -nP '(register|default).*processor' -n -C3 src/Internal/ContextProcessor/ContextProcessorManager.php
rg -nP '(visited|seen|SplObjectStorage|maxDepth|maxDepth|depth|cycle)' -n -C3 src/Internal/ContextProcessor/ContextProcessorManager.php Length of output: 1187 🏁 Script executed: #!/bin/bash
# Display the registerDefaultProcessors method including the registration calls
awk 'NR>=70 && NR<=140' src/Internal/ContextProcessor/ContextProcessorManager.php Length of output: 1234 Add cycle detection and recursion depth limit to ContextProcessorManager ContextProcessorManager currently delegates recursion to ObjectProcessor without tracking already-visited objects or capping recursion depth. This opens the door to infinite loops on circular references and stack overflows on deeply nested data. • In src/Internal/ContextProcessor/ContextProcessorManager.php, Addressing these will prevent unbounded recursion and ensure safe handling of self-referencing or deeply nested object graphs. 🤖 Prompt for AI Agents
|
||
|
||
// Fallback to class name if no public properties | ||
return \get_class($value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Processor for resource types. | ||
* | ||
* Converts resources to string representation indicating the resource type. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @implements ContextProcessorInterface<resource, string> | ||
*/ | ||
class ResourceProcessor implements ContextProcessorInterface | ||
{ | ||
public function canProcess(mixed $value): bool | ||
{ | ||
return \is_resource($value); | ||
} | ||
|
||
/** | ||
* @param resource $value | ||
* @param callable(mixed): mixed $recursiveProcessor | ||
* @return string | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed | ||
{ | ||
return \get_resource_type($value) . ' resource'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace RoadRunner\PsrLogger\Internal\ContextProcessor; | ||
|
||
/** | ||
* Processor for objects implementing the Stringable interface. | ||
* | ||
* Converts Stringable objects to their string representation. | ||
* | ||
* @internal This class is internal to the PSR Logger implementation and should not be used directly. | ||
* | ||
* @implements ContextProcessorInterface<\Stringable, string> | ||
*/ | ||
class StringableProcessor implements ContextProcessorInterface | ||
{ | ||
public function canProcess(mixed $value): bool | ||
{ | ||
return $value instanceof \Stringable; | ||
} | ||
|
||
/** | ||
* @param \Stringable $value | ||
* @param callable(mixed): mixed $recursiveProcessor | ||
* @return string | ||
*/ | ||
public function process(mixed $value, callable $recursiveProcessor): mixed | ||
{ | ||
return (string) $value; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add recursion guards (max depth + object cycle detection) to prevent runaway processing
Self-referential arrays/objects in context can cause infinite recursion and memory exhaustion. Add a shallow guard and track visited objects per processing call. Keep the external API unchanged by capturing guard state in the recursive callback.
Illustrative diff:
Note: This change confines guard state to each processContext call, so no cross-call retention.
📝 Committable suggestion