skip to content

Logging & Caching PSRs

PSR-3 gives one logger contract with eight levels and {placeholder} context, while PSR-6 pools and PSR-16 simple cache define cache contracts. Interviewers ask which cache PSR to depend on.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In PSR-3, what are the eight log levels in Psr\Log\LogLevel, and how do you choose between them?

level: juniorimportance: must knowfreq 55%

answer

  1. borrowed from RFC 5424 syslog
  2. one method per level, plus log()
  3. emergency, alert, critical at the top
  4. warning is not an error
  5. unknown level: Psr\Log\InvalidArgumentException

basics

~20 s

PSR-3 defines eight RFC 5424 levels: emergency, alert, critical, error, warning, notice, info and debug, each with its own LoggerInterface method. Choose by urgency: from system unusable, through failures and oddities, down to routine events and diagnostics.

solid answer

~40 s

`Psr\Log\LogLevel` holds eight string constants taken from the RFC 5424 syslog levels, and `LoggerInterface` has one method per level plus `log($level, $message, $context)`. From most to least severe: `emergency` (system unusable), `alert` (act now: whole site down, database unavailable), `critical` (a component unavailable, an unexpected exception), `error` (runtime errors that need no immediate action but should be monitored), `warning` (exceptional but not errors, such as deprecated API use), `notice` (normal but significant), `info` (interesting events such as a user login) and `debug` (detailed diagnostics). Calling `log()` with a level constant must behave like the matching method, and a level the implementation does not know must throw `Psr\Log\InvalidArgumentException`. PSR-3 does not define a minimum-level filter; that is the implementation's configuration.

code

php · 20 lines
php
<?php
declare(strict_types=1);

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

function reportRateFetch(LoggerInterface $logger, string $pair, ?\Throwable $failure, bool $servedStale): void
{
    if ($failure === null) {
        $logger->info('Fetched rate for {pair}', ['pair' => $pair]);
        return;
    }

    // Stale fallback kept the caller working: warning. No fallback: error.
    $level = $servedStale ? LogLevel::WARNING : LogLevel::ERROR;
    $logger->log($level, 'Rate fetch for {pair} failed', [
        'pair' => $pair,
        'exception' => $failure,
    ]);
}

go deeper

for a junior

Recite the eight levels in order, emergency down to debug, and give one example each for error, warning and info.

for a middle

Explain log() versus the level methods, the InvalidArgumentException rule for unknown levels, and why filtering is outside PSR-3.

for a senior

Set level conventions for a codebase or library so on-call alerts stay meaningful: what pages someone, what only feeds dashboards, what is debug noise.

for a principal

Balance log volume, cost and signal across services: which levels are retained, sampled or dropped, and how level discipline is enforced in review.

