Observe Lwt Unix

observe-lwt-unix is the ready Observe composition for Lwt applications on Unix. Link it with (libraries observe observe-lwt-unix lwt.unix). Add (preprocess (pps observe.ppx)) when using the deriver or untyped-value extension.

Initialization

let config =
  Observe.Config.create_exn ~service:"orders" ~environment:"development" ()

let () = Observe_lwt_unix.init_exn config

let () =
  [%observe.info text ~tag:"startup" "service ready"]

The initializer is synchronous. It installs Lwt callback-local context, the OS wall clock, cryptographically random UUID v4 operation identities, and runtime-owned random sampling draws when configured, and automatic formatted output on standard error. Console records are submitted without blocking the producer and written in order by one bounded Lwt worker. Merely linking the package allocates no writer, notification, or background Lwt work; those resources start during successful initialization when console output is active. Initialization starts no scheduler and returns no logging handle. See Observe_lwt_unix.

Config.Auto selects pretty output for an absent, dev, or development environment and NDJSON otherwise. Pretty, Ndjson, and Silent are explicit overrides.

Disclosure rules are also selected at configuration time:

let redaction =
  let module R = Observe.Logs.Redaction in
  R.create_exn
    ~rules:
      [ R.Rule.at (R.Path.fields [ "payment"; "card_number" ])
          (R.Action.mask
             (R.Mask.keep_suffix ~characters:4
                ~hidden:(R.Mask.Fill "*") ())) ]
    ()

let config = Observe.Config.create_exn ~service:"orders" ~redaction ()

Omitting ~redaction applies no caller-defined rule. Exact paths affect structured fields; string value matchers can also replace or mask point-text and wide annotation messages. Observe does not guess sensitive fields, rewrite package metadata, or deliver an original value when a redaction operation fails. See Observe.Logs.Redaction for typed schema policies, finite and custom masks, and stricter per-drain branches.

Sampling and completion retention are also selected through Observe.Config. Omitted sampling is inert. The Unix composition supplies the ordinary random source; deterministic tests can pass ~sampling_draw to Observe_lwt_unix.init. Observe serializes a custom source, validates each finite draw, diagnoses failures, and retains the affected log when a decision cannot be produced. Exact zero and 100 percent policies perform no draw or entropy initialization. Drain routes run only after the completed global safety floor. Scoped capture bypasses sampling and external routing so test results do not depend on randomness or production destinations.

Before process exit, run Observe_lwt_unix.shutdown through the active Lwt scheduler. Use Observe_lwt_unix.flush when the worker should remain open. Shutdown first closes production admission, then drains accepted output work. It is terminal and idempotent, including when called before initialization. Official filesystem workers register with the same lifecycle, so these calls cover console and daily-file records accepted before their barriers. The ordinary calls use a finite 30-second caller wait and raise Observe_lwt_unix.Lifecycle.Incomplete when a participant rejects, loses, fails, or remains unresolved. A timeout stops only that caller's wait; the shared output work continues.

Observe_lwt_unix.Lifecycle is the advanced reporting surface. Its flush and shutdown return an abstract report with bounded, named problems:

let within =
  Observe_lwt_unix.Lifecycle.Duration.create_exn ~seconds:5.
in
let* report = Observe_lwt_unix.Lifecycle.shutdown ~within () in
if not (Observe_lwt_unix.Lifecycle.complete report) then
  inspect (Observe_lwt_unix.Lifecycle.problems report)

An immediate custom output remains an Observe.Drain.t and needs no lifecycle registration. An independently installed asynchronous output that really owns a worker and cleanup can use Observe_lwt_unix.Lifecycle.Integration to register its finite cumulative rejection/loss facts plus flush and shutdown hooks. Independent flush boundaries can overlap; integration callbacks must be prompt and concurrency-safe. The ready filesystem package uses this seam internally while still returning its drain directly.

Operations

Observe_lwt_unix.with_operation creates a wide log, binds it as current for the callback, and makes one final publication attempt when the callback settles. Invalid error interpretation seals and withholds the wide log. Code in the callback retrieves the open handle through Observe.Logs.current or a schema-locked handle through Observe.Logs.current_typed. Child operations use Observe_lwt_unix.fork and restore the parent binding afterward.

Tests or deployments with an existing identity policy can supply ~id_generator to Observe_lwt_unix.init. The portable observe-lwt composition keeps next_id injectable for deterministic tests without changing the ready production default. The ready Unix composition serializes generator calls. Each call must promptly return a fresh, non-empty, valid UTF-8 operation identifier; a raised exception or invalid result withholds only that operation and produces a diagnostic.

Capture

Observe_lwt_unix.Test.with_capture_exn installs a bounded capture through real Lwt callback-local state. It restores the prior route after success, failure, or Lwt.Canceled.