Skip to content
Chris Michael edited this page Sep 29, 2026 · 1 revision

Pudu

Sinks

A sink is a record of four functions: emit, flush, close, and attach for failure listeners. Sink.of builds one from an emit function and Sink.lines from a formatter and a writer.

Sink Module Options
Console Sinks.Console outputTemplate, theme (Theme.literate, grayscale, code, sixteen, none), colored, standardErrorFromLevel, formatter
File Sinks.File formatter, rollingInterval, fileSizeLimitBytes, rollOnFileSizeLimit, retainedFileCountLimit, retainedFileTimeLimit, buffered, flushInterval, onOpened, onDeleting
HTTP Sinks.Http endpoint, headers, contentType, formatter, batching, transport
Batching Sinks.Batching batchSizeLimit, bufferingTimeLimit, eagerlyEmitFirstEvent, queueLimit, retryTimeLimit
Async Sinks.Async bufferSize, blockWhenFull; pending and dropped counts
Memory Sinks.Memory create, bounded; events, messages, matching, withTemplate, latest, clear
Map Sinks.Map keyOf, create, limit; byProperty
Observable Sinks.Observable subscribe, unsubscribe, count; observers receive next and completed

Wrappers in Sink: restricted, controlled, conditional, aggregate, audited, fallible, fallbackChain, and reportTo for a listener writing to the self-log.

module Ex07Sinks

import Std.Env as Env
import Std.Io as Io
import Std.Result as Result
import PuduLangLog.Configuration as Configuration
import PuduLangLog.Formatting.Compact as Compact
import PuduLangLog as Log
import PuduLangLog.Logger as Logger
import PuduLangLog.Sink as Sink
import PuduLangLog.Sinks.Async as Async
import PuduLangLog.Sinks.Batching as Batching
import PuduLangLog.Sinks.File as File
import PuduLangLog.Sinks.Map as Map
import PuduLangLog.Sinks.Memory as Memory
import PuduLangLog.Value as Value

fn refusing(reason: Str) -> Sink.Sink { Sink.of(fn(_event: &Log.Event) -> Result[(), Str] { Err(reason) }) }

export fn main() -> Int {
  let directory = Env.temporaryDirectory() + "/pudu-lang-log-sinks-chapter"
  let _made = Io.makeDirectory(directory)
  let file = File.sink(File.Options{..File.defaults(directory + "/app.log"), formatter: Compact.compact(), fileSizeLimitBytes: Some(200), rollOnFileSizeLimit: true})
  let batches = Memory.create()
  let batching = Batching.sink(Batching.Target {
      emitBatch: fn(batch: &Array[Log.Event]) -> Result[(), Str] {
        let _said = Io.writeLine("batch of " + show(batch.length()))
        Ok(())
      },
      onEmptyBatch: fn() -> () {}
    }, Batching.Options{..Batching.defaults(), batchSizeLimit: 2, eagerlyEmitFirstEvent: false})
  let background = Async.start(file, Async.defaults())
  let fallback = Memory.create()
  let tenants = Memory.create()
  let audit = Memory.create()
  let logger = Configuration.create()
    .writeTo(Async.sinkOf(&background))
    .writeTo(batching)
    .writeTo(Map.sink(Map.defaults(Map.byProperty("Tenant", "shared"), fn(_tenant: Str) -> Sink.Sink { Memory.sink(&tenants) })))
    .writeToFallbackChain([refusing("primary down"), Memory.sink(&fallback)])
    .writeToFallible(refusing("disk full"), fn(report: &Sink.Report) -> () { let _said = Io.writeLine("listener heard: " + report.message) })
    .auditTo(Memory.sink(&audit))
    .createLogger()
  for number in [1, 2, 3, 4, 5] {
    logger.forContext("Tenant", Value.text(if number % 2 == 0 { "even" } else { "odd" })).information("Event \{Number\} of the day", [Value.int(number)])
  }
  let audited = logger.tryWrite(Log.Information, None, "Audited \{Action\}", [Value.text("refund")])
  Logger.close(&logger)
  let files = Result.unwrapOr(Io.listPaths(directory), [])
  let _files = Io.writeLine("files written: " + show(files.length()))
  for path in files { let _removed = Io.remove(path) }
  let _fallback = Io.writeLine("fallback kept " + show(Memory.events(&fallback).length()) + ", tenants kept " + show(Memory.events(&tenants).length()) + ", audit answered " + show(audited))
  0
}

Check it and run it:

pudu check src/Ex07Sinks.pudu
pudu run src/Ex07Sinks.pudu

Output:

listener heard: Failed to emit event to wrapped sink: disk full
listener heard: Failed to emit event to wrapped sink: disk full
batch of 2
listener heard: Failed to emit event to wrapped sink: disk full
listener heard: Failed to emit event to wrapped sink: disk full
listener heard: Failed to emit event to wrapped sink: disk full
batch of 2
listener heard: Failed to emit event to wrapped sink: disk full
batch of 2
files written: 2
fallback kept 6, tenants kept 6, audit answered Ok(())

Every sink but the audit sink is isolated: a failure goes to the self-log or a listener, and the other sinks still receive the event. The fallback chain tried the refusing sink first and kept each event in the next one.

Related

Clone this wiki locally