moonbitstack/moontel/log does not have a README file

    Sink

    pub(open) trait Sink {
    fn accept(Self, LogRecord) -> Unit
    }

    Where log records go.

    The same seam the trace package leaves open, for the same reason: writing to a console, to a file, to a collector over HTTP or to nothing at all is the caller's business. A sink is called on the task that wrote the record, so an implementation that talks to the network should queue rather than block.

    Discard

    pub(all) struct Discard {
    }

    Throws records away. The sink a logger has before its owner has decided where they go.
    impl Sink for Discard

    Discard::accept

    fn Discard::accept(_ : Discard, _ : LogRecord) -> Unit

    Fanout

    pub struct Fanout {
    sinks : Array[&Sink]
    }

    Hands every record to each of several sinks, in the order they were given.

    A program that wants a console line and an export of the same record writes the line once and lets this do both; the sinks never learn about each other.
    impl Sink for Fanout

    Fanout::accept

    fn Fanout::accept(self : Fanout, record : LogRecord) -> Unit

    Fanout::new

    fn Fanout::new(sinks~ : Array[&Sink]) -> Fanout

    One sink that writes to all of sinks.

    LogRecord

    pub(all) struct LogRecord {
    time : Int64?
    observed : Int64
    severity : Severity
    severity_text : String?
    body :
    Value

    attributes : Array[(String,
    Value
    )]
    trace_id : Bytes?
    span_id : Bytes?
    flags : Byte
    event_name : String?
    scope : String
    dropped : Int
    } derive(Eq,
    Debug
    )

    One log record, as OpenTelemetry's data model defines it: when it happened, what it says, how urgent it is, the trace it belongs to, and the attributes it carries.

    It is a value. A sink may keep it, queue it, copy it or throw it away, and a logger that has handed one over never sees it again.

    time is None when the caller has no event time of its own — a record read out of another logging library may know only when it was collected. The observed time is always present, because a record that cannot say when it was seen cannot be ordered at all.

    LogRecord::context

    The trace and span the record belongs to, or None when it names no trace or names one that is not a trace: an id of the wrong width, or all zeros, is a producer's mistake rather than a link.

    LogRecord::equal

    fn LogRecord::equal(LogRecord, LogRecord) -> Bool

    LogRecord::new

    fn LogRecord::new(severity~ : Severity, body~ :
    Value
    , at~ : Int64, observed? : Int64, scope? : String) -> LogRecord

    A record at at, observed at the same moment unless the caller says otherwise.

    LogRecord::not_equal

    fn LogRecord::not_equal(x : LogRecord, y : LogRecord) -> Bool

    LogRecord::severity_text

    fn LogRecord::severity_text(self : LogRecord) -> String

    The severity as text: what the producer wrote, or the short name of the severity when it wrote nothing.

    LogRecord::timestamp

    fn LogRecord::timestamp(self : LogRecord) -> Int64

    When the record says it happened: its own time when it has one, and the moment it was observed otherwise. This is the timestamp a backend orders on, so it is never absent.

    LogRecord::with_attribute

    fn LogRecord::with_attribute(self : LogRecord, key : String, value :
    Value
    ) -> LogRecord

    The same record with one attribute added, replacing a value under the same key.

    LogRecord::with_context

    The same record carrying context's trace and span ids and its flags. A record written inside a span names that span, which is what lets a backend show the log line beside the work it belongs to.

    LogRecord::with_event_name

    fn LogRecord::with_event_name(self : LogRecord, event_name : String) -> LogRecord

    The same record with an event name, which is what makes it eligible to become a span event.

    LogRecord::with_time

    fn LogRecord::with_time(self : LogRecord, at~ : Int64) -> LogRecord

    The same record timed at at instead of where it was.

    Logger

    pub struct Logger {
    sink : &Sink
    now : () -> Int64
    scope : String
    attributes : Array[(String,
    Value
    )]
    current :
    TraceContext
    ?
    floor : Severity?
    trace_based : Bool
    limits :
    Limits

    }

    Writes records to a sink, carrying the trace it was written inside.

    A logger holds what every line from one part of a program shares: the scope it names, the attributes it always carries, the severity below which nothing is written, and the span the work is happening in. What changes per line is handed to log.

    It reads no clock of its own. now is the caller's, so a replay can write a timeline it chose and a service can hand in whichever clock it already trusts.

    Logger::debug

    fn Logger::debug(self : Logger, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64) -> Unit

    Write a DEBUG record.

    Logger::enabled

    fn Logger::enabled(self : Logger, severity : Severity) -> Bool

    Whether a record of this severity would be written: false below the floor, and false inside a trace that was not sampled when trace_based is on.

    Instrumented code can ask before it builds an expensive body.

    Logger::error

    fn Logger::error(self : Logger, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64, error? : Error) -> Unit

    Write an ERROR record.

    Logger::fatal

    fn Logger::fatal(self : Logger, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64) -> Unit

    Write a FATAL record.

    Logger::info

    fn Logger::info(self : Logger, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64) -> Unit

    Write an INFO record.

    Logger::inside

    The same logger writing inside span: every record it writes names that span's trace and span ids.

    Logger::log

    fn Logger::log(self : Logger, severity : Severity, body :
    Value
    , attributes? : Array[(String,
    Value
    )], at? : Int64, observed? : Int64, event_name? : String, error? : Error) -> Unit

    Write one record.

    at is when the event happened and observed is when the program noticed; both default to the logger's clock. error adds the three exception attributes — exception.type, exception.message, exception.stacktrace — and the caller's own attributes win over them, as the specification asks: a producer that knows better than the generic mapping keeps its value.

    Logger::new

    fn Logger::new(sink~ : &Sink, now~ : () -> Int64, scope? : String, attributes? : Array[(String,
    Value
    )], floor? : Severity, trace_based? : Bool, limits? :
    Limits
    ) -> Logger

    A logger writing to sink, timed by now.

    floor drops everything less urgent than it, None keeping every severity: the default is to keep what the program writes and let the sink decide what to do with it. trace_based drops records written inside a trace that was not sampled, which is what keeps a sampled-down service from paying for log lines about work no backend will ever see; records outside any trace are unaffected.

    limits is the span limits type, and a logger reads two of its numbers: attributes and attribute_value_length.

    Logger::scope

    fn Logger::scope(self : Logger) -> String

    The scope this logger names.

    Logger::trace

    fn Logger::trace(self : Logger, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64) -> Unit

    Write a TRACE record.

    Logger::under

    fn Logger::under(self : Logger, scope : String) -> Logger

    The same logger, naming scope instead. Nesting a scope is how a program says "this line came from the payment part of the order service".

    Logger::warn

    fn Logger::warn(self : Logger, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64) -> Unit

    Write a WARN record.

    Logger::with_attribute

    fn Logger::with_attribute(self : Logger, key : String, value :
    Value
    ) -> Logger

    The same logger with one more attribute on every record it writes, replacing a value under the same key.

    Logger::with_context

    The same logger writing inside context, which is what a caller that read a traceparent off a request has rather than a span of its own.

    Logger::write

    fn Logger::write(self : Logger, severity : Severity, message : String, attributes? : Array[(String,
    Value
    )], at? : Int64, observed? : Int64, event_name? : String, error? : Error) -> Unit

    Write a record whose body is plain text.

    Recorder

    pub struct Recorder {
    records :
    Ref
    [Array[LogRecord]]
    }

    Keeps records in memory, in the order they were written.

    For tests, replays and the debugging of one request. A service wants a sink that ships records off the process, because this one grows without bound.
    impl Sink for Recorder

    Recorder::accept

    fn Recorder::accept(self : Recorder, record : LogRecord) -> Unit

    Recorder::len

    fn Recorder::len(self : Recorder) -> Int

    How many records are kept.

    Recorder::new

    fn Recorder::new() -> Recorder

    An empty recorder.

    Recorder::records

    fn Recorder::records(self : Recorder) -> Array[LogRecord]

    The records kept so far, oldest first.

    Recorder::take

    fn Recorder::take(self : Recorder) -> Array[LogRecord]

    The records kept so far, and an empty recorder afterwards.

    Severity

    pub(all) enum Severity {
    Trace
    Debug
    Info
    Warn
    Error
    Fatal
    } derive(Eq,
    Debug
    )

    How urgent a record is.

    The six names are the ranges OpenTelemetry reserves in its severity number space, and the number each one reports is the first of its four slots: a backend that sorts on the number puts an Info record above every Debug record and below every Warn record, whatever the gaps between them.

    Severity::at_least

    fn Severity::at_least(self : Severity, floor : Severity) -> Bool

    Whether this severity is at least as urgent as floor, which is how a minimum level filters a stream of records.

    Severity::equal

    fn Severity::equal(Severity, Severity) -> Bool

    Severity::not_equal

    fn Severity::not_equal(x : Severity, y : Severity) -> Bool

    Severity::number

    fn Severity::number(self : Severity) -> Int

    The severity number, which is what an OTLP export carries: TRACE 1, DEBUG 5, INFO 9, WARN 13, ERROR 17, FATAL 21 (OpenTelemetry logs data model, "Severity Number").

    Severity::of_name

    fn Severity::of_name(name : StringView) -> Severity?

    The severity a name stands for, matched without regard to case, or None for a name this enum does not have. Reading a level out of configuration is the one place a string has to become a Severity.

    Severity::text

    fn Severity::text(self : Severity) -> String

    The severity text: the short name a human reads, and the value OTLP carries in severityText when the record does not spell its own.

    Severity::to_repr

    SpanEvents

    pub struct SpanEvents {
    span :
    Span

    }

    Turns a named record into an event on the span it was written inside.

    A log line that says "the payment was authorised" is also something that happened during the request, and a trace reader wants it on the timeline rather than in a separate list. Put this beside the exporter in a Fanout and the record does both.

    Only a record that names itself, names a valid trace and span, and names the span this sink was built with becomes an event. The last condition is what keeps a sink shared by a whole process from attaching one request's line to another request's span.
    impl Sink for SpanEvents

    SpanEvents::accept

    fn SpanEvents::accept(self : SpanEvents, record : LogRecord) -> Unit

    SpanEvents::new

    A sink that adds the records written inside span to span as events.

    discard

    let discard : Discard