Skip to content

The managed store does not pin lc_messages, so a localised severity label makes the log parser's residue class unreachable #3053

Closed

Description

@erikdarlingdata

The managed store does not pin lc_messages, so it runs whatever locale the host OS gave initdb. PostgreSQL translates its messages under that locale — including the severity label itself. A German host writes FEHLER: where an English one writes ERROR:.

Every rule in the tree that matches PostgreSQL message text assumes English. The store is product-managed, so that assumption is the product's to make true rather than each parser's to work around.

Where it bites hardest, measured

StoreLogClassifier (V111, #3021) anchors on the severity field and decides retention with:

public static bool IsAtLeastWarning(string? severity) =>
    severity is "WARNING" or "ERROR" or "FATAL" or "PANIC";

and its default arm is:

return IsAtLeastWarning(severity)
    ? (UnclassifiedClass, true)     // retained, keeps its text
    : (RoutineClass, false);        // counted at most, text dropped

So an unrecognised severity token cannot satisfy IsAtLeastWarning, cannot reach unclassified-retained, and lands in the routine arm. On a non-English store the residue class that exists specifically to preserve unrecognised shapes is unreachable, and a FEHLER: line is dropped without text.

That inverts the safety direction. The design's load-bearing property is that a rule the table forgot costs a heading, never a row. Under a localised severity label it costs the row — not "visible but unclassified" but not visible.

The fix is one line, in a mechanism that already exists

DarlingManagedPostgres already owns a marker-guarded postgresql.conf append that is re-checked every start and self-heals — its own doc comment describes a crash between initdb and the append recovering on the next run, with each marker checked independently and a marker-present-but-different block never rewritten in place.

Adding lc_messages = 'C' through that mechanism makes the English assumption true by construction instead of by luck. C rather than en_US.UTF-8 because it is guaranteed present everywhere and needs no locale to be installed on the host.

Two things to check while doing it, rather than assume:

  • Whether it needs a restart or a reload. lc_messages is SIGHUP-changeable, so a reload should be enough, but the existing append path's behaviour on an already-running server is what decides whether an operator sees the change this start or next.
  • Whether anything wants localised messages. Nothing in this product surfaces PostgreSQL message text to an end user in their own language; it goes to parsers and to operator-facing diagnostics that are English throughout. So C costs nothing here — but say that explicitly rather than leaving it implied.

Verification this needs

A pin that the conf append includes lc_messages, and — more useful — one asserting that every severity token IsAtLeastWarning accepts is one error_severity() can actually write under lc_messages = 'C'. That ties the parser's assumption to the config that guarantees it, so removing the pin from the conf breaks the test rather than silently breaking the parser.

Worth also correcting the same latent assumption wherever else message text is matched, or at minimum enumerating those sites so the next reader knows the guarantee they now rest on.

Provenance

Raised in review of #3046 by fable-loop, which caught that that PR's own stated limitation was optimistic in the wrong direction. Confirmed here against origin/dev: lc_messages appears zero times in DarlingManagedPostgres.cs, IsAtLeastWarning is the four literals above, and the default arm is as quoted. #3046's body was corrected before merge to say routine/dropped rather than unclassified/safe.

Activity

  1. erikdarlingdata commented on Sep 6, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Correcting this issue's premise, which I got wrong. It says initdb takes lc_messages from the host OS. That is initdb's default, and this product already overrides it: DarlingManagedPostgres.cs:1142 passes --locale=C to initdb, landed 2026-08-08 with #2128. initdb templates the resolved lc_* values into the conf it generates, so a cluster any current build initialized was never running a translated catalogue.

    So the fresh-install exposure I described does not exist. PR #3058 still earns its place, for narrower and more honest reasons now stated in its body: it makes the parser's dependency explicit and testable, so moving --locale for the collation reason it exists for cannot silently re-localise messages; it reaches data directories the current initdb call did not create; and it corrects an edited value by last-occurrence. Also recorded there: postgresql.auto.conf is read after postgresql.conf, so an ALTER SYSTEM SET lc_messages still wins — deliberately not forced through the -o override, because this is diagnostic fidelity rather than a security boundary like listen_addresses.

    The exposure that does exist is target-side, and this pin cannot reach it. Filed separately rather than grown into #3058. Four parsers read a customer-owned instance's log, and both collectors are AppliesTo => true, so on a non-English target they go blind — and zero rows is the healthy state for pg_deadlocks, which makes a locale-blinded reader indistinguishable from a quiet server.

  2. added a commit that references this issue on Sep 6, 2026
    33ec6c7
  3. erikdarlingdata commented on Sep 6, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Closed by #3058, merged to dev at 9315a46d9b00063858ff9ac9be66e6e97b734454. Closing by hand because a closing keyword does not fire against dev.

    A v10 marker-guarded postgresql.conf block carrying lc_messages = 'C', appended after v9 through the mechanism the class already owns — a new versioned marker rather than an edit of v9, because a marker-present-but-different block is never rewritten in place, so an in-place edit would never reach a store already in the field.

    This issue's premise was wrong and the fix's justification is narrower than filed. initdb taking lc_messages from the host OS is initdb's default; DarlingManagedPostgres.cs:1142 already passes --locale=C, landed 2026-08-08 with #2128, and initdb templates the resolved lc_* into the conf it generates. So a cluster any current build initialised was never running a translated catalogue.

    What it does buy, and the strongest ground is the third: it reaches data directories the current initdb call did not create; it corrects an edited value by last-occurrence; and — the one that survives the premise being wrong — it makes the parser's dependency on --locale=C explicit and testable. That flag exists for collation. Nothing stopped it moving for a collation reason and silently re-localising every message the log parsers match. Now that cannot happen without a test failing.

    The accounting is honest where it costs: postgresql.auto.conf is read after postgresql.conf, so an ALTER SYSTEM SET lc_messages still wins — deliberately not forced through the -o override, because this is diagnostic fidelity rather than a security boundary like listen_addresses.

    The load-bearing pin derives both sides: accepted severities walked out of StoreLogClassifier via CSharpSourceWalker (the gate is a pattern, invisible to reflection), the pinned locale reflected out of every Build*ConfAppend factory, the guaranteed vocabulary from PrimarySeverities. Red-proofed five ways.

    The marker-reachability pin caught a flaw in its own first form — it asserted EnsureConfAppended names every declared marker, and v8 correctly failed, being the one block keyed on a fingerprint rather than its marker's absence. Restated as reachability.

    Scope, stated rather than implied: this reaches the managed store only. StoreLogSweep runs on every store shape, so a bring-your-own store is still classified under its owner's locale, and the product can pin its own store but not someone else's. The target-side exposure — four parsers reading a customer instance's log in English, where a locale-blinded pg_deadlocks read is indistinguishable from a quiet server — is #3061.

    Not verified: no live managed store is startable in this environment, so the conf-append behaviour is source-pinned only, and the four new tests could not be confirmed by name from CI (MTP prints no per-test Passed lines and the .trx is not uploaded). The lane explicitly declined to offer a discovery-count delta as proof.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions