Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*`

| Header | Audience | Provides |
|---|---|---|
| `SolidSyslog.h` | Application code that logs events | `SolidSyslogMessage`, `SolidSyslog_Log(handle, message)`, `SolidSyslog_Service(handle)`. `struct SolidSyslog` opaque. |
| `SolidSyslog.h` | Application code that logs events | `SolidSyslogMessage`, `SolidSyslog_Log(handle, message)`, `SolidSyslog_LogWithSd(handle, message, sd, sdCount)` (attach caller-built per-message SD-ELEMENTs after the per-instance SDs; `Log` is exactly this with `NULL, 0`), `SolidSyslog_Service(handle)`. `struct SolidSyslog` opaque. |
| `SolidSyslogConfig.h` | System setup code | `SolidSyslogConfig`, `SolidSyslog_Create(config) → handle`, `SolidSyslog_Destroy(handle)`. Instance struct lives in a library-internal static pool (E11; tunable `SOLIDSYSLOG_POOL_SIZE`, default `1U`). Pool-exhaustion fallback is a TU-private `NullInstance` wired to `SolidSyslogNull{Buffer,Sender,Store}_Get()` siblings — `_Log`/`_Service` against it dispatch through Null vtables and silently drop. |
| `SolidSyslogErrors.h` | Any code installing an error handler that wants to react to SolidSyslog-singleton events (pointer-identity match on `SolidSyslogErrorSource`, switch on `enum SolidSyslogErrors`) | `enum SolidSyslogErrors` (`SOLIDSYSLOG_ERROR_*` codes + `SOLIDSYSLOG_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource SolidSyslogErrorSource`. Integrators ignore if not handling errors per source. |
| `SolidSyslogError.h` | Any code installing a handler to react to library-internal errors (NULL guards, send failures); also the library-internal call site for emitting them | `struct SolidSyslogErrorEvent` (`Severity`, `Source`, `uint16_t Category`, `int32_t Detail`), `SolidSyslogErrorHandler` typedef (`void(void* context, const struct SolidSyslogErrorEvent* event)`), `SolidSyslog_SetErrorHandler(handler, context)`, `SolidSyslog_Error(severity, source, category, detail)`. The handler reads three orthogonal axes off the event: `Source` (extensible identity, pointer-identity match), `Category` (portable reaction axis — see `SolidSyslogErrorCategory.h`), `Detail` (per-class `enum SolidSyslog<Class>Errors` value today, native `errno`/`X509_V_ERR_*` later). Default handler is a silent no-op; setting `handler = NULL` restores the default. Single global slot — intended for setup-time configuration, not synchronised with concurrent `Error` calls. |
Expand Down
14 changes: 14 additions & 0 deletions Core/Interface/SolidSyslog.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
#include "ExternC.h"
#include "SolidSyslogPrival.h"

#include <stddef.h>

EXTERN_C_BEGIN

struct SolidSyslog;
struct SolidSyslogStructuredData;

struct SolidSyslogMessage
{
Expand All @@ -17,6 +20,17 @@ EXTERN_C_BEGIN
};

void SolidSyslog_Log(struct SolidSyslog * handle, const struct SolidSyslogMessage* message);

/* As SolidSyslog_Log, but attaches sd[0..sdCount) as caller-built SD-ELEMENTs to this one
message, emitted after the per-instance SDs registered at Create. The SD objects need only
live for the duration of the call. SolidSyslog_Log is exactly this with sd = NULL, count = 0. */
void SolidSyslog_LogWithSd(
struct SolidSyslog * handle,
const struct SolidSyslogMessage* message,
struct SolidSyslogStructuredData** sd,
size_t sdCount
);

void SolidSyslog_Service(struct SolidSyslog * handle);

EXTERN_C_END
Expand Down
1 change: 1 addition & 0 deletions Core/Interface/SolidSyslogErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ EXTERN_C_BEGIN
SOLIDSYSLOG_ERROR_CREATE_INCONSISTENT_SD,
SOLIDSYSLOG_ERROR_LOG_NULL_MESSAGE,
SOLIDSYSLOG_ERROR_LOG_NULL_HANDLE,
SOLIDSYSLOG_ERROR_LOG_INCONSISTENT_SD,
SOLIDSYSLOG_ERROR_SERVICE_NULL_HANDLE,
SOLIDSYSLOG_ERROR_POOL_EXHAUSTED,
SOLIDSYSLOG_ERROR_UNKNOWN_DESTROY,
Expand Down
43 changes: 42 additions & 1 deletion Core/Source/SolidSyslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ struct SolidSyslogStructuredData;

static inline void SolidSyslog_DrainBufferIntoStore(struct SolidSyslog* self);
static inline void SolidSyslog_SendOneFromStore(struct SolidSyslog* self);
static void SolidSyslog_DoLog(
struct SolidSyslog* handle,
const struct SolidSyslogMessage* message,
struct SolidSyslogStructuredData** sd,
size_t sdCount
);
static void SolidSyslog_InstallAppName(
struct SolidSyslog* self,
SolidSyslogHeaderFieldFunction configured,
Expand Down Expand Up @@ -282,6 +288,26 @@ static inline void SolidSyslog_SendOneFromStore(struct SolidSyslog* self)
}

void SolidSyslog_Log(struct SolidSyslog* handle, const struct SolidSyslogMessage* message)
{
SolidSyslog_DoLog(handle, message, NULL, 0);
}

void SolidSyslog_LogWithSd(
struct SolidSyslog* handle,
const struct SolidSyslogMessage* message,
struct SolidSyslogStructuredData** sd,
size_t sdCount
)
{
SolidSyslog_DoLog(handle, message, sd, sdCount);
}

static void SolidSyslog_DoLog(
struct SolidSyslog* handle,
const struct SolidSyslogMessage* message,
struct SolidSyslogStructuredData** sd,
size_t sdCount
)
{
if (handle == NULL)
{
Expand All @@ -301,10 +327,25 @@ void SolidSyslog_Log(struct SolidSyslog* handle, const struct SolidSyslogMessage
}
else
{
/* Inconsistent pairing — the formatter would dereference sd[i] for
i < sdCount against a NULL array. Report and drop to no per-message
SD so the message still logs (degrade safely), mirroring the
Create-time guard in SolidSyslog_InstallStructuredData. */
size_t safeSdCount = sdCount;
if ((sd == NULL) && (sdCount > 0U))
{
SolidSyslog_Report(
SOLIDSYSLOG_BAD_ARGUMENT_SEVERITY,
SOLIDSYSLOG_CAT_BAD_ARGUMENT,
SOLIDSYSLOG_ERROR_LOG_INCONSISTENT_SD
);
safeSdCount = 0U;
}

SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(SOLIDSYSLOG_MAX_MESSAGE_SIZE)];
struct SolidSyslogFormatter* f = SolidSyslogFormatter_Create(storage, SOLIDSYSLOG_MAX_MESSAGE_SIZE);

SolidSyslogMessageFormatter_Format(f, message, &handle->Format);
SolidSyslogMessageFormatter_Format(f, message, &handle->Format, sd, safeSdCount);
SolidSyslogBuffer_Write(
handle->Buffer,
SolidSyslogFormatter_AsFormattedBuffer(f),
Expand Down
44 changes: 36 additions & 8 deletions Core/Source/SolidSyslogMessageFormatter.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ static inline void MessageFormatter_FormatMsgId(struct SolidSyslogFormatter* f,
static inline bool MessageFormatter_StringIsValid(const char* value);
static inline void MessageFormatter_FormatStructuredData(
struct SolidSyslogFormatter* f,
struct SolidSyslogStructuredData** baseSd,
size_t baseSdCount,
struct SolidSyslogStructuredData** messageSd,
size_t messageSdCount
);
static inline void MessageFormatter_FormatSdElements(
struct SolidSyslogSdElement* element,
struct SolidSyslogStructuredData** sd,
size_t sdCount
);
Expand All @@ -51,7 +58,9 @@ static inline const char* MessageFormatter_SkipLeadingBom(const char* msg);
void SolidSyslogMessageFormatter_Format(
struct SolidSyslogFormatter* f,
const struct SolidSyslogMessage* message,
const struct SolidSyslogMessageFormatterContext* context
const struct SolidSyslogMessageFormatterContext* context,
struct SolidSyslogStructuredData** messageSd,
size_t messageSdCount
)
{
MessageFormatter_FormatPrival(f, MessageFormatter_MakePrival(message));
Expand Down Expand Up @@ -82,7 +91,7 @@ void SolidSyslogMessageFormatter_Format(
SolidSyslogFormatter_AsciiCharacter(f, ' ');
MessageFormatter_FormatMsgId(f, message->MessageId);
SolidSyslogFormatter_AsciiCharacter(f, ' ');
MessageFormatter_FormatStructuredData(f, context->Sd, context->SdCount);
MessageFormatter_FormatStructuredData(f, context->Sd, context->SdCount, messageSd, messageSdCount);
MessageFormatter_FormatMsg(f, message->Msg);
}

Expand Down Expand Up @@ -180,25 +189,44 @@ static inline bool MessageFormatter_StringIsValid(const char* value)

static inline void MessageFormatter_FormatStructuredData(
struct SolidSyslogFormatter* f,
struct SolidSyslogStructuredData** sd,
size_t sdCount
struct SolidSyslogStructuredData** baseSd,
size_t baseSdCount,
struct SolidSyslogStructuredData** messageSd,
size_t messageSdCount
)
{
size_t lengthBefore = SolidSyslogFormatter_Length(f);
struct SolidSyslogSdElement element;

SolidSyslogSdElement_FromFormatter(&element, f);
for (size_t i = 0; i < sdCount; i++)
{
SolidSyslogStructuredData_Format(sd[i], &element);
}
MessageFormatter_FormatSdElements(&element, baseSd, baseSdCount);
MessageFormatter_FormatSdElements(&element, messageSd, messageSdCount);

if (SolidSyslogFormatter_Length(f) == lengthBefore)
{
SolidSyslogFormatter_NilValue(f);
}
}

static inline void MessageFormatter_FormatSdElements(
struct SolidSyslogSdElement* element,
struct SolidSyslogStructuredData** sd,
size_t sdCount
)
{
for (size_t i = 0; i < sdCount; i++)
{
/* Skip NULL entries rather than dereference them. Per-instance slots are
expected to use SolidSyslogNullSd, but a per-message array is supplied
at the call site where a conditionally-absent SD is naturally NULL —
the library must not crash on caller input. */
if (sd[i] != NULL)
{
SolidSyslogStructuredData_Format(sd[i], element);
}
}
}

static inline void MessageFormatter_FormatMsg(struct SolidSyslogFormatter* f, const char* msg)
{
/* Guard msg before SkipLeadingBom dereferences it, then guard the
Expand Down
8 changes: 6 additions & 2 deletions Core/Source/SolidSyslogMessageFormatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ EXTERN_C_BEGIN
/* Emits a full RFC 5424 SYSLOG-MSG into f:
* <PRIVAL>1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID SD [SP BOM MSG].
* Captures the timestamp via context->Clock and delegates its formatting
* to SolidSyslogTimestampFormatter_Format. */
* to SolidSyslogTimestampFormatter_Format. The SD area emits the per-instance
* SDs (context->Sd) followed by the per-message SDs (messageSd[0..messageSdCount)),
* NILVALUE only when neither produces an element. */
void SolidSyslogMessageFormatter_Format(
struct SolidSyslogFormatter * formatter,
const struct SolidSyslogMessage* message,
const struct SolidSyslogMessageFormatterContext* context
const struct SolidSyslogMessageFormatterContext* context,
struct SolidSyslogStructuredData** messageSd,
size_t messageSdCount
);

EXTERN_C_END
Expand Down
55 changes: 55 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14657,3 +14657,58 @@ MISRA rule — different category, doesn't set precedent.

### Open questions
- None outstanding.

## 2026-06-06 — S14.09: per-message structured data (SolidSyslog_LogWithSd)

This is the per-message-attachment wave of E14 — the headline "custom SD on
individual Log calls" — pulled ahead of the integrator guide (the old S14.09
guide was renumbered to **S14.10 #549**; this story is the new **#560**). Confirmed
the epic still scopes it ("attaching caller-built SD-ELEMENTs … via
SolidSyslogMessage"; the per-message Open questions); it was the never-decomposed
"Later wave".

### Decisions
- **`SolidSyslog_LogWithSd(handle, message, sd, sdCount)`** — David's shape: per-message
SD is a *call argument* (array of `SolidSyslogStructuredData*`, same type as
`SolidSyslogConfig.Sd[]`), not a field on `SolidSyslogMessage` (which stays a pure
data struct). `SolidSyslog_Log` is exactly `LogWithSd(h, m, NULL, 0)`.
- **Ordering:** per-instance (config) SDs first, then per-message — both into the one
shared `SolidSyslogSdElement` the dispatch already builds; NILVALUE only when neither
writes. `MessageFormatter_Format` gained the per-message array; `FormatSdElements`
helper DRYs the base-then-message loop.
- **NULL array + nonzero count** → reported (new `SOLIDSYSLOG_ERROR_LOG_INCONSISTENT_SD`,
BAD_ARGUMENT) and treated as 0 so the message still logs — mirrors the Create-time
`InstallStructuredData` guard.
- **NULL element *inside* the array** (David's catch on review) → skipped, message still
emits, no crash. The dispatch `SolidSyslogStructuredData_Format` never null-guarded, and
nothing tested a NULL element in *either* array (create-time relied only on the
"use SolidSyslogNullSd, not NULL" convention). Guard added in the shared
`MessageFormatter_FormatSdElements` loop, so both the per-instance and per-message arrays
now tolerate a conditionally-absent (NULL) SD — justified because the per-message array is
caller input at the call site. Tests added for both arrays + the still-emits-NILVALUE case.
- **Reentrancy clarification (David pushed back, rightly):** the vtable `Format(self,
element)` has no per-call argument, so a custom SD reads call-specific data via `self`.
Formatting is synchronous, so the SD objects need only live across the call. The only
hazard is sharing one mutable SD object across concurrent Log calls — avoidable with a
per-call-site instance. Not an API flaw; a one-line note for the S14.10 guide.
- **MISRA 8.7 surprise + the `_Emit`→`_DoLog` naming:** making `Log` call `LogWithSd`
directly gave `LogWithSd` exactly one internal reference → cppcheck 8.7 (public API,
external callers invisible to the analysis set; a BDD/test caller wouldn't help — those
aren't in the cppcheck file set). Rather than add the project's first active 8.7
suppression (D.014 retired the last), both public functions delegate to a static
`SolidSyslog_DoLog`, so each is zero-internal-ref (like every other public function
here). Named DoLog not Emit — `_Emit` is taken in OriginSd (write one SD piece) and
heavy in prose; `_Do<Operation>` is the established "real impl behind the public op"
idiom (`LwipRawTcpStream_Do*`).
- Tests: emit, base-before-per-message ordering, empty-per-message keeps the base set,
per-message values escaped (injection-proof — asserted on the raw frame because the
SDATA test-helper naively stops at any `]`), NULL-with-count guard. 1479 green;
BddTargetTests 66/66; clang-format clean; cppcheck-MISRA exit 0 (8.7 cleared by the
static helper; 11.8 line drift renumbered SolidSyslog.c +6 / MessageFormatter.c +9).

### Deferred
- Worked custom-SD example, oracle round-trip BDD, and the integrator-guide prose
(incl. the don't-share-a-mutable-SD-object note) → **S14.10 (#549)**, which closes E14.

### Open questions
- None outstanding.
2 changes: 1 addition & 1 deletion Tests/SolidSyslogMessageFormatterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ TEST_GROUP(SolidSyslogMessageFormatter)

void format()
{
SolidSyslogMessageFormatter_Format(formatter, &message, &context);
SolidSyslogMessageFormatter_Format(formatter, &message, &context, nullptr, 0);
}

// Clears the output buffer between two format() calls without rebuilding
Expand Down
Loading
Loading