Skip to content

Commit 8931a1e

Browse files
DavidCozensclaude
andcommitted
feat: spool records to a file store with a CRC-16 at rest
A SolidSyslogBlockStore over a FileBlockDevice over the library's FatFs port, replacing the Null store. The service task drains the ring into the store and sends from there, so a failed send costs a retry rather than the record. Flash +11,576 B (+3,964 on the previous stage) RAM +7,092 B (+1,212) Log stack +448 B (unchanged) Service +736 B (+64) The log stack does not move: storing happens on the service task, and a task that calls Log still knows nothing about what happens after it returns. The service task's own high-water rises 64 bytes to 788, which the 2 KiB seam the buffered stage sized absorbs, so no RAM moves with it. The static RAM is pool allocation, not buffers. The 8192-byte block size is file capacity — nothing holds a block in memory, so the store costs its handles rather than its capacity. CRC-16 is a checksum, not tamper-evidence: it catches a truncated write or bit-rot, and anyone who can edit a stored record can recompute it. What it buys is knowing a record came back the way it went in, which is the prerequisite for spooling at all. One file per block, syslog00.log upward on the volume the device already mounts, four blocks, oldest discarded when full — a device that cannot reach its collector should keep the newest evidence rather than stop logging. SolidSyslog::FatFs is a header-configured upstream, so it is named in SOLIDSYSLOG_PLATFORMS and linked. The discard policy enumerator is SOLIDSYSLOG_DISCARD_POLICY_OLDEST. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent f4724fc commit 8931a1e

6 files changed

Lines changed: 60 additions & 30 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ set(LWIP_CONTRIB_FREERTOS_DIR "${LWIP_DIR}/contrib/ports/freertos")
3939
# link target — only the header-configured packs below do.
4040
# https://docs.cososo.co.uk/solid-syslog/getting-started/#path-a--cmake-consumer
4141
# Pinned to a commit until there is a release tag to pin to.
42-
set(SOLIDSYSLOG_PLATFORMS "LwipRaw;Atomics;FreeRtos" CACHE STRING "" FORCE)
42+
set(SOLIDSYSLOG_PLATFORMS "LwipRaw;Atomics;FreeRtos;FatFs" CACHE STRING "" FORCE)
4343

