
Logger.pudu
Pudu224 lines10.1 KB
1/** @Log.Logger.Facade — writes events and derives contextual loggers */2module PuduLangLog.Logger34import Std.Sync as Sync5import PuduLangLog.Constants.Names as Names6import PuduLangLog.Domain.Capture as Capture7import PuduLangLog.Domain.Parser as Parser8import PuduLangLog.Enricher as Enricher9import PuduLangLog as Log10import PuduLangLog.Pipeline as Pipeline11import PuduLangLog.SelfLog as SelfLog12import PuduLangLog.Sink as Sink1314/** @Log.Logger.Handle — a pipeline seen through one context */15export type Logger = { root: Root, context: Array[Enricher.Enricher], source: Option[Str], trace: Option[(Str, Str)] }1617/** @Log.Logger.Root — a pipeline fixed at creation or replaceable later */18export type Root = Fixed(Pipeline.Pipeline) | Reloadable(Sync.Cell[Pipeline.Pipeline])192021export fn fromPipeline(pipeline: Pipeline.Pipeline) -> Logger { Logger{root: Fixed(pipeline), context: [], source: None, trace: None} }222324export fn reloadable(pipeline: Pipeline.Pipeline) -> Logger { Logger{root: Reloadable(Sync.cell(pipeline)), context: [], source: None, trace: None} }25262728export fn reload(logger: &Logger, pipeline: Pipeline.Pipeline) -> Bool {29 match &logger.root {30 case Reloadable(cell) => {31 if let Ok(previous) = Sync.swap(&cell, pipeline) {32 (previous.sink.flush)()33 (previous.sink.close)()34 }35 true36 }37 case Fixed(_) => false38 }39}404142export fn none() -> Logger { fromPipeline(Pipeline.silent()) }434445export fn pipelineOf(logger: &Logger) -> Pipeline.Pipeline {46 match &logger.root {47 case Fixed(pipeline) => pipeline48 case Reloadable(cell) => {49 match Sync.get(&cell) {50 case Ok(pipeline) => pipeline51 case Err(_) => Pipeline.silent()52 }53 }54 }55}565758export fn flush(logger: &Logger) -> () {59 let pipeline = pipelineOf(logger)60 (pipeline.sink.flush)()61 if let Some(audit) = pipeline.audit { (audit.flush)() }62}636465export fn close(logger: &Logger) -> () {66 flush(logger)67 let pipeline = pipelineOf(logger)68 (pipeline.sink.close)()69 if let Some(audit) = pipeline.audit { (audit.close)() }70}71727374export fn asSink(logger: &Logger) -> Sink.Sink {75 let shared = *logger76 Sink.Sink {77 emit: fn(event: &Log.Event) -> Result[(), Str] {78 let pipeline = pipelineOf(&shared)79 if !Pipeline.enabled(&pipeline, &shared.source, event.level) { return Ok(()) }80 Pipeline.process(&pipeline, *event, &shared.context)81 },82 flush: fn() -> () { flush(&shared) },83 close: fn() -> () { close(&shared) },84 attach: fn(listener: Sink.Listener) -> () { (pipelineOf(&shared).sink.attach)(listener) }85 }86}8788/** @Log.Logger.Logging — the operations of a logger */89export trait Logging {90 fn isEnabled(self: &Self, level: Log.Level) -> Bool91 fn write(self: &Self, level: Log.Level, template: Str, arguments: Array[Log.Value]) -> ()92 fn writeFailure(self: &Self, level: Log.Level, failure: Log.Failure, template: Str, arguments: Array[Log.Value]) -> ()93 fn tryWrite(self: &Self, level: Log.Level, failure: Option[Log.Failure], template: Str, arguments: Array[Log.Value]) -> Result[(), Str]94 fn emit(self: &Self, event: Log.Event) -> Result[(), Str]95 fn verbose(self: &Self, template: Str, arguments: Array[Log.Value]) -> ()96 fn debug(self: &Self, template: Str, arguments: Array[Log.Value]) -> ()97 fn information(self: &Self, template: Str, arguments: Array[Log.Value]) -> ()98 fn warning(self: &Self, template: Str, arguments: Array[Log.Value]) -> ()99 fn error(self: &Self, template: Str, arguments: Array[Log.Value]) -> ()100 fn fatal(self: &Self, template: Str, arguments: Array[Log.Value]) -> ()101 fn forContext(self: &Self, name: Str, held: Log.Value) -> Self102 fn forContextDestructured(self: &Self, name: Str, held: Log.Value) -> Self103 fn forSource(self: &Self, source: Str) -> Self104 fn forEnricher(self: &Self, enricher: Enricher.Enricher) -> Self105 fn withTrace(self: &Self, traceId: Str, spanId: Str) -> Self106 fn bindTemplate(self: &Self, template: Str, arguments: Array[Log.Value]) -> (Log.Template, Array[Log.Property])107 fn bindProperty(self: &Self, name: Str, held: Log.Value, destructure: Bool) -> Option[Log.Property]108}109110impl Logging for Logger {111 112 fn isEnabled(self: &Self, level: Log.Level) -> Bool { Pipeline.enabled(&pipelineOf(self), &self.source, level) }113114 115 fn write(self: &Self, level: Log.Level, template: Str, arguments: Array[Log.Value]) -> () {116 let _written = self.tryWrite(level, None, template, arguments)117 }118119 120 fn writeFailure(self: &Self, level: Log.Level, failure: Log.Failure, template: Str, arguments: Array[Log.Value]) -> () {121 let _written = self.tryWrite(level, Some(failure), template, arguments)122 }123124 125 126 fn tryWrite(self: &Self, level: Log.Level, failure: Option[Log.Failure], template: Str, arguments: Array[Log.Value]) -> Result[(), Str] {127 let pipeline = pipelineOf(self)128 if !Pipeline.enabled(&pipeline, &self.source, level) { return Ok(()) }129 let parsed = Pipeline.templateOf(&pipeline, template)130 let bound = Capture.bind(&pipeline.policy, &parsed, &arguments)131 Pipeline.report(&pipeline, &bound.problems)132 let event = Log.Event {133 timestamp: (pipeline.clock)(),134 level: level,135 template: parsed,136 properties: bound.properties,137 failure: failure,138 traceId: traceOf(&self.trace, true),139 spanId: traceOf(&self.trace, false)140 }141 Pipeline.process(&pipeline, event, &self.context)142 }143144 145 fn emit(self: &Self, event: Log.Event) -> Result[(), Str] {146 let pipeline = pipelineOf(self)147 if !Pipeline.enabled(&pipeline, &self.source, event.level) { return Ok(()) }148 Pipeline.process(&pipeline, event, &self.context)149 }150151 152 fn verbose(self: &Self, template: Str, arguments: Array[Log.Value]) -> () { self.write(Log.Verbose, template, arguments) }153154 155 fn debug(self: &Self, template: Str, arguments: Array[Log.Value]) -> () { self.write(Log.Debug, template, arguments) }156157 158 fn information(self: &Self, template: Str, arguments: Array[Log.Value]) -> () { self.write(Log.Information, template, arguments) }159160 161 fn warning(self: &Self, template: Str, arguments: Array[Log.Value]) -> () { self.write(Log.Warning, template, arguments) }162163 164 fn error(self: &Self, template: Str, arguments: Array[Log.Value]) -> () { self.write(Log.Error, template, arguments) }165166 167 fn fatal(self: &Self, template: Str, arguments: Array[Log.Value]) -> () { self.write(Log.Fatal, template, arguments) }168169 170 171 fn forContext(self: &Self, name: Str, held: Log.Value) -> Self { contextual(self, name, held, Log.Default) }172173 174 fn forContextDestructured(self: &Self, name: Str, held: Log.Value) -> Self { contextual(self, name, held, Log.Destructure) }175176 177 fn forSource(self: &Self, source: Str) -> Self { contextual(self, Names.SOURCE_CONTEXT, Log.Scalar(Log.Text(source)), Log.Default) }178179 180 fn forEnricher(self: &Self, enricher: Enricher.Enricher) -> Self { Logger{..*self, context: self.context.push(enricher)} }181182 183 fn withTrace(self: &Self, traceId: Str, spanId: Str) -> Self { Logger{..*self, trace: Some((traceId, spanId))} }184185 186 fn bindTemplate(self: &Self, template: Str, arguments: Array[Log.Value]) -> (Log.Template, Array[Log.Property]) {187 let pipeline = pipelineOf(self)188 let parsed = Pipeline.templateOf(&pipeline, template)189 let bound = Capture.bind(&pipeline.policy, &parsed, &arguments)190 Pipeline.report(&pipeline, &bound.problems)191 (parsed, bound.properties)192 }193194 195 fn bindProperty(self: &Self, name: Str, held: Log.Value, destructure: Bool) -> Option[Log.Property] {196 if !Parser.isValidName(name) { return None }197 let hint = if destructure { Log.Destructure } else { Log.Default }198 Some(Log.Property{name: name, value: Capture.capture(&pipelineOf(self).policy, &held, hint)})199 }200}201202203204fn contextual(logger: &Logger, name: Str, held: Log.Value, hint: Log.Hint) -> Logger {205 let pipeline = pipelineOf(logger)206 if !Parser.isValidName(name) {207 SelfLog.write(&pipeline.selfLog, "Attempt to add a context property with an invalid name `" + name + "`")208 return *logger209 }210 let captured = Capture.capture(&pipeline.policy, &held, hint)211 let enricher = Enricher.property(name, captured)212 let source = match (name == Names.SOURCE_CONTEXT, held) {213 case (true, Log.Scalar(Log.Text(text))) => Some(text)214 case _ => logger.source215 }216 Logger{..*logger, context: logger.context.push(enricher), source: source}217}218219220fn traceOf(trace: &Option[(Str, Str)], wantTrace: Bool) -> Option[Str] {221 let pair = (*trace) ?222 Some(if wantTrace { pair[0] } else { pair[1] })223}224