# `checks.module-eval-journald-severity` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). # # The odd one out in this directory: every sibling suite evaluates a host or an # agent and reads the config that falls out, where this one reads a single file # that is already a value. It is a suite of its own for the reason the operator # gave when she saw the mapping asserted in two tiers at once — **test the # thing only once**. The table lives in one file, so its contents are one # subject and belong to one case. Each tier's suite then asks a different # question, about wiring rather than contents: does MY receiver carry this. # # What that buys is a readable failure. Invert the table and exactly this suite # goes red, naming the table. Drop the parser from one tier's receiver and # exactly that tier goes red, naming the tier. Neither failure makes you read # the other one to work out which of the two happened. # # Needs no `nixosSystem` fixture at all — the thing under test is a file — so # this suite costs nothing to evaluate next to its siblings. { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) journaldSeverityParser runGroup ; # The parser's own mapping read back as a plain `PRIORITY -> severity-name` # function, so the case below can ask what a priority MAPS TO instead of # matching on the shape of the attrset that produces it. `null` for a # priority the table does not name, which the case uses as its control. severityOf = priority: let hits = lib.attrNames ( lib.filterAttrs (_: v: lib.elem priority (lib.toList v)) journaldSeverityParser.mapping ); in if journaldSeverityParser == null || hits == [ ] then null else lib.head hits; cases = [ { # ⚠️ The direction, and the only part of this mapping a reviewer cannot # check by looking. syslog's PRIORITY counts DOWN in urgency (0 = emerg, # 7 = debug) where the OpenTelemetry severity counts UP (1 = trace, # 21 = fatal). Wired across, debug renders as critical and emerg as # unspecified — and nothing reports an error, because every line still # arrives and every field is still populated. # # Every priority, not a sample. An inverted copy still maps each value # to SOMETHING, so a case that checks one end, or checks that a mapping # merely exists, passes on the exact defect it was written for. The # `"8"` arm is the control: it says this reader looks values up rather # than answering the same way whatever it is handed, so the eight # arms above it mean what they appear to mean. name = "journald PRIORITY maps to severity the right way up"; ok = severityOf "0" == "fatal" && severityOf "1" == "fatal" && severityOf "2" == "fatal" && severityOf "3" == "error" && severityOf "4" == "warn" && severityOf "5" == "info" && severityOf "6" == "info" && severityOf "7" == "debug" && severityOf "8" == null; } { # The second way this mapping can be present and useless, and the one # that looks already handled. Without `overwrite_text` the parser sets # the severity NUMBER from the table above and leaves the severity TEXT # as the raw value it matched — so `severity_text` reaches the log store # as the literal "6". That is a populated field: it passes any check # asking merely whether severity is set, and it is not a level anything # renders. VictoriaLogs has no ingest parameter naming a level field, so # the text is the whole interface. name = "the mapping writes a level name, not the raw priority digit"; ok = journaldSeverityParser.overwrite_text or false; } { # Two settings that are load-bearing in opposite directions. # # `parse_from` names where the value is. The journald input unmarshals # each `journalctl -o json` line into the entry BODY as a map, so the # field lives under `body.`; `attributes.PRIORITY` is the spelling that # looks equally plausible and finds nothing, leaving a parser that # reports a missing field per entry rather than failing to start. # # `on_error` decides what happens to a record it cannot read. It must # still REACH the store: `drop` would make the logs dashboard's # "lines with no severity" panel read zero by deleting its own evidence, # which is the empty-result-as-success failure this whole mapping exists # to end. name = "the mapping reads the field the receiver fills, and keeps what it cannot read"; ok = journaldSeverityParser.parse_from or null == "body.PRIORITY" && journaldSeverityParser.on_error or null == "send"; } ]; in runGroup "journald-severity" cases