4444
# Compile-time limits. Every tunable is #ifndef-guarded, so this file only needs
4545
# the ones this device wants changed.
@@ -171,7 +171,7 @@ target_include_directories(baseline PRIVATE
171171
# library, or context struct sizes diverge between consumer and library.
172172
target_compile_definitions(baseline PRIVATE MBEDTLS_USER_CONFIG_FILE=${MBEDTLS_USER_CONFIG_HEADER})
173173

174-
target_link_libraries(baseline PRIVATE mbedtls mbedx509 mbedcrypto SolidSyslog SolidSyslog::LwipRaw SolidSyslog::FreeRtos)
174+
target_link_libraries(baseline PRIVATE mbedtls mbedx509 mbedcrypto SolidSyslog SolidSyslog::LwipRaw SolidSyslog::FreeRtos SolidSyslog::FatFs)
175175

176176
target_link_options(baseline PRIVATE
177177
-mcpu=cortex-m3 -mthumb

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ It builds on a baseline that simulates the sort of device you might be adding th
1010
measures itself: see [docs/baseline.md](docs/baseline.md) for what the baseline is, how the
1111
figures are made, and how to run it.
1212

13-
## This stage — Time quality
13+
## This stage — File store
1414

15-
The record gains a `timeQuality` SD-ELEMENT and a `sysUpTime` PARAM on `meta`. This device's clock
16-
free-runs on the tick after one reading at boot, so it reports `isSynced="0"` and the collector
17-
knows how far to trust the timestamp.
15+
Records spool to a block store on the volume the device already mounts — four blocks, one file
16+
each — instead of being held only in memory. The service task drains the ring into the store and
17+
sends from there, so a failed send costs a retry rather than the record. The CRC-16 written with
18+
each record catches corruption at rest, not tampering.
1819

1920
<!-- STAGE-COST:START (generated by scripts/gen-cost-table.py — do not edit by hand) -->
2021

21-
**Cost above baseline: Flash +7,612 B, RAM +5,880 B.**
22+
**Cost above baseline: Flash +11,576 B, RAM +7,092 B.**
2223

2324
<!-- STAGE-COST:END -->
2425

@@ -42,6 +43,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage.
4243
| Buffered | logging that returns immediately, with the send moved off the logging task | +6,788 | +5,676 |
4344
| TCP | records the network retransmits instead of dropping, and a send that fails when the collector is gone | +7,320 | +5,856 |
4445
| Time quality | a timestamp the collector knows how far to trust, and an uptime that tells a reboot from a counter wrap | +7,612 | +5,880 |
46+
| File store | records that survive a failed send, spooled to disk with a checksum at rest | +11,576 | +7,092 |
4547

4648
*Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.*
4749

app/syslog/Syslog.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@
99

1010
#include "Syslog.h"
1111

12+
#include "SolidSyslogBlockStore.h"
1213
#include "SolidSyslogCircularBuffer.h"
1314
#include "SolidSyslogConfig.h"
15+
#include "SolidSyslogCrc16Policy.h"
1416
#include "SolidSyslogEndpoint.h"
1517
#include "SolidSyslogEndpointHost.h"
18+
#include "SolidSyslogFatFsFile.h"
19+
#include "SolidSyslogFileBlockDevice.h"
1620
#include "SolidSyslogFreeRtosMutex.h"
1721
#include "SolidSyslogFreeRtosSysUpTime.h"
1822
#include "SolidSyslogLwipRawAddress.h"
1923
#include "SolidSyslogLwipRawMarshal.h"
2024
#include "SolidSyslogLwipRawResolver.h"
2125
#include "SolidSyslogLwipRawTcpStream.h"
2226
#include "SolidSyslogMetaSd.h"
23-
#include "SolidSyslogNullStore.h"
2427
#include "SolidSyslogStdAtomicCounter.h"
2528
#include "SolidSyslogStreamSender.h"
2629
#include "SolidSyslogTimeQuality.h"
@@ -46,6 +49,10 @@
4649
* backlog the store is there to hold. */
4750
#define SYSLOG_BUFFER_RECORDS 8U
4851

52+
/* One "<prefix>NN.log" per block, on the volume the device already mounts. */
53+
#define SYSLOG_STORE_PREFIX "syslog"
54+
#define SYSLOG_STORE_BLOCKS 4U
55+
4956
static struct SolidSyslog* s_logger = NULL;
5057
static uint8_t s_ring[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(SYSLOG_BUFFER_RECORDS)];
5158

@@ -115,12 +122,19 @@ void Syslog_Start(void)
115122
s_sd[0] = SolidSyslogMetaSd_Create(&metaConfig);
116123
s_sd[1] = SolidSyslogTimeQualitySd_Create(SyslogTimeQuality);
117124

125+
/* Oldest discarded when the ceiling is reached: a device that cannot reach its
126+
* collector should keep the newest evidence, not stop logging. */
127+
struct SolidSyslogBlockStoreConfig storeConfig = {
128+
.BlockDevice = SolidSyslogFileBlockDevice_Create(SolidSyslogFatFsFile_Create(), SYSLOG_STORE_PREFIX, 0U),
129+
.MaxBlocks = SYSLOG_STORE_BLOCKS,
130+
.DiscardPolicy = SOLIDSYSLOG_DISCARD_POLICY_OLDEST,
131+
.SecurityPolicy = SolidSyslogCrc16Policy_Create(),
132+
};
133+
118134
struct SolidSyslogConfig config = {
119135
.Buffer = SolidSyslogCircularBuffer_Create(SolidSyslogFreeRtosMutex_Create(), s_ring, sizeof(s_ring)),
120136
.Sender = sender,
121-
/* No store-and-forward here. The Null object rather than NULL is how
122-
* that is said out loud — NULL is reported as a fault. */
123-
.Store = SolidSyslogNullStore_Get(),
137+
.Store = SolidSyslogBlockStore_Create(&storeConfig),
124138
/* PROCID stays unset — a bare-metal image has no process. */
125139
.Clock = SyslogFields_Clock,
126140
.GetHostname = SyslogFields_Hostname,

measurements/file-store.csv

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# file-store figures (bytes) — captured by scripts/run.sh (CAPTURE=1).
2+
# The device reads measurements/Baseline.csv as its frozen baseline and reports current-minus-Baseline.
3+
flash_text,361252
4+
flash_data,632
5+
static_bss,117652
6+
heap_used,4440
7+
mbedtls_peak,21276
8+
mbedtls_free,11492
9+
lwip_mem_free,7576
10+
lwip_pbufs_free,14
11+
stack_log,568
12+
stack_service,788
13+
stack_harness,2848

measurements/stages.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ message-cap Message cap a bounded record size, so a long message truncates inste
2020
buffered Buffered logging that returns immediately, with the send moved off the logging task
2121
tcp TCP records the network retransmits instead of dropping, and a send that fails when the collector is gone
2222
time-quality Time quality a timestamp the collector knows how far to trust, and an uptime that tells a reboot from a counter wrap
23+
file-store File store records that survive a failed send, spooled to disk with a checksum at rest

run-report.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# solid-syslog-example — run (time-quality)
1+
# solid-syslog-example — run (file-store)
22

33
## Device (self-measured)
44

@@ -10,16 +10,16 @@
1010
[device] first record logged: yes
1111
[report] --- SolidSyslog cost above baseline (simulated existing application) ---
1212
[report] key,current,baseline,used_above_baseline
13-
[report] flash_text,357424,349992,7432
14-
[report] flash_data,496,316,180
15-
[report] static_bss,116576,110876,5700
13+
[report] flash_text,361252,349992,11260
14+
[report] flash_data,632,316,316
15+
[report] static_bss,117652,110876,6776
1616
[report] heap_used,4440,4440,0
17-
[report] mbedtls_peak,21352,21332,20
18-
[report] mbedtls_free,11416,11436,-20
17+
[report] mbedtls_peak,21276,21332,-56
18+
[report] mbedtls_free,11492,11436,56
1919
[report] lwip_mem_free,7576,7576,0
20-
[report] lwip_pbufs_free,13,14,-1
20+
[report] lwip_pbufs_free,14,14,0
2121
[report] stack_log,568,120,448
22-
[report] stack_service,724,52,672
22+
[report] stack_service,788,52,736
2323
[report] stack_harness,2848,2840,8
2424
[report] --- end ---
2525
[device] ready
@@ -29,7 +29,7 @@
2929

3030
```text
3131
text data bss dec hex filename
32-
357416 504 116576 474496 73d80 /w/build/baseline-cross/baseline.elf
32+
361244 640 117652 479536 75130 /w/build/baseline-cross/baseline.elf
3333
```
3434

3535
## Listeners (proved before the device ran)
@@ -47,23 +47,23 @@
4747
## Collector (syslog-ng) received
4848

4949
```text
50-
wire <134>1 2026-07-29T07:42:02.620000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="362"][timeQuality tzKnown="1" isSynced="0"] device started
51-
parsed PRIORITY=134 TIMESTAMP=2026-07-29T07:42:02+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="362"][timeQuality tzKnown="1" isSynced="0"] MSG=device started
50+
wire <134>1 2026-07-29T08:06:15.410000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="241"][timeQuality tzKnown="1" isSynced="0"] device started
51+
parsed PRIORITY=134 TIMESTAMP=2026-07-29T08:06:15+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="241"][timeQuality tzKnown="1" isSynced="0"] MSG=device started
5252
```
5353

54-
## Self-check (vs measurements/time-quality.csv)
54+
## Self-check (vs measurements/file-store.csv)
5555

5656
```text
57-
OK flash_text: 357424 (expected 357424, Δ0)
58-
OK flash_data: 496 (expected 496, Δ0)
59-
OK static_bss: 116576 (expected 116576, Δ0)
57+
OK flash_text: 361252 (expected 361252, Δ0)
58+
OK flash_data: 632 (expected 632, Δ0)
59+
OK static_bss: 117652 (expected 117652, Δ0)
6060
OK heap_used: 4440 (expected 4440, Δ0)
61-
OK mbedtls_peak: 21352 (expected 21352, Δ0)
62-
OK mbedtls_free: 11416 (expected 11416, Δ0)
61+
OK mbedtls_peak: 21276 (expected 21276, Δ0)
62+
OK mbedtls_free: 11492 (expected 11492, Δ0)
6363
OK lwip_mem_free: 7576 (expected 7576, Δ0)
64-
OK lwip_pbufs_free: 13 (expected 13, Δ0)
64+
OK lwip_pbufs_free: 14 (expected 14, Δ0)
6565
OK stack_log: 568 (expected 568, Δ0)
66-
OK stack_service: 724 (expected 724, Δ0)
66+
OK stack_service: 788 (expected 788, Δ0)
6767
OK stack_harness: 2848 (expected 2848, Δ0)
6868
```
6969

0 commit comments

Comments
 (0)