feat: native systemd journal socket handler (structured per-field export) #1
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Wiring hslog into
xr-invoicedsurfaced a gap: our currentjournalformat (FormatJournal/FormatColorJournal) is the common<priority>-prefix-on-stdout trick, relying on systemd's own stdout-capture to parse the<N>prefix (persd-daemonconvention). It writes into whateverio.Writerthe caller supplies, consistent with every other hslog handler.xr-invoicedpreviously shipped (v0.0.15, "native systemd journal handler with per-field structured logging") a different, richer integration: it dials/run/systemd/journal/socket(unixgram) directly and exports each slog attr as its own uppercase journal field (e.g.MAILBOX=INBOX,TAGGED=true), on top of a human-readableMESSAGE=line. That makes attrs queryable viajournalctl MAILBOX=INBOX— something the stdout-prefix trick cannot do, since everything besides the priority ends up folded into one opaqueMESSAGEfield.Why this doesn't fit today's Format model
io.Writeryou gave me" (NewHandler(w, format, opts);detectKind/colorableetc. operate onw). A native journal-socket handler ignoresw— it dials a fixed system path and only falls back tow(e.g.os.Stderr) if the dial fails.FormatJournal's auto-detection (detectKind) triggers onJOURNAL_STREAM(is stdout specifically captured by the journal?). The native-socket approach is meaningful wheneverINVOCATION_IDis set (are we a systemd unit at all?), independent of where stdout goes.So this isn't a new
kindvalue inFormat— it needs its own constructor, e.g.:(dials the socket; on failure, falls back to a plain/text handler over
fallback).Reference implementation
We have a working, tested implementation to port from at
go.schlittermann.de/ius/xr-invoiced(as of writing, inhandler_journal.goonmaster, ~166 lines):net.Dial("unixgram", "/run/systemd/journal/socket"); falls back toslog.NewTextHandler(fallback, ...)if the dial fails.KEY=value\nfor single-line values; for values containing\n, the length-prefixed binary form (KEY\n+uint64LE(len)+ value +\n).PRIORITYfield mapped fromslog.Level(0-tier mapping: >=Error -> 3, >=Warn -> 4, >=Info -> 6, else 7).MESSAGEfield: human-readable text with attrs inlined askey=value(mirrors what a text handler would show).[A-Z0-9_]only, leading underscore avoided since it's reserved for trusted journal fields).WithAttrs/WithGroupsupported (attrs accumulate; groups currently flatten, matching hslog's own group handling elsewhere would need reconciling).Ask
Port/adapt this into hslog as a dedicated handler + constructor (not a
Formatvalue), with tests covering: the wire framing (both branches), field-name sanitization, the priority mapping, and the fallback-when-socket-unavailable path (probably via anet.Listen("unixgram", ...)test double rather than the real system path).Until this lands,
xr-invoicedis keeping its ownhandler_journal.gofor journal-mode logging (hybrid approach: hslog for plain/color-plain/jsonl, our own handler for journal) rather than regressing the structured-export feature.