Logged — Accumulated Logs
Collecting audit logs or diagnostic traces during computations typically requires either manually
threading an array of log messages through every function or calling impure side effects
(console.log) that complicate unit testing.
Logged<W, A> pairs a computed value A with an accumulated log W (defaulting to string[]).
Operations on Logged append log entries automatically as transformations are chained: value A
with an accumulated read-only array of log entries W:
Logging is decoupled from execution. The log is treated purely as immutable data. We build our pipeline step-by-step, letting the logs accumulate automatically, and decide what to do with them once at the boundary of our program.
Creating Logged Values
Section titled “Creating Logged Values”To begin logging, we lift our values into Logged using its core constructors:
Logged.from.entry represents the atomic logging block. It writes a single log entry and returns
undefined as its value, ready to be sequenced into a pipeline.
Transforming and Sequencing
Section titled “Transforming and Sequencing”We can transform the values inside Logged and sequence multiple logging steps point-free.
Transforming values with map
Section titled “Transforming values with map”map transforms the underlying value of a Logged container, leaving any accumulated log entries
completely untouched:
Sequencing logs with chain
Section titled “Sequencing logs with chain”chain is the key combinator for Logged. It passes the value of the current Logged container to
your next step, executes the step, and automatically concatenates the log arrays from both steps in
order:
The intermediate log arrays are stitched together by chain itself. Each individual step only
declares its own log entry, fully isolated from the history of the pipeline.
Practical Example: A Business Rules Engine
Section titled “Practical Example: A Business Rules Engine”Consider a discount calculator that applies a series of promotional codes. To audit decisions, each rule must record its reasoning:
The promotional rules remain completely independent. Neither applyMemberPromo nor applyBulkPromo
has any knowledge of the other’s existence or log records. The audit trail is built automatically
during sequencing.
Extracting Results with run
Section titled “Extracting Results with run”Logged.run unpacks the container and returns the value and accumulated log as a standard tuple:
By calling run at the boundary of your system, you can choose to write the logs to an external
database, return them to the client, or filter them, keeping your operational code 100% pure.
Accumulating values: bind / bindTo
Section titled “Accumulating values: bind / bindTo”When you need to perform multiple sequential operations and gather their results into a single
object, nesting chain and map inside pipelines can become highly complex:
To solve this, you can use bindTo and bind to cleanly accumulate values key-by-key in a flat,
readable pipeline.
bindTo lifts a value into the pipeline’s accumulator object:
bind runs a new operation using the accumulated object and attaches the result to a new key:
All logs produced at each key-binding step are automatically concatenated in sequential order.
Problems it solves
Section titled “Problems it solves”- Auditable calculation engines: In tax estimation, pricing engines, and compliance evaluations,
business logic must output both a calculated result and an explicit audit trail explaining how
each step was determined.
Loggedpairs the computed value with an accumulated log array purely, without relying on side-effecting global loggers. - Data migration and non-fatal warning accumulation: When importing legacy datasets or
sanitizing user inputs, entries often contain non-fatal warnings (such as deprecated formats or
fallback defaults applied) that must be surfaced alongside transformed data.
Loggedpreserves these diagnostic records across pipeline steps. - Deterministic, mock-free log testing: Asserting that a workflow logged specific decisions
normally requires intercepting stdout or mocking logger instances.
Loggedtreats logs as first-class returned data, allowing unit tests to assert directly on trace arrays.