Skip to content

Logger

Canonical source: protos/lib/logging/Logger.protos at locked revision e99b712ecf658a527b1fe82c8beba2c1286d8dc9.

// THE LICENSED WORK IS PROVIDED UNDER THE TERMS OF THE ADAPTIVE PUBLIC LICENSE
// ("LICENSE") AS FIRST COMPLETED BY: Guillermo Adrián Molina. ANY USE, PUBLIC
// DISPLAY, PUBLIC PERFORMANCE, REPRODUCTION OR DISTRIBUTION OF, OR PREPARATION OF
// DERIVATIVE WORKS BASED ON, THE LICENSED WORK CONSTITUTES RECIPIENT'S ACCEPTANCE
// OF THIS LICENSE AND ITS TERMS, WHETHER OR NOT SUCH RECIPIENT READS THE TERMS OF
// THE LICENSE. "LICENSED WORK" AND "RECIPIENT" ARE DEFINED IN THE LICENSE. A COPY
// OF THE LICENSE IS LOCATED IN THE TEXT FILE ENTITLED "LICENSE.TXT" ACCOMPANYING
// THE CONTENTS OF THIS FILE. IF A COPY OF THE LICENSE DOES NOT ACCOMPANY THIS
// FILE, A COPY OF THE LICENSE MAY ALSO BE OBTAINED AT THE FOLLOWING WEB SITE:
// https://github.com/guillermomolina/protos
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
// the specific language governing rights and limitations under the License.
//! Explicit structured loggers. A logger is a frozen ordinary object holding a
//! minimum level, a sink, and context `fields`; it is created and passed around
//! explicitly like any other value. There is no global or default logger, no
//! registry, no mutable configuration, and no thread-, Task-, or dynamically
//! scoped context. A logger opens no stream, file, or connection, reads no
//! ambient clock, and starts no Task.
//!
//! Time is an explicit capability: a logger created with a time source, a
//! zero-argument callable answering a `std:datetime/Instant`, stamps each
//! enabled event with the instant that source answers; a logger without one
//! emits events whose `timestamp` is `null`. The source is invoked exactly
//! once per enabled call, after its fields are merged and before the event is
//! created, and never for a disabled call or a derivation. An Error signalled
//! by the source, or a result that is not an instant, signals an Error from the
//! logging call before anything is emitted; it is not contained like a sink
//! failure.
//!
//! A sink is any object answering `emit(event)`; it is the only output
//! authority a logger uses and it receives each enabled `std:logging/LogEvent`
//! synchronously. An Error signalled by `emit` is contained by the logging
//! call: it does not reach the caller, is not retried, and is not reported
//! anywhere else. No order is promised between events emitted concurrently
//! through loggers sharing a sink.
//!
//! Level filtering happens first: a call whose level is less severe than the
//! minimum level answers `null` after validating only its level, without
//! inspecting its other arguments, copying data, reading the time source,
//! creating an event, or invoking the sink. Callers avoid building expensive
//! data for disabled levels with `isEnabled(level)`.
//!
//! Context and call-site fields merge by key: a derived logger's fields
//! override its base logger's fields, and call-site fields override both.
/// Answers a fresh frozen logger that emits events at least as severe as
/// `minimumLevel` to `sink`, stamped with times from `timeSource`, and has no
/// context fields. `minimumLevel` must be a canonical level and `sink` must not
/// be `null`; otherwise an Error is signalled. `timeSource` is a zero-argument
/// callable answering a `std:datetime/Instant`, or `null`, the default, for
/// events without a timestamp; it is not invoked here. The logger keeps the
/// time source privately, since a slot holding a callable would invoke it on
/// access.
///
/// The logger answers:
///
/// - `isEnabled(level)`: whether `level` is at least `minimumLevel`.
/// - `log(level, message)`, `log(level, message, fields)`, and
/// `log(level, message, fields, error)`: when enabled, emits a
/// `std:logging/LogEvent` whose fields are the logger's context merged with
/// `fields` (a Map, or `null` for none), whose error is `error` (an Error,
/// or `null`), and whose timestamp is the result of one `timeSource()` call
/// (`null` without a time source); answers `null`. Invalid enabled arguments
/// and a failing time source signal an Error.
/// - `trace`, `debug`, `info`, `warn`, and `error`: `log` at that level with
/// the remaining arguments.
/// - `with(fields)`: a derived logger with the same minimum level, sink, and
/// time source whose context is this logger's context overridden by a
/// snapshot of the Map `fields`. The receiver is unchanged.
call: (minimumLevel, sink, timeSource = null) => {
Level: import("std:logging/Level")
LogEvent: import("std:logging/LogEvent")
Instant: import("std:datetime/Instant")
Level.compare(minimumLevel, minimumLevel)
(sink === null).ifTrue(() => {
Error().signal()
})
// An empty matcher Map recognizes exactly normal standard Maps without
// invoking any behavior of the candidate.
mapMatcher: %{}
requireMap: (value) => {
(mapMatcher.match(value) === true).ifFalse(() => {
Error().signal()
})
}
build: null
build = (baseFields) => {
logger: {
minimumLevel: minimumLevel
sink: sink
fields: baseFields
isEnabled: (level) => {
Level.compare(level, minimumLevel) >= 0
}
log: (level, message, ...details) => {
(details.size() > 2).ifTrue(() => {
Error().signal()
})
(Level.compare(level, minimumLevel) >= 0).ifTrue(() => {
callFields: null
attached: null
(details.size() > 0).ifTrue(() => {
callFields = details[0]
})
(details.size() > 1).ifTrue(() => {
attached = details[1]
})
// Only top-level references are merged here; the event
// snapshots every nested value.
merged: Map()
baseFields.each((key, item) => {
merged[key] = item
})
(callFields === null).ifFalse(() => {
requireMap(callFields)
callFields.each((key, item) => {
String.recognizes(key).ifFalse(() => {
Error().signal()
})
merged[key] = item
})
})
timestamp: null
(timeSource === null).ifFalse(() => {
timestamp = timeSource()
Instant.recognizes(timestamp).ifFalse(() => {
Error().signal()
})
})
event: LogEvent(level, message, merged, attached, timestamp)
// Only the emission is contained; time-source and event
// failures reach the caller.
Error.handle(() => {
sink.emit(event)
}, (failure) => {
null
})
})
null
}
trace: (message, ...details) => {
this.log(Level.TRACE, message, ...details)
}
debug: (message, ...details) => {
this.log(Level.DEBUG, message, ...details)
}
info: (message, ...details) => {
this.log(Level.INFO, message, ...details)
}
warn: (message, ...details) => {
this.log(Level.WARN, message, ...details)
}
error: (message, ...details) => {
this.log(Level.ERROR, message, ...details)
}
with: (extraFields) => {
requireMap(extraFields)
// Context fields obey exactly the event field domain and
// snapshot rules, so the event constructor provides them.
snapshot: LogEvent(Level.TRACE, "", extraFields, null).fields
merged: Map()
baseFields.each((key, item) => {
merged[key] = item
})
snapshot.each((key, item) => {
merged[key] = item
})
merged.freeze()
build(merged)
}
}
logger.freeze()
logger
}
noFields: Map()
noFields.freeze()
build(noFields)
}

← logging family · Standard-library source