Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-log

Structured event logging for Pudu: message templates, enrichment, filtering, formatting, and sinks

0.1.0Apache-2.01

InstallClose

Configuration.pudu

Pudu208 lines10.7 KB

GitHub ↗
1/** @Log.Configuration.Builder — assembles a logger's pipeline step by step */2module PuduLangLog.Configuration34import Std.List as List5import PuduLangLog.Clock as Clock6import PuduLangLog.Domain.Capture as Capture7import PuduLangLog.Domain.Masking as Masking8import PuduLangLog.Enricher as Enricher9import PuduLangLog.Filter as Filter10import PuduLangLog.LevelSwitch as LevelSwitch11import PuduLangLog as Log12import PuduLangLog.Logger as Logger13import PuduLangLog.Pipeline as Pipeline14import PuduLangLog.SelfLog as SelfLog15import PuduLangLog.Sink as Sink1617/** @Log.Configuration.Settings — every choice a pipeline is built from */18export type Configuration = {19  minimum: Log.Level,20  control: Option[LevelSwitch.LevelSwitch],21  overrides: Array[Pipeline.Override],22  enrichers: Array[Enricher.Enricher],23  filters: Array[Filter.Filter],24  sinks: Array[Sink.Sink],25  audits: Array[Sink.Sink],26  policy: Capture.Policy,27  selfLog: SelfLog.SelfLog,28  clock: Clock.Clock29}3031/// A configuration keeping `Information` and above, with no sinks, the default capture policy, a32/// disabled self-log, and the system clock.33export fn create() -> Configuration {34  Configuration {35    minimum: Log.Information,36    control: None,37    overrides: [],38    enrichers: [],39    filters: [],40    sinks: [],41    audits: [],42    policy: Capture.defaults(),43    selfLog: SelfLog.create(),44    clock: Clock.system()45  }46}4748/// A configuration for a sub-logger: like `create` but keeping every level, since the parent49/// already decided which events arrive.50export fn nested() -> Configuration { Configuration{..create(), minimum: Log.Verbose} }5152/// The pipeline the configuration describes.53export fn pipelineOf(configuration: &Configuration) -> Pipeline.Pipeline {54  Pipeline.Pipeline {55    minimum: configuration.minimum,56    control: configuration.control,57    overrides: List.sortOn(&configuration.overrides, |entry: Pipeline.Override| entry.source.toLower()).reverse(),58    enrichers: configuration.enrichers,59    filters: configuration.filters,60    sink: Sink.aggregate(configuration.sinks, &configuration.selfLog),61    audit: if configuration.audits.isEmpty() { None } else { Some(Sink.audited(configuration.audits)) },62    policy: configuration.policy,63    selfLog: configuration.selfLog,64    clock: configuration.clock,65    templates: Pipeline.templates(),66    silent: false67  }68}6970/** @Log.Configuration.Configuring — one decision changed per call */71export trait Configuring {72  fn createLogger(self: &Self) -> Logger.Logger73  fn createReloadableLogger(self: &Self) -> Logger.Logger74  fn minimumLevel(self: &Self, level: Log.Level) -> Self75  fn controlledBy(self: &Self, control: &LevelSwitch.LevelSwitch) -> Self76  fn overrideLevel(self: &Self, source: Str, level: Log.Level) -> Self77  fn overrideSwitch(self: &Self, source: Str, control: &LevelSwitch.LevelSwitch) -> Self78  fn writeTo(self: &Self, sink: Sink.Sink) -> Self79  fn writeToAtLeast(self: &Self, sink: Sink.Sink, minimum: Log.Level) -> Self80  fn writeToControlled(self: &Self, sink: Sink.Sink, control: &LevelSwitch.LevelSwitch) -> Self81  fn writeToWhen(self: &Self, condition: fn(&Log.Event) -> Bool, sink: Sink.Sink) -> Self82  fn writeToLogger(self: &Self, logger: &Logger.Logger) -> Self83  fn writeToFallible(self: &Self, sink: Sink.Sink, listener: fn(&Sink.Report) -> ()) -> Self84  fn writeToFallbackChain(self: &Self, sinks: Array[Sink.Sink]) -> Self85  fn auditTo(self: &Self, sink: Sink.Sink) -> Self86  fn enrichWith(self: &Self, enricher: Enricher.Enricher) -> Self87  fn enrichWithProperty(self: &Self, name: Str, held: Log.Value) -> Self88  fn filterWith(self: &Self, filter: Filter.Filter) -> Self89  fn filterExcluding(self: &Self, predicate: fn(&Log.Event) -> Bool) -> Self90  fn filterIncludingOnly(self: &Self, predicate: fn(&Log.Event) -> Bool) -> Self91  fn destructureByTransforming(self: &Self, tag: Str, transform: fn(Log.Value) -> Log.Value) -> Self92  fn destructureAsScalar(self: &Self, tag: Str) -> Self93  fn destructureWithRule(self: &Self, rule: fn(&Log.Value) -> Option[Log.Value]) -> Self94  fn destructureByIgnoring(self: &Self, tag: Str, names: Array[Str]) -> Self95  fn destructureByMasking(self: &Self, tag: Str, names: Array[Str], mask: Str) -> Self96  fn maximumDepth(self: &Self, depth: Int) -> Self97  fn maximumStringLength(self: &Self, length: Int) -> Self98  fn maximumCollectionCount(self: &Self, count: Int) -> Self99  fn selfLog(self: &Self, log: &SelfLog.SelfLog) -> Self100  fn clock(self: &Self, clock: Clock.Clock) -> Self101}102103impl Configuring for Configuration {104  /// The logger the configuration describes. Sinks are joined so one failing sink never stops the105  /// others; overrides are tried most specific first.106  fn createLogger(self: &Self) -> Logger.Logger { Logger.fromPipeline(pipelineOf(self)) }107108  /// A logger whose pipeline `Logger.reload` can later replace with another configuration's, for109  /// logging during startup before the full configuration is read.110  fn createReloadableLogger(self: &Self) -> Logger.Logger { Logger.reloadable(pipelineOf(self)) }111112  /// Keeps events at or above a level, replacing any level switch.113  fn minimumLevel(self: &Self, level: Log.Level) -> Self { Configuration{..*self, minimum: level, control: None} }114115  /// Keeps events a level switch allows, so the minimum can change while running.116  fn controlledBy(self: &Self, control: &LevelSwitch.LevelSwitch) -> Self { Configuration{..*self, minimum: Log.Verbose, control: Some(*control)} }117118  /// Keeps events from a source prefix at or above their own level, in place of the minimum.119  fn overrideLevel(self: &Self, source: Str, level: Log.Level) -> Self { self.overrideSwitch(source, &LevelSwitch.create(level)) }120121  /// Keeps events from a source prefix that a level switch allows, in place of the minimum; it122  /// replaces an earlier override of the same prefix in any case.123  fn overrideSwitch(self: &Self, source: Str, control: &LevelSwitch.LevelSwitch) -> Self {124    let others = self.overrides.filter(|existing: Pipeline.Override| existing.source.toLower() != source.toLower())125    Configuration{..*self, overrides: others.push(Pipeline.Override{source: source, control: *control})}126  }127128  /// Writes events to a sink.129  fn writeTo(self: &Self, sink: Sink.Sink) -> Self { Configuration{..*self, sinks: self.sinks.push(sink)} }130131  /// Writes events at or above a level to a sink; a sink can raise the level but not lower it.132  fn writeToAtLeast(self: &Self, sink: Sink.Sink, minimum: Log.Level) -> Self { self.writeTo(Sink.restricted(sink, minimum)) }133134  /// Writes events a level switch allows to a sink.135  fn writeToControlled(self: &Self, sink: Sink.Sink, control: &LevelSwitch.LevelSwitch) -> Self { self.writeTo(Sink.controlled(sink, control)) }136137  /// Writes events a condition accepts to a sink.138  fn writeToWhen(self: &Self, condition: fn(&Log.Event) -> Bool, sink: Sink.Sink) -> Self { self.writeTo(Sink.conditional(sink, condition)) }139140  /// Writes events through another logger, whose own enrichers, filters, and sinks then apply.141  fn writeToLogger(self: &Self, logger: &Logger.Logger) -> Self { self.writeTo(Logger.asSink(logger)) }142143  /// Writes events to a sink whose failures go to a listener.144  fn writeToFallible(self: &Self, sink: Sink.Sink, listener: fn(&Sink.Report) -> ()) -> Self { self.writeTo(Sink.fallible(sink, listener)) }145146  /// Writes each event to the first sink of a chain that records it.147  fn writeToFallbackChain(self: &Self, sinks: Array[Sink.Sink]) -> Self { self.writeTo(Sink.fallbackChain(sinks)) }148149  /// Writes events to an audit sink, whose failures `tryWrite` reports to the caller.150  fn auditTo(self: &Self, sink: Sink.Sink) -> Self { Configuration{..*self, audits: self.audits.push(sink)} }151152  /// Applies an enricher to every event.153  fn enrichWith(self: &Self, enricher: Enricher.Enricher) -> Self { Configuration{..*self, enrichers: self.enrichers.push(enricher)} }154155  /// Adds a property to every event that does not name it.156  fn enrichWithProperty(self: &Self, name: Str, held: Log.Value) -> Self { self.enrichWith(Enricher.property(name, held)) }157158  /// Keeps only events a filter keeps.159  fn filterWith(self: &Self, filter: Filter.Filter) -> Self { Configuration{..*self, filters: self.filters.push(filter)} }160161  /// Drops events a predicate accepts.162  fn filterExcluding(self: &Self, predicate: fn(&Log.Event) -> Bool) -> Self { self.filterWith(Filter.byExcluding(predicate)) }163164  /// Keeps only events a predicate accepts.165  fn filterIncludingOnly(self: &Self, predicate: fn(&Log.Event) -> Bool) -> Self { self.filterWith(Filter.byIncludingOnly(predicate)) }166167  /// Replaces destructured structures of a tag with the transform's answer.168  fn destructureByTransforming(self: &Self, tag: Str, transform: fn(Log.Value) -> Log.Value) -> Self {169    Configuration{..*self, policy: Capture.Policy{..self.policy, transforms: self.policy.transforms.push(Capture.Transform{tag: tag, apply: transform})}}170  }171172  /// Captures structures of a tag as text, even when destructured.173  fn destructureAsScalar(self: &Self, tag: Str) -> Self {174    Configuration{..*self, policy: Capture.Policy{..self.policy, scalars: self.policy.scalars.push(tag)}}175  }176177  /// Adds a rule that may replace any destructured structure; the first rule answering wins.178  fn destructureWithRule(self: &Self, rule: fn(&Log.Value) -> Option[Log.Value]) -> Self {179    Configuration{..*self, policy: Capture.Policy{..self.policy, custom: self.policy.custom.push(rule)}}180  }181182  /// Leaves the listed members out of destructured structures of a tag; names compare ignoring case.183  fn destructureByIgnoring(self: &Self, tag: Str, names: Array[Str]) -> Self {184    self.destructureWithRule(fn(held: &Log.Value) -> Option[Log.Value] { Masking.ignoring(held, tag, &names) })185  }186187  /// Replaces the listed members of destructured structures of a tag with the mask text; names188  /// compare ignoring case.189  fn destructureByMasking(self: &Self, tag: Str, names: Array[Str], mask: Str) -> Self {190    self.destructureWithRule(fn(held: &Log.Value) -> Option[Log.Value] { Masking.masking(held, tag, &names, mask) })191  }192193  /// Captures at most `depth` levels of nesting.194  fn maximumDepth(self: &Self, depth: Int) -> Self { Configuration{..*self, policy: Capture.Policy{..self.policy, maximumDepth: depth}} }195196  /// Cuts captured text beyond `length` characters.197  fn maximumStringLength(self: &Self, length: Int) -> Self { Configuration{..*self, policy: Capture.Policy{..self.policy, maximumStringLength: length}} }198199  /// Keeps at most `count` members of each captured collection.200  fn maximumCollectionCount(self: &Self, count: Int) -> Self { Configuration{..*self, policy: Capture.Policy{..self.policy, maximumCollectionCount: count}} }201202  /// Reports pipeline problems to a self-log.203  fn selfLog(self: &Self, log: &SelfLog.SelfLog) -> Self { Configuration{..*self, selfLog: *log} }204205  /// Takes event timestamps from a clock.206  fn clock(self: &Self, clock: Clock.Clock) -> Self { Configuration{..*self, clock: clock} }207}208