## Where the levels come from **PSR-3** is the PHP-FIG logger interface, shipped in the `psr/log` package. Its levels are not invented: they are the eight severities of **RFC 5424**, the syslog protocol. `Psr\Log\LogLevel` exposes them as string constants, and `Psr\Log\LoggerInterface` has one method for each. | Constant | Method | Specification's description and example | |---|---|---| | `LogLevel::EMERGENCY` = `'emergency'` | `emergency()` | system is unusable | | `LogLevel::ALERT` = `'alert'` | `alert()` | action must be taken immediately: entire website down, database unavailable | | `LogLevel::CRITICAL` = `'critical'` | `critical()` | critical conditions: application component unavailable, unexpected exception | | `LogLevel::ERROR` = `'error'` | `error()` | runtime errors that do not need immediate action but should be logged and monitored | | `LogLevel::WARNING` = `'warning'` | `warning()` | exceptional occurrences that are not errors: deprecated APIs, poor use of an API | | `LogLevel::NOTICE` = `'notice'` | `notice()` | normal but significant events | | `LogLevel::INFO` = `'info'` | `info()` | interesting events: user logs in, SQL logs | | `LogLevel::DEBUG` = `'debug'` | `debug()` | detailed debug information | ## The ninth method `log($level, $message, array $context = [])` takes the level as its first argument. The specification sets two rules: 1. Calling `log()` with one of the eight constants **must** have the same result as calling the level-specific method. 2. Calling it with a level the specification does not define **must** throw `Psr\Log\InvalidArgumentException` if the implementation does not know that level. Users should not use custom levels unless they are sure the implementation supports them. `log()` is useful when the level is computed at runtime, for example from a configuration value or from how bad an outcome was. ## Choosing a level in practice Take a library that fetches exchange rates from a remote service and caches them: - **debug**: the raw response size and timings, useful only while investigating; - **info**: rates fetched and cached for a currency pair; - **notice**: the remote service returned a rate outside the usual range, but valid; - **warning**: the fetch failed, and the library served a stale cached rate instead; - **error**: the fetch failed and there was no cached rate to fall back on, so the call failed; - **critical**: every configured rate source is unavailable, so the component is out of action; - **alert** and **emergency**: rarely right inside a library, because a library seldom knows that the whole system is down; they belong to the application's own health checks. The common thread is **who has to act and how soon**: debug and info need no one, warning and error are for monitoring and later triage, critical and above should wake someone. ## What PSR-3 leaves to the implementation PSR-3 defines how to *write* log records, not how to route them. It does not define: - a minimum level below which records are dropped; - where records go (files, standard error, a log collector); - formatting or timestamps. Those are configured on the concrete logger the application installs. A library that depends only on `LoggerInterface` should therefore log honestly at the right level and let the application decide what to keep. ## Helpers shipped with the interface - `Psr\Log\AbstractLogger` and `Psr\Log\LoggerTrait` let an implementation write only `log()`; the eight level methods forward to it. Because traits cannot implement interfaces, a class using the trait must still declare `implements LoggerInterface`. - `Psr\Log\NullLogger` discards everything, as a fallback when no logger is provided. - `Psr\Log\LoggerAwareInterface` has a single `setLogger()` method, and `LoggerAwareTrait` implements it with a `$this->logger` property. ## Mistakes interviewers listen for - Logging every caught exception at `alert` or `emergency`, which trains people to ignore the pager. - Treating `warning` as "a small error": the specification describes it as something exceptional that is **not** an error. - Inventing a level such as `'trace'` and passing it to `log()`, which a conforming implementation that does not know it must reject with `Psr\Log\InvalidArgumentException`.

  • What must a PSR-3 logger do when log() receives the level 'trace'?
    If the implementation does not know that level, it must throw `Psr\Log\InvalidArgumentException`. The eight `LogLevel` constants are the only levels the standard defines, and users should not pass custom levels unless they know the installed logger supports them.
  • Does PSR-3 let a library set the minimum level that gets recorded?
    No. `LoggerInterface` only has methods for writing records. Filtering by level, choosing destinations and formatting are configured on the concrete logger by the application. A library should log at the honest level and leave filtering to whoever installs it.

saying these in an interview costs you the question

  • Says PSR-3 has five levels: debug, info, warning, error and fatal.
  • Describes warning as a minor error rather than a non-error exceptional event.
  • Believes LoggerInterface lets callers set a minimum log level.
  • Logs every caught exception at emergency so it gets noticed.
  • Thinks log() silently accepts any custom level string.
open as a page

What is the difference between PSR-6 CacheItemPoolInterface and PSR-16 CacheInterface, and which should a reusable PHP library depend on?

level: middleimportance: must knowfreq 48%

basics

~20 s

PSR-6 is a pool of item objects: getItem(), isHit(), set(), save(), with deferred saves. PSR-16 is a simple key/value API: get(), set(), delete() with a TTL. A library needing only get/set usually depends on PSR-16.

open as a page

In PSR-3, why should a log message use {placeholder} tokens filled from the context array, and where does a caught exception belong?

level: middleimportance: should knowfreq 45%

basics

~20 s

PSR-3 intends the message to be a static string with {name} placeholders and all variable data in the context array, so the logger can escape it per output format. A caught exception goes in the context under the 'exception' key.

open as a page

In PSR-6, what do CacheItemPoolInterface::saveDeferred() and commit() do, and what may a pool do with deferred items before commit()?

level: middleimportance: should knowfreq 25%

basics

~20 s

saveDeferred() queues an item for later persistence and commit() persists every queued item, so a pool can batch writes. A pool may persist deferred items earlier, must not lose them, and must return them from getItem() before commit.

open as a page

When writing a framework-agnostic PHP library that should log and cache, how do you depend on PSR-3 and PSR-16 without forcing either on the application?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Require only psr/log and psr/simple-cache, accept LoggerInterface and CacheInterface in the constructor, default the logger to NullLogger and make the cache optional. The application injects its own implementations; the library never names a vendor.

open as a page

Under PSR-6 and PSR-16, which cache key characters and lengths must every implementation accept, and which characters are reserved?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Both PSRs require implementations to accept keys of A-Z, a-z, 0-9, underscore and period, up to 64 characters in UTF-8. The characters {}()/@: are reserved and must not be supported; an illegal key throws the standard's InvalidArgumentException.

open as a page