From f7f3ec8c89e52de84701fc4beb3906e5817bcb3f Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 25 Jul 2026 03:49:32 +0200 Subject: [PATCH 1/6] New blogpost: general PSR-3 logging guidelines by yours truly --- .../2026-07-24-php-logging-with-psr-3.md | 540 ++++++++++++++++++ .../distributed-trace-jaeger.png | Bin 0 -> 39979 bytes 2 files changed, 540 insertions(+) create mode 100644 source/_posts/2026-07-24-php-logging-with-psr-3.md create mode 100644 source/img/posts/2026-07-24-php-logging-with-psr-3/distributed-trace-jaeger.png diff --git a/source/_posts/2026-07-24-php-logging-with-psr-3.md b/source/_posts/2026-07-24-php-logging-with-psr-3.md new file mode 100644 index 00000000..3e0e8dae --- /dev/null +++ b/source/_posts/2026-07-24-php-logging-with-psr-3.md @@ -0,0 +1,540 @@ +--- +layout: post +title: Proper logging in PHP with PSR-3 +category: Blog +tags: [ "logging", "php", "software design", "psr-3" ] +year: 2026 +month: 07 +day: 24 +published: true +summary: "Logging with PSR-3 in PHP - the proper way" +description: Common logging usage in PHP, how to do it well, and what to avoid +--- + +

Who is this article for?

+ +

+ This post is for people that do day-by-day busywork coding, and for team leads that want to direct their + peers towards better logging practices. +

+ +

+ Note that this article comes from my regular need to present these exact points to different people, multiple + times a year, in multiple teams, in multiple companies. +

+ +

+ Also, we will not talk about how to configure a PSR-3 logger, + but rather how to use one. +

+ +

Handling errors properly

+ +

+ Error/exception handling is the main use-case for logging. +

+ +

+ When logging exceptions, please pass the Throwable instance to the 'exception' context key. +

