Logger
Logger
Section titled “Logger”Canonical source:
protos/lib/logging/Logger.protosat locked revisione99b712ecf658a527b1fe82c8beba2c1286d8dc9.
// 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)}