Implementations

Logb is a format. The specification is the normative document, and an implementation is a claim to conform to it — where the two disagree, the specification wins and the implementation is the bug.

One implementation exists today. If you write another, in any language, it belongs on this page: open an issue or a pull request on the repository.

Writing one

A conforming reader is roughly a thousand lines plus a decompressor. There is no XML, no external schema registry, and no runtime dependency that must still exist in 2050. The things worth knowing before you start are on the conformance page — in particular that the bit-numbering vectors in §6.2, not the prose around them, are what your extractor has to reproduce.

A writer is smaller still, and needs only a byte sink: nothing points forward, so it never seeks back to patch a field it already emitted. That is what makes the format practical on an embedded logger.

Reference implementation — Go

`github.com/rveen/logb` — MIT, covering the specification as well as the code.

go get github.com/rveen/logb

A reader that needs only an `io.Reader`, a writer that needs only an `io.Writer`, bit extraction and the full conversion set, plus `Resync` — design rule 3 as an API: hand it a byte slice starting anywhere and it scans forward to the next sync frame and returns a reader positioned there.

API reference is on pkg.go.dev.

Command-line tools

go run ./cmd/logbgen -o /tmp/x.logb       # write the example file
go run ./cmd/logbdump /tmp/x.logb         # frames, schemas, records
go run ./cmd/logbdump -resync /tmp/x.logb # resynchronise, from the CLI
in half and read what is left:
head -c 7000 /tmp/x.logb > /tmp/cut.logb
go run ./cmd/logbdump /tmp/cut.logb       # 302 records, TRUNCATED

No repair step, no recovery mode — the truncated file is simply a shorter valid file.

Writing and reading a stream

The schema declares the record layout, the axis, and the stream's identity. The UUID says which logical stream this is across segments and files.

func schema() *logb.Schema {
	return &logb.Schema{
		UUID:       uuid.NewSHA1(uuid.NameSpaceOID, []byte("example/EngineData")),
		Name:       "EngineData",
		RecordBits: 24, // EngineSpeed(16) + CoolantTemp(8)

// One record every 10 ms, derived from the record index: the axis costs // zero bytes per record. AxisExp is the tick size as a power of ten, so // -9 means one tick is a nanosecond. AxisKind: logb.AxisTime, AxisMode: logb.AxisImplicit, AxisExp: -9, AxisUnit: "s", AxisStep: logb.TickVal(10_000_000),

// Raw is what the bus produced; Conv derives the physical value. Fields: []logb.Field{ {Name: "EngineSpeed", BitOffset: 0, BitWidth: 16, Type: logb.TypeUint, Unit: "rpm", Conv: logb.Linear{A: 0, B: 0.25}}, {Name: "CoolantTemp", BitOffset: 16, BitWidth: 8, Type: logb.TypeUint, Unit: "degC", Conv: logb.Linear{A: -40, B: 1}}, }, } }


Writing needs only an `io.Writer`. A segment restates its schemas, which is what lets a reader start there:

lw, _ := logb.NewWriter(w)
lw.AddStream(s)
lw.BeginSegment(1773480413000000000) // wall clock, ns
lw.WriteMeta("vehicle.vin", "WVWZZZ1JZXW000001")
lw.WriteData(s, logb.TickVal(0), 0, n, rec)
lw.Close() // writes the index and end frames — both optional by construction

Reading yields one batch per DATA frame:

r, _ := logb.NewReader(bytes.NewReader(data))
for {
	b, err := r.Next()
	if err == io.EOF {
		break
	}
	for i := 0; i < int(b.Count); i++ {
		axis, _ := b.Axis(i)     // b.Raw(i, f) gives the stored bits instead
		rpm, _ := b.Value(i, 0)  // conversion applied
		degC, _ := b.Value(i, 1)
		_, _, _ = axis, rpm, degC
	}
}

// Truncated means the scan stopped at damage rather than at a clean end. // Every batch returned before it was set is intact and trustworthy. if r.Truncated { fmt.Println("file is truncated; batches above are still intact") }


Recovering a cut file is one call:

r, off, err := logb.Resync(data[7000:])

The full worked example — multiple segments, mixed byte orders, variable-length fields, compression — is in `internal/example`.

Other languages

None yet. The specification is complete enough to implement from, the conformance vectors will tell you whether you got the hard part right, and there is nobody to ask for permission.


MD5 signature: 97bb9ff63c4a83746aa55c5829e04e09