+ +~~~php +try { + // logic here +} catch (SomeException $failed) { + $this->logger->error('Something went wrong', [ + 'exception' => $failed, + ]); +} +~~~ + +

+ Avoid cluttering the logger call with data deriving from the exception: it's not the logger call-site's job, + and you are just repeating work. +

+ +

+ I often see unnecessary code like: +

+ +~~~php +try { + // logic here +} catch (SomeException $failed) { + $this->logger->error('Something went wrong', [ + // first mistake: we forgot 'exception' + 'previous' => $failed->getPrevious(), // let the logger do this! + 'line' => $failed->getLine(), // already part of the stack trace + 'error' => $failed->getMessage(), // also always rendered + 'error_type' => $failed::class, // done by the logger, usually + ]); +} +~~~ + +

+ The logger itself must instead be configured (and usually already is configured) to render: +

+ + + +

+ Your responsibility is to instead pass context information that the logger can't infer on its own. +

+ +

+ What if my code fails gracefully, and does not raise an exception? +

+ +~~~php +if (is_wrong($something)) { + $this->logger->warn('Something went wrong', ['something' => $something]); +} +~~~ + +

+ For business-specific failures that deserve a type, we can upcast them to a Throwable anyway: +

+ +~~~php +if (is_wrong($something)) { + $this->logger->warn('Something went wrong', [ + 'something' => $something, + 'exception' => new SomethingWentWrong($something), // Throwable + ]); +} +~~~ + +

+ Having clear exception types, even if used just with the logger, will allow you to easily + detect multiple code locations affected by the same kind of failure later on. +

+ +

+ Beware: raising exceptions and logging both come with substantial CPU, memory and IO overhead, + so you should always decide carefully when logging and exceptions can be raised in a tight loop. +

+ +

+ Remember also that a Throwable always collects the entire stack trace it was raised from, + which may affect garbage collection, if the logger keeps messages in memory. +

+ +

+ Loggers are perfectly capable of determining the stack trace of a raised log message: the 'exception' + key is not necessary for that feature to work, so creating a new Throwable is your decision. +

+ +

+ "Some logs" are better than "no logs" +

+ +

+ I'm personally not a fan of cluttering code with log and debug statements, but it is undeniable that logging will + help you keep a general understanding of how your software is behaving in production, both when healthy or unhealthy. +

+ +

+ A system that produces no output may be functioning perfectly, or be completely broken: having some insight + into whether it is "still ticking" is a good idea. +

+ +
+ Not sure if everything OK, or monitoring is broken. +
+ +

+ I recommend having $logger->info('Heartbeat'); or similar calls in code that runs in long-running + operations, polling loops, or that are sitting idly, waiting for input: +

+ +~~~php +$eventLoop->whenever(function ($someEvent) { + // main application logic +}); + +$eventLoop->periodically(function () { + $this->logger->info('still here, waiting for you'); +}, Time::seconds()->multiply(120)); + +$eventLoop->run(); +~~~ + +

+ You can either configure the logger or the call-site to only log a percentage of the calls, + where the system would otherwise become too chatty. +

+ +

+ Periodically logging is not a replacement for + + a health-check probe + . +

+ +

Injecting loggers

+ +

+ Please use dependency injection when requesting a logger: +

+ +~~~php +final readonly class MyService implements SomeService +{ + public function __construct(private LoggerInterface $logger) {} + + function someLogic() { + $this->logger->debug('Look ma, I got the logger via DI!'); + } +} +~~~ + +

+ Besides avoiding the pitfalls of service location and global state, you get: +

+ + + +

+ Here's how one could customize the logger in a service definition: +

+ +~~~php +$serviceDefinitions->add( + SomeService::class, + function (MainLogger $rootLogger) { + return new MyService( + $rootLogger + ->forWiredService(SomeService::class) + ->withEnvironment($someEnvironment) + ); + } +); +~~~ + +

+ Here's how one could work with log messages in a test: +

+ +~~~php +#[Test] +function my_service_does_a_bunch_of_things_in_a_very_specific_order(): void +{ + $testSpyLogger = new RecordingLogger(); + + $systemUnderTest = new MyService($testSpyLogger); + + $systemUnderTest->doSomeWork(); + + Assert::equals( + [ + 'Extracted data', + 'processed row A', + 'processed row B', + 'failed to process row C', + 'finished', + ], + $testSpyLogger->messages + ); +} +~~~ + +

Using the logger for measurements?

+ +

+ I often see teams using loggers to record metric information, then grepping through the result, + to produce graphs or further analytics data: +

+ +~~~php +final readonly class LoggedCart implements CartService { + public function __construct( + // ... + private LoggerInterface $logger, + ) {} + + public function cartCheckout( + // ... + ): void { + // ... + + $this->logger->info( + 'cart.checkout', + ['total_amount' => $cart->totalAmount()] + ); + } +} +~~~ + +

+ While you can most certainly do that, the logger is the wrong abstraction for metrics. +

+ +

+ The correct tool for metrics is + OTEL metrics, + (although any "metrics-alike" tooling works too): +

+ +~~~php +final readonly class LoggedCart implements CartService { + public function __construct( + // ... + MeterProvider $metrics, + ) { + $this->checkoutAmounts = $metrics->createHistogram('cart.checkout.total_amount'); + } + + public function cartCheckout( + // ... + ): void { + // ... + + $this->checkoutAmounts->record($cart->totalAmount()); + } +} +~~~ + +

+ See also the MeterProviderInterface. +

+ +

+ With this setup, your metrics can be collected more efficiently (in batches), and can be sent to dedicated + backends, such as time series databases, ready to be viewed. +

+ +

+ Note that you are still free to wire the metrics reader so that it forwards recorded metrics to your logger! +

+ +

Logging durations

+ +

+ You will often see developers logging the elapsed time for an operation: +

+ +~~~php +final readonly class CreditCardCheckout implements Checkout { + public function __construct( + // ... + private LoggerInterface $logger, + ) {} + + public function cartCheckout( + // ... + ): void { + $start = $this->clock->now(); + $this->logger->debug('checkout.start', ['time' => $start]) + // ... + + $end = $this->clock->now(); + $this->logger->log( + 'checkout.end', + [ + 'time' => $end, + 'duration' => $end->diff($start) + ] + ); + } +} +~~~ + +

+ Similarly to metrics, a logger is not the correct abstraction: instead, look at + OTEL Tracing +

+ +

+ Traces allows for a cleaner implementation: +

+ +~~~php +final readonly class CreditCardCheckout implements Checkout { + public function __construct( + // ... + private Tracer $tracer, + ) {} + + public function cartCheckout( + // ... + ): void { + $span = $this->tracer->spanBuilder('checkout') + ->startSpan(); + + // ... + + $span->end(); + } +} +~~~ + +

+ The API can be further improved with your own Tracer additions, and you can still + send span start/end to your logger. +

+ +

+ By using the correct abstraction, dedicated trace collector software (such as + Jaeger, Zipkin, AWS X-Ray, etc.) + can give you full insight into how operations are nested, run concurrently, etc: +

+ +

+ An example trace containing multiple parallel spans in different services. Image taken from https://github.com/open-telemetry/opentelemetry-php/tree/c948c8fe4eff3c6264f02b6a92e8b44f577ef2d5/examples/traces/demo +

+ +

Message interpolation

+ +

+ Please don't do this: +

+ +~~~php +$this->logger->info('user ' . $user->username() . ' logged in'); +~~~ + +

+ PSR-3 specifies a {bracket_based} message interpolation convention, which you can rely upon: +

+ +~~~php +$this->logger->info( + 'user {username} logged in', + ['username' => $user->username()] +); +~~~ + +

+ With the above, you gain: +

+ + + +

Log levels

+ +

+ The log level mostly has an effect on: +

+ + + +

+ It is important to not raise the log level unnecessarily, + or you may run into a full disk, capped out monitoring system, full email inbox, or annoyed + on-call coworker. +

+ +
+ Logs should capture our attention only when relevant: attention is a valuable currency +
+ +

+ When reviewing new code, always ask yourself whether you can "push the log level down". +

+ +

+ For tight loops, debug could suffice. You also don't want to see these messages in production: they + should be turned off by default. +

+ +

+ Successful operations should probably receive an info level: you also want to know if a system + is working correctly. +

+ +

+ For acceptable blips in your data, a notice could work. +

+ +

+ Data processing that failed, but recovered, should probably receive a warning. +

+ +

+ Anything from exception up should be discussed within your team and business domain, when introduced. +

+ +

Avoid performing I/O during logging

+ +

+ Logging is a delicate matter: avoid making it more delicate, as it is your last resort in trying to + understand a failing system. +

+ +

+ Following code is problematic: +

+ +~~~php +$this->logger->error( + 'user {username} failed to log in', + [ + 'exception' => $exception, + 'username' => $this->users->get($userId)->username() + ] +); +~~~ + +

+ At this stage, you do not know if the system is in an irrecoverable state, and this entire expression may fail. +

+ +

+ Additionally, your logging operation is potentially slowing down the system: perhaps logging $userId + sufficed? +

+ +

+ As a good rule of thumb, the logger call-site should not perform expressions that can @throw, + or which interact with global state (a @phpstan-pure or @psalm-pure declaration can help). +

+ +

Conclusion

+ +

+ This article hopefully contains things that you can point at when discussing logger usages with your colleagues: + I sure needed this compendium of patterns for my future self ☺️ +

\ No newline at end of file diff --git a/source/img/posts/2026-07-24-php-logging-with-psr-3/distributed-trace-jaeger.png b/source/img/posts/2026-07-24-php-logging-with-psr-3/distributed-trace-jaeger.png new file mode 100644 index 0000000000000000000000000000000000000000..48a28dc37057ce0b82b5a5e0110ed84d092e83e1 GIT binary patch literal 39979 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

8Y5=aLuY_@-nf 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 Date: Tue, 28 Jul 2026 14:45:59 +0200 Subject: [PATCH 2/6] Adding note about using metrics for healthchecks --- source/_posts/2026-07-24-php-logging-with-psr-3.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/_posts/2026-07-24-php-logging-with-psr-3.md b/source/_posts/2026-07-24-php-logging-with-psr-3.md index 3e0e8dae..681eb6a5 100644 --- a/source/_posts/2026-07-24-php-logging-with-psr-3.md +++ b/source/_posts/2026-07-24-php-logging-with-psr-3.md @@ -183,10 +183,15 @@ $eventLoop->run();

Periodically logging is not a replacement for - a health-check probe + an external health-check probe .

+

+ Note that you may want to also use metrics instead + (more on this below). +

+

Injecting loggers

@@ -264,7 +269,7 @@ function my_service_does_a_bunch_of_things_in_a_very_specific_order(): void } ~~~ -

Using the logger for measurements?

+

Using the logger for measurements?

I often see teams using loggers to record metric information, then grepping through the result, From 6aefaf8352ba1437a2d6935730793060ac8c98b1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 28 Jul 2026 14:50:02 +0200 Subject: [PATCH 3/6] Noting how the PSR-3 interpolation pattern needs to be enabled --- source/_posts/2026-07-24-php-logging-with-psr-3.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/_posts/2026-07-24-php-logging-with-psr-3.md b/source/_posts/2026-07-24-php-logging-with-psr-3.md index 681eb6a5..fb0333db 100644 --- a/source/_posts/2026-07-24-php-logging-with-psr-3.md +++ b/source/_posts/2026-07-24-php-logging-with-psr-3.md @@ -451,6 +451,11 @@ $this->logger->info( +

+ Remember that this behaviour needs to be + enabled. +

+

Log levels

From 9ea74c2b8d18c34cb5a1b4548c47dbe8482219be Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 28 Jul 2026 14:53:40 +0200 Subject: [PATCH 4/6] Clarifying warning/failure scenarios --- source/_posts/2026-07-24-php-logging-with-psr-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_posts/2026-07-24-php-logging-with-psr-3.md b/source/_posts/2026-07-24-php-logging-with-psr-3.md index fb0333db..028b96df 100644 --- a/source/_posts/2026-07-24-php-logging-with-psr-3.md +++ b/source/_posts/2026-07-24-php-logging-with-psr-3.md @@ -500,7 +500,7 @@ $this->logger->info(

- Data processing that failed, but recovered, should probably receive a warning. + Data processing that failed, but recovered with a fallback, should probably receive a warning.

From a8d7dc5ce8b21a7e6295f61074e5b0014dab25fb Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 28 Jul 2026 14:59:44 +0200 Subject: [PATCH 5/6] Using today's date --- ...sr-3.md => 2026-07-28-php-logging-with-psr-3.md} | 4 ++-- .../distributed-trace-jaeger.png | Bin 2 files changed, 2 insertions(+), 2 deletions(-) rename source/_posts/{2026-07-24-php-logging-with-psr-3.md => 2026-07-28-php-logging-with-psr-3.md} (99%) rename source/img/posts/{2026-07-24-php-logging-with-psr-3 => 2026-07-28-php-logging-with-psr-3}/distributed-trace-jaeger.png (100%) diff --git a/source/_posts/2026-07-24-php-logging-with-psr-3.md b/source/_posts/2026-07-28-php-logging-with-psr-3.md similarity index 99% rename from source/_posts/2026-07-24-php-logging-with-psr-3.md rename to source/_posts/2026-07-28-php-logging-with-psr-3.md index 028b96df..697f5372 100644 --- a/source/_posts/2026-07-24-php-logging-with-psr-3.md +++ b/source/_posts/2026-07-28-php-logging-with-psr-3.md @@ -5,7 +5,7 @@ category: Blog tags: [ "logging", "php", "software design", "psr-3" ] year: 2026 month: 07 -day: 24 +day: 28 published: true summary: "Logging with PSR-3 in PHP - the proper way" description: Common logging usage in PHP, how to do it well, and what to avoid @@ -412,7 +412,7 @@ final readonly class CreditCardCheckout implements Checkout {

An example trace containing multiple parallel spans in different services. Image taken from https://github.com/open-telemetry/opentelemetry-php/tree/c948c8fe4eff3c6264f02b6a92e8b44f577ef2d5/examples/traces/demo

diff --git a/source/img/posts/2026-07-24-php-logging-with-psr-3/distributed-trace-jaeger.png b/source/img/posts/2026-07-28-php-logging-with-psr-3/distributed-trace-jaeger.png similarity index 100% rename from source/img/posts/2026-07-24-php-logging-with-psr-3/distributed-trace-jaeger.png rename to source/img/posts/2026-07-28-php-logging-with-psr-3/distributed-trace-jaeger.png From 1072b8584eeacc339ae689a14544cf455c361287 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 28 Jul 2026 15:38:36 +0200 Subject: [PATCH 6/6] Removing TMP directory removals: they operate outside regular security boundaries Should help with: ``` + /nix/store/i2vmgx46q9hd3z6rigaiman3wl3i2gc4-coreutils-9.9/bin/rm -f default.nix + rm -rf -- /home/runner/work/_temp/tmp.dr0DBe199C rm: cannot remove '/home/runner/work/_temp/tmp.dr0DBe199C/src/app/config/sculpin_site.yml': Permission denied rm: cannot remove '/home/runner/work/_temp/tmp.dr0DBe199C/src/app/config/sculpin_kernel.yml': Permission denied rm: cannot remove '/home/runner/work/_temp/tmp.dr0DBe199C/src/app/SculpinKernel.php': Permission denied ``` Ref: https://github.com/Ocramius/ocramius.github.com/actions/runs/30361448929/job/90281953668?pr=133 --- flake.nix | 2 -- php-packages.nix | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index 7fa3a20e..5d723bb1 100644 --- a/flake.nix +++ b/flake.nix @@ -81,7 +81,6 @@ update-php-packages = pkgs.writeShellScriptBin "generate-composer-to-nix.sh" '' set -euxo pipefail TMPDIR="$(${pkgs.coreutils}/bin/mktemp -d)" - trap 'rm -rf -- "$TMPDIR"' EXIT mkdir "$TMPDIR/src" mkdir "$TMPDIR/composer2nix" ${pkgs.coreutils}/bin/cp "${./composer.json}" "$TMPDIR/src/" @@ -117,7 +116,6 @@ publish-to-github-pages = pkgs.writeShellScriptBin "publish-blog.sh" '' set -euxo pipefail TMPDIR="$(${pkgs.coreutils}/bin/mktemp -d)" - trap 'rm -rf -- "$TMPDIR"' EXIT cd "$TMPDIR" ${pkgs.git}/bin/git clone git@github.com:Ocramius/ocramius.github.com.git . git checkout master diff --git a/php-packages.nix b/php-packages.nix index a0b77716..71981ef5 100644 --- a/php-packages.nix +++ b/php-packages.nix @@ -858,7 +858,7 @@ let in composerEnv.buildPackage { inherit packages devPackages noDev; - name = "ocramius.github.com"; + name = "ocramius.github.io"; src = composerEnv.filterSrc ./.; executable = false; symlinkDependencies = false;