Object-First Logging

Here’s a second, tighter **Incisive Review** of moldable logging with Beacon, followed by concrete falsification tests you can paste into GT. I stick to what the GT book actually claims and push on the edges.

--- ## What the GT book *really* claims (baseline) * **Object-first logging.** You emit **signals** (objects), not formatted strings; Beacon intentionally omits “formatters” and instead leans on inspectors and custom views. ([gtoolkit book][1]) * **Scoped logging + filtering.** You capture signals only when a **SignalLogger** is running, and you can scope it (`runDuring:`) or **filter by class** (`runFor:`). ([gtoolkit book][1]) * **Signals → Events.** “Moldable logging” = define **start/stop** signals and let a **grouper/collector** build **nested events** (typed or generic) from the stream. ([gtoolkit book][2]) * **Lazy payloads.** Heavy data can be produced lazily (computed only if a logger is listening). ([gtoolkit book][1]) * **It works in practice.** There are concrete examples (e.g., Zinc HTTP logging, parsing GT’s own Beacon logs). ([gtoolkit book][3]) --- ## Incisive review → targeted falsification ### 1) “Object logs beat text for analysis” **Supported:** Object signals + inspectors are more explorable than strings. ([gtoolkit book][1]) **Weak point:** Firehose streams (e.g., Maven/Nix stderr) can create huge numbers of objects and big `MemoryLogger` collections (unbounded by design). **Expect GC pressure and sluggish inspectors.** The docs don’t promise scalability here. **Test (overhead):** ```smalltalk | tText tObj mText mObj | Smalltalk garbageCollect. tText := [ 100000 timesRepeat: [ Transcript show: 'x'; cr ] ] timeToRun. mText := Smalltalk garbageCollect; memoryUsage. Smalltalk garbageCollect. tObj := [ (MemoryLogger named: 'L') runFor: StringSignal during: [ 100000 timesRepeat: [ StringSignal emit: 'x' ] ] ] timeToRun. mObj := Smalltalk garbageCollect; memoryUsage. { #textMs->tText. #objMs->tObj. #ΔKB->((mObj-mText)//1024) } "Expect obj >> text" ``` ### 2) “Event grouping yields robust hierarchies” **Supported:** Start/stop + (typed or labeled) signals → nested events. ([gtoolkit book][2]) **Weak point:** Real build tools rarely emit clean boundaries. Overlaps, missing stops, and concurrent phases require **correlation keys** you must inject; the book doesn’t guarantee recovery from malformed sequences. **Tests (malformed & overlap):** ```smalltalk | g s | s := OrderedCollection new. s add: (GtBeaconTypedLogSignal new markAsStopSignal; eventTypeName: #X). s add: (GtBeaconTypedLogSignal new markAsStartSignal; eventTypeName: #X; signalLabel: 'A'). s add: (GtBeaconTypedLogSignal new markAsStartSignal; eventTypeName: #X; signalLabel: 'B'). s add: (GtBeaconTypedLogSignal new markAsStopSignal; eventTypeName: #X; signalLabel: 'A'). s add: (GtBeaconTypedLogSignal new markAsStopSignal; eventTypeName: #X; signalLabel: 'B'). g := GtBeaconEventsGrouper new. g processEventSignalsFor: s. { #events->g events size. #strays->g straySignals size } "Watch for unexpected nesting/strays" ``` ### 3) “Filtering by signal class is precise and simple” **Supported:** `runFor:` captures only listed classes; the book shows exactly this pattern. ([gtoolkit book][1]) **Test:** ```smalltalk (MemoryLogger named: 'F') runFor: StringSignal during: [ StringSignal emit: 'kept'. Object new asBeaconSignal emit. "should be dropped" ]; recordings size "Expect 1" ``` ### 4) “Minimal boilerplate via generic signals” **Supported:** You can avoid custom classes with `GtBeaconTypedLogSignal` / `GtBeaconLabelledLogSignal` and still get events. ([gtoolkit book][2]) **Weak point:** You’ll still write **molded views** for useful summaries; the book doesn’t claim that comes for free. **Test (typed, no custom classes):** ```smalltalk | rec g | rec := (MemoryLogger named: 'E') runFor: GtBeaconTypedLogSignal during: [ (GtBeaconTypedLogSignal new markAsStartSignal; eventTypeName: #Build; signalLabel: 'ZKFX') emit. (GtBeaconTypedLogSignal new markAsEndSignal; eventTypeName: #Build; signalLabel: 'ZKFX') emit ]. g := GtBeaconEventsGrouper new. g processEventSignalsFor: rec. { #events->g events size. #strays->g straySignals size } "Expect 1 event, 0 strays" ``` ### 5) “Performance is acceptable with lazy capture” **Supported:** Lazy via `prepareForDelivery` means no cost if nobody listens. ([gtoolkit book][1]) **Weak point:** If a logger **is** running, you still allocate signals per line. **Test (lazy):** ```smalltalk | ran | ran := false. StringSignal new prepareForDelivery: [ ran := true. 'expensive' ]; emit. "no logger → ran stays false" { #ran->ran } "Expect false" ``` ### 6) “Integration with GT is a force multiplier (custom views)” **Supported:** Beacon ships inspector extensions; signals/events are first-class in GT. ([gtoolkit book][1]) **Weak point:** You still need `<gtView>`s for domain summaries; that’s moldable power, but it’s engineering work. **Micro-test (a trivial event view in-place):** Create a typed event as above, then add a one-table `<gtView>` on the event class and time it; you’ll find *building* the view is quick, but *good* views require domain logic. ### 7) “Resilience when things go wrong” **Documented:** You can emit **exceptions as signals** (that’s a pattern shown in book pages). ([gtoolkit book][2]) **Not documented:** Guarantees if **signal construction itself throws** (e.g., your parser explodes). You must guard your emitters. **Test:** ```smalltalk (MemoryLogger named: 'R') runFor: StringSignal during: [ [ 1 / 0 ] on: Error do: [ :e | StringSignal emit: ('caught: ', e message) ]. [ StringSignal emit: (1 / 0) ] on: Error do: [ :e | StringSignal emit: ('emit failed: ', e message) ] ]. "Expect 2 signals; verify logging continues after failures" ``` --- ## What likely **fails** first in your stderr/build scenario * **Throughput & memory:** `MemoryLogger` is unbounded; high-rate stderr → many objects → inspector lag/GC churn. The docs do **not** promise otherwise. Use scoped runs (`runFor:`), downsample, or a **bounded** custom logger. ([gtoolkit book][1]) * **Event boundaries:** You must synthesize start/stop (typed/labelled) and add correlation for parallelism; otherwise the grouper creates strays or odd trees. ([gtoolkit book][2]) * **Tooling effort:** The *value* comes from molded views you write; generic events alone aren’t the insight. --- ## Hardening patterns (while staying inside the docs’ contract) * **Scope + filter always.** Wrap builds in `MemoryLogger runFor: YourSignalClass during: [...]` to avoid unrelated signals. ([gtoolkit book][1]) * **Typed start/stop + keys.** Emit `GtBeaconTypedLogSignal` for *Build* / *Module* phases; include correlation (module name). Let `GtBeaconEventsCollector` group live. ([gtoolkit book][2]) * **Lazy enrichments.** Put expensive parsing/enrichment in `prepareForDelivery` so zero cost without a logger. ([gtoolkit book][1]) * **Bound your sink.** If you need long runs, don’t rely on `MemoryLogger`; implement a bounded logger (ring buffer) or snapshot/clear periodically. (The book’s `MemoryLogger` is in-memory by design.) ([gtoolkit book][1]) * **Hybrid logging.** Keep a rotating **text** log for completeness; emit Beacon signals only for **summaries** and **noteworthy** stderr lines (errors/warnings). --- ## Verdict * The **claims are valid within scope**: object signals + class filters + start/stop → events, lazy payloads, rich inspection in GT. ([gtoolkit book][1]) * They **do not** guarantee scalability for unbounded, noisy streams, nor resilience to malformed boundaries or construction-time exceptions—you must engineer those parts. If you want, I can turn the tests above into a one-click Lepiter page that runs them against your `p := GtSubprocessWithInMemoryOutput …` build and shows a pass/fail dashboard. [1]: https://book.gtoolkit.com/logging-with-beacon-53lj10cumhck8flzo567i0cuu?utm_source=chatgpt.com "Logging with Beacon" [2]: https://book.gtoolkit.com/an-example-of-moldable-logging-using-beaco-9iy8ihtv3046fcttg153ybg0y?utm_source=chatgpt.com "An example of moldable logging using Beacon" [3]: https://book.gtoolkit.com/how-to-log-zinc-events-using-beacon-94i4898osisv77xpzco65f9tq?utm_source=chatgpt.com "How to log Zinc events using Beacon"

–– chatgpt