fio/observer

Types

A single fio operation event emitted to a sink.

pub type Event {
  Event(
    op: String,
    path: String,
    outcome: Result(Nil, error.FioError),
    bytes: option.Option(Int),
  )
}

Constructors

  • Event(
      op: String,
      path: String,
      outcome: Result(Nil, error.FioError),
      bytes: option.Option(Int),
    )

    Arguments

    op

    Short operation name: “read”, “write”, “copy”, “delete”, etc.

    path

    Primary path argument of the operation.

    outcome

    Ok(Nil) on success, Error(e) on failure. The actual return value (file content, etc.) is excluded to keep the event type generic and independent of the operation’s return type.

    bytes

    Byte count involved, when known. For read operations: bytes returned. For write operations: bytes written. None when not applicable (e.g. delete, rename).

A sink consumes events produced by observed fio operations.

Implement this type to integrate with any observability backend: structured loggers, metrics systems, test recorders, or custom hooks. A sink receives events synchronously on the same thread/process as the caller.

pub type Sink =
  fn(Event) -> Nil

Values

pub fn emit(
  result: Result(a, error.FioError),
  op: String,
  path: String,
  bytes: option.Option(Int),
  sink: fn(Event) -> Nil,
) -> Result(a, error.FioError)

Emit an Event to sink after result is produced, then return result unchanged.

This is the lowest-level building block. Use trace when you do not have a byte count to report.

fio.read_bits("data.bin")
|> observer.emit("read_bits", "data.bin", option.None, my_sink)
pub fn fan_out(
  first: fn(Event) -> Nil,
  second: fn(Event) -> Nil,
) -> fn(Event) -> Nil

Combine two sinks into one: both receive every event in order.

Useful for fan-out — log to stdout AND record in a test buffer:

let combined = observer.fan_out(log_sink, test_recorder_sink)
fio.write("out.txt", data) |> observer.trace("write", "out.txt", combined)
pub fn format(event: Event) -> String

Format an Event as a human-readable string. Useful when building simple logging sinks.

fn log_sink(event: Event) -> Nil {
  io.println(observer.format(event))
}
pub fn noop_sink(event: Event) -> Nil

A no-op sink that discards all events. Useful as a default/placeholder argument when observability is optional.

pub fn copy(src, dest, sink: observer.Sink) {
  fio.copy(src, dest) |> observer.trace("copy", src, sink)
}
// caller passes observer.noop_sink when not interested
copy("a.txt", "b.txt", observer.noop_sink)
pub fn trace(
  result: Result(a, error.FioError),
  op: String,
  path: String,
  sink: fn(Event) -> Nil,
) -> Result(a, error.FioError)

Like emit but without a byte count (bytes is always None).

Use this for operations where byte count is not meaningful (delete, rename, touch, create_directory, etc.):

fio.delete("old.txt")
|> observer.trace("delete", "old.txt", my_sink)
pub fn trace_bytes(
  result: Result(BitArray, error.FioError),
  op: String,
  path: String,
  sink: fn(Event) -> Nil,
) -> Result(BitArray, error.FioError)

Like trace but automatically infers the byte count from the result when the operation returns a BitArray (e.g. fio.read_bits).

fio.read_bits("archive.tar.gz")
|> observer.trace_bytes("read_bits", "archive.tar.gz", my_sink)
Search Document