Skuld.Effects.Writer (skuld v0.1.26)

View Source

Writer effect - accumulate a log during computation.

Supports both simple single-log usage and multiple independent logs via tags.

Simple Usage (default tag)

use Skuld.Syntax
alias Skuld.Effects.Writer

comp do
  _ <- Writer.tell("step 1")
  _ <- Writer.tell("step 2")
  :done
end
|> Writer.with_handler([], output: fn result, log -> {result, log} end)
|> Comp.run!()
#=> {:done, ["step 2", "step 1"]}

Multiple Logs (explicit tags)

comp do
  _ <- Writer.tell(:audit, "user logged in")
  _ <- Writer.tell(:metrics, {:counter, :login, 1})
  _ <- Writer.tell(:audit, "user viewed dashboard")
  :ok
end
|> Writer.with_handler([], tag: :audit, output: fn r, log -> {r, log} end)
|> Writer.with_handler([], tag: :metrics)
|> Comp.run!()
#=> {:ok, ["user viewed dashboard", "user logged in"]}

Scoped Operations and Throw

The scoped operations (listen, pass, censor) use a peek-before/peek-after pattern to calculate captured logs. This means on abnormal exit (throw):

  • Logs written before the throw persist in state (not rolled back)
  • listen does not capture partial logs - the throw propagates out
  • censor does not apply its transform - logs leak out untransformed

This "fire-and-forget" semantic is intentional for logging (you typically want to see logs leading up to an error).

Summary

Functions

Install Writer handler via catch clause syntax.

Censor: transform all logs written during a computation.

Get the accumulated log from the environment.

Run a computation and capture its log output.

Run a computation that returns {value, log_transform_fn}.

Read the current log (reverse chronological order).

Returns the env.state key used for a given tag.

Append a message to the log.

Tell multiple messages.

Install a scoped Writer handler for a computation.

Functions

__handle__(comp, initial)

Install Writer handler via catch clause syntax.

Accepts nil, initial, or {initial, opts}:

catch
  Writer -> nil                          # empty initial
  Writer -> []                           # explicit empty initial
  Writer -> {[], output: fn r, l -> {r, Enum.reverse(l)} end}

censor(comp, transform_fn)

Censor: transform all logs written during a computation.

Examples

Writer.censor(comp, &Enum.map(&1, fn m -> "[REDACTED]" end))
Writer.censor(:audit, comp, &Enum.reverse/1)

censor(tag, comp, transform_fn)

clear(tag \\ Skuld.Effects.Writer)

@spec clear(atom()) :: Skuld.Comp.Types.computation()

Clear the log.

Examples

Writer.clear()        # use default tag
Writer.clear(:audit)  # use explicit tag

get_log(env, tag \\ Skuld.Effects.Writer)

@spec get_log(Skuld.Comp.Types.env(), atom()) :: [term()]

Get the accumulated log from the environment.

Examples

Writer.get_log(env)          # use default tag
Writer.get_log(env, :audit)  # use explicit tag

listen(comp)

Run a computation and capture its log output.

Returns {result, captured_log} where captured_log contains only the messages written during the inner computation.

Examples

Writer.listen(comp)          # use default tag
Writer.listen(:audit, comp)  # use explicit tag

listen(tag, comp)

pass(comp)

Run a computation that returns {value, log_transform_fn}.

The transform function is applied to the logs written during the computation.

Examples

Writer.pass(comp)          # use default tag
Writer.pass(:audit, comp)  # use explicit tag

pass(tag, comp)

peek(tag \\ Skuld.Effects.Writer)

@spec peek(atom()) :: Skuld.Comp.Types.computation()

Read the current log (reverse chronological order).

Examples

Writer.peek()        # use default tag
Writer.peek(:audit)  # use explicit tag

state_key(tag)

@spec state_key(atom()) :: {module(), atom()}

Returns the env.state key used for a given tag.

Useful for configuring EffectLogger's state_keys filter.

Examples

# Only capture Writer log in EffectLogger snapshots
EffectLogger.with_logging(state_keys: [Writer.state_key(:audit)])

# Multiple logs
EffectLogger.with_logging(state_keys: [
  Writer.state_key(:audit),
  Writer.state_key(:metrics)
])

tell(msg)

@spec tell(term()) :: Skuld.Comp.Types.computation()

Append a message to the log.

Examples

Writer.tell("message")           # use default tag
Writer.tell(:audit, "message")   # use explicit tag

tell(tag, msg)

@spec tell(atom(), term()) :: Skuld.Comp.Types.computation()

tell_many(messages)

@spec tell_many([term()]) :: Skuld.Comp.Types.computation()

Tell multiple messages.

Examples

Writer.tell_many(["a", "b", "c"])           # use default tag
Writer.tell_many(:audit, ["a", "b", "c"])   # use explicit tag

tell_many(tag, messages)

@spec tell_many(atom(), [term()]) :: Skuld.Comp.Types.computation()

with_handler(comp, initial \\ [], opts \\ [])

Install a scoped Writer handler for a computation.

Options

  • tag - the log tag (default: Skuld.Effects.Writer)
  • output - optional function (result, final_log) -> new_result to transform the result before returning.

Examples

# Simple usage with default tag
comp do
  _ <- Writer.tell("step 1")
  _ <- Writer.tell("step 2")
  :done
end
|> Writer.with_handler()
|> Comp.run!()
#=> :done

# Include final log in result
comp do
  _ <- Writer.tell("step 1")
  :done
end
|> Writer.with_handler([], output: fn result, log -> {result, log} end)
|> Comp.run!()
#=> {:done, ["step 1"]}

# With explicit tag
comp do
  _ <- Writer.tell(:audit, "action 1")
  :done
end
|> Writer.with_handler([], tag: :audit, output: fn r, log -> {r, log} end)
|> Comp.run!()
#=> {:done, ["action 1"]}

# Multiple logs
comp do
  _ <- Writer.tell(:foo, "message 1")
  _ <- Writer.tell(:bar, "message 2")
  :done
end
|> Writer.with_handler([], tag: :foo)
|> Writer.with_handler([], tag: :bar)
|> Comp.run!()
#=> :done