You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Per-message structured data — caller-built SD-ELEMENTs attached to individual
Log calls — plus the safe authoring API that makes caller-supplied SD content
injection-proof. Custom/enterprise SD-IDs (name@enterprise-number format).
Deferred until E07 (Standard Structured Data) was complete and the SD
serialisation machinery was proven.
Agreed design (2026-06-05)
Authoring is moved off the raw SolidSyslogFormatter and onto two opaque,
stack-transient writer types (no pool — like the formatter, deviation D.002):
SolidSyslogSdElement — handed to the SD vtable Format(base, element).
Owns grammar + names: _Begin(name, enterpriseNumber) (validates SD-NAME;
IANA name when no enterprise, else name@num), _Param(name) → returns a SolidSyslogSdValue*, _End.
SolidSyslogSdValue — the per-param value sink. Escaped UTF-8 + convenience
primitives (_String, _Uint32 now; _Timestamp later — the menu is additive).
Streaming, bounded by the message buffer (cannot overflow). A third-party value
producer only ever sees the SdValue — it physically cannot open a param or
break the framing.
The SD vtable changes Format(base, formatter) → Format(base, element), which
removes the raw-formatter footgun; SolidSyslogFormatter then leaves the
public interface entirely. Header-field callbacks (hostname/appName/processId)
move to a small US-ASCII writer (SolidSyslogToken), retiring SolidSyslogStringFunction. The three existing SDs are refactored onto the new
API (dogfood), which also eliminates the OriginSd preformat scratch blob (see
Constraints).
Key decisions: two types (not one with a state machine); dedicated header
writer (not const char* return — that reintroduces materialisation/ownership
cost); value-primitive menu additive forever (only the types and callback
signatures freeze at beta); no per-value cap (buffer-bounded only); param presence gated by the element author (NULL callback → no _Param), emptiness
controlled by the producer, producer cannot signal "skip me"; the value writer
carries ≤4-byte UTF-8 continuation state so a multi-byte codepoint streamed
across two _String calls reassembles correctly (Option B) — single-shot _EscapedString never had this; streaming introduces it.
This supersedes the S12.32 (#533) formatter escape-toggle proposal: the
escaping fix falls out of routing values through the escaped SdValue and lands
across S14.04–S14.05.
Scope
API for attaching zero or more SD-ELEMENTs per Log call via SolidSyslogMessage
Combining per-instance SD (from E07) with per-message SD in the correct order
Enterprise number registration guidance
SD-ID and PARAM-NAME syntax validation per RFC 5424 §6.3.2 (1–32
PRINTUSASCII excluding = ] " and space). Caller-supplied names are
validated at SD Create time; invalid names cause the custom SD's Create to return NULL, which in turn causes SolidSyslog_Create to
fail. Fail-loud-at-boundary only — no format-time re-validation.
Oracle round-trip BDD for PARAM-VALUE escape rules — parameterised
example fixture driving software/swVersion (and custom-SD values)
containing each of ", \, ] through syslog-ng and asserting the
decoded value matches.
Decomposition
Authoring-API foundation (this wave) — each story its own red→green→refactor:
S14.01 — SolidSyslogSdValue: escaped streaming value sink with Option-B
continuation state. Tested standalone.
S14.03 — Migrate TimeQualitySd onto the writer. Walking skeleton: vtable
unchanged (SD wraps the handed formatter via _FromFormatter); no string
callback so the cleanest first cut; existing tests stand (output unchanged).
S14.06 — Flip the SD vtable to Format(base, element); move the wrap into
MessageFormatter. Footgun closed — authors can no longer reach a raw
formatter. (Small/mechanical; may merge into S14.05 tail or S14.08 head.)
S14.07 — Header fields off the formatter: SolidSyslogToken writer +
hostname/appName/processId callbacks + Posix/Windows helpers. SolidSyslogStringFunction retired.
S14.08 — Internalise the formatter: header → Core/Source/, drop public
forward-decls, update the CLAUDE.md public-header table; author docs/structured-data.md.
S14.09 — Custom-SD integrator guide + worked example / BDD (JSON-in-value).
Later wave (per-message attachment): attaching caller-built SD-ELEMENTs to
individual Log calls via SolidSyslogMessage; max-elements / allocation strategy
on constrained targets. To be decomposed once the foundation lands.
Deferred from
S07.04 (S07.04: SD escaping and validation #68) — SD-NAME syntax validation. The infrastructure was
originally scoped into S07.04 but deferred here because all three
standard SDs (meta/timeQuality/origin) have compile-time-constant
names, so validation protects nothing until callers supply names.
S07.04 (S07.04: SD escaping and validation #68) — oracle round-trip BDD for PARAM-VALUE escape rules.
Requires parameterised example fixture; natural fit here since custom
SDs introduce caller-supplied values.
Constraints from related work
OriginSd preformat tail audit (from S07.08: OriginSd.Format is non-reentrant — shared scratch corrupts concurrent Log output #346). The current SolidSyslogOriginSd
pre-formats a [origin software="…" swVersion="…" enterpriseId="…" prefix into a
static blob at Create and reads it on every Format. Concurrent-Log reentrancy is
preserved today only because the blob's last byte is always ASCII (", or n from [origin in the all-NULL case), which keeps TrimTruncatedMultiByteTail inside SolidSyslogFormatter_AsFormattedBuffer from entering its write loop. If the SD-helper
restructure changes what trails any shared SD scratch buffer, either audit that the
final byte stays ASCII or eliminate the shared scratch entirely by emitting straight
into the caller's formatter (the shape MetaSd/TimeQualitySd already use). Eliminating
the shared scratch is the cleaner outcome and would also retire the latent formattedLength concern noted against E12. → S14.05 takes the eliminate-the-scratch
path.
Open questions
C API design for caller-built SD — builder, pre-formatted string, or struct? Answered: two-type writer (SolidSyslogSdElement + SolidSyslogSdValue),
streaming. See Agreed design.
Maximum number of SD-ELEMENTs per message — fixed limit or dynamic? (per-message wave)
Memory allocation strategy for per-message SD on constrained targets (per-message wave)
Research context
Industrial use cases for per-message custom SD include access control events,
process safety alarms, configuration change tracking, and device diagnostics.
See E07 for full standards research.
Per-message structured data — caller-built SD-ELEMENTs attached to individual
Log calls — plus the safe authoring API that makes caller-supplied SD content
injection-proof. Custom/enterprise SD-IDs (
name@enterprise-numberformat).Deferred until E07 (Standard Structured Data) was complete and the SD
serialisation machinery was proven.
Agreed design (2026-06-05)
Authoring is moved off the raw
SolidSyslogFormatterand onto two opaque,stack-transient writer types (no pool — like the formatter, deviation D.002):
SolidSyslogSdElement— handed to the SD vtableFormat(base, element).Owns grammar + names:
_Begin(name, enterpriseNumber)(validates SD-NAME;IANA name when no enterprise, else
name@num),_Param(name)→ returns aSolidSyslogSdValue*,_End.SolidSyslogSdValue— the per-param value sink. Escaped UTF-8 + convenienceprimitives (
_String,_Uint32now;_Timestamplater — the menu is additive).Streaming, bounded by the message buffer (cannot overflow). A third-party value
producer only ever sees the
SdValue— it physically cannot open a param orbreak the framing.
The SD vtable changes
Format(base, formatter)→Format(base, element), whichremoves the raw-formatter footgun;
SolidSyslogFormatterthen leaves thepublic interface entirely. Header-field callbacks (hostname/appName/processId)
move to a small US-ASCII writer (
SolidSyslogToken), retiringSolidSyslogStringFunction. The three existing SDs are refactored onto the newAPI (dogfood), which also eliminates the OriginSd preformat scratch blob (see
Constraints).
Key decisions: two types (not one with a state machine); dedicated header
writer (not
const char*return — that reintroduces materialisation/ownershipcost); value-primitive menu additive forever (only the types and callback
signatures freeze at beta); no per-value cap (buffer-bounded only); param
presence gated by the element author (NULL callback → no
_Param), emptinesscontrolled by the producer, producer cannot signal "skip me"; the value writer
carries ≤4-byte UTF-8 continuation state so a multi-byte codepoint streamed
across two
_Stringcalls reassembles correctly (Option B) — single-shot_EscapedStringnever had this; streaming introduces it.This supersedes the S12.32 (#533) formatter escape-toggle proposal: the
escaping fix falls out of routing values through the escaped
SdValueand landsacross S14.04–S14.05.
Scope
name@enterprise-number(RFC 5424 §7.1.5)PRINTUSASCII excluding
= ] "and space). Caller-supplied names arevalidated at SD
Createtime; invalid names cause the custom SD'sCreateto return NULL, which in turn causesSolidSyslog_Createtofail. Fail-loud-at-boundary only — no format-time re-validation.
example fixture driving software/swVersion (and custom-SD values)
containing each of
",\,]through syslog-ng and asserting thedecoded value matches.
Decomposition
Authoring-API foundation (this wave) — each story its own red→green→refactor:
SolidSyslogSdValue: escaped streaming value sink with Option-Bcontinuation state. Tested standalone.
SolidSyslogSdElement: framing + SD-NAME/enterprise validation +internal
_FromFormatterconstructor. Tested standalone.unchanged (SD wraps the handed formatter via
_FromFormatter); no stringcallback so the cleanest first cut; existing tests stand (output unchanged).
GetLanguage→SolidSyslogSdValue*+void* context). Closes thelanguageinjection vector (part of S12.32: Escape integrator-supplied SD param values (scoped formatter escape toggle) #533).GetIpAt→SolidSyslogSdValue*+void* context). Closes theipvector → S12.32: Escape integrator-supplied SD param values (scoped formatter escape toggle) #533 fully done; eliminates thepreformat scratch blob.
Format(base, element); move the wrap intoMessageFormatter. Footgun closed — authors can no longer reach a raw
formatter. (Small/mechanical; may merge into S14.05 tail or S14.08 head.)
SolidSyslogTokenwriter +hostname/appName/processId callbacks + Posix/Windows helpers.
SolidSyslogStringFunctionretired.Core/Source/, drop publicforward-decls, update the CLAUDE.md public-header table; author
docs/structured-data.md.Later wave (per-message attachment): attaching caller-built SD-ELEMENTs to
individual Log calls via
SolidSyslogMessage; max-elements / allocation strategyon constrained targets. To be decomposed once the foundation lands.
Deferred from
originally scoped into S07.04 but deferred here because all three
standard SDs (meta/timeQuality/origin) have compile-time-constant
names, so validation protects nothing until callers supply names.
Requires parameterised example fixture; natural fit here since custom
SDs introduce caller-supplied values.
Constraints from related work
SolidSyslogOriginSdpre-formats a
[origin software="…" swVersion="…" enterpriseId="…"prefix into astatic blob at
Createand reads it on everyFormat. Concurrent-Log reentrancy ispreserved today only because the blob's last byte is always ASCII (
", ornfrom[originin the all-NULL case), which keepsTrimTruncatedMultiByteTailinsideSolidSyslogFormatter_AsFormattedBufferfrom entering its write loop. If the SD-helperrestructure changes what trails any shared SD scratch buffer, either audit that the
final byte stays ASCII or eliminate the shared scratch entirely by emitting straight
into the caller's formatter (the shape MetaSd/TimeQualitySd already use). Eliminating
the shared scratch is the cleaner outcome and would also retire the latent
formattedLengthconcern noted against E12. → S14.05 takes the eliminate-the-scratchpath.
Open questions
C API design for caller-built SD — builder, pre-formatted string, or struct?Answered: two-type writer (
SolidSyslogSdElement+SolidSyslogSdValue),streaming. See Agreed design.
Research context
Industrial use cases for per-message custom SD include access control events,
process safety alarms, configuration change tracking, and device diagnostics.
See E07 for full standards research.