
Registry.pudu
Pudu208 lines9.4 KB
1/** @Log.Settings.Registry.Catalog — sinks and enrichers settings may name */2module PuduLangLog.Settings.Registry34import Std.List as List5import PuduLangLog.Domain.Settings as Plan6import PuduLangLog.Enricher as Enricher7import PuduLangLog.Enrichers.Environment as Environment8import PuduLangLog.Formatting.Compact as Compact9import PuduLangLog.Formatting.Json as Json10import PuduLangLog.Formatting.Text as Text11import PuduLangLog as Log12import PuduLangLog.Sink as Sink13import PuduLangLog.Sinks.Batching as Batching14import PuduLangLog.Sinks.Console as Console15import PuduLangLog.Sinks.File as File16import PuduLangLog.Sinks.Http as Http17import PuduLangLog.Sinks.Theme as Theme1819/** @Log.Settings.Registry.SinkFactory — a sink built from its arguments */20export type SinkFactory = fn(&Array[(Str, Str)]) -> Result[Sink.Sink, Str]2122/** @Log.Settings.Registry.EnricherFactory — an enricher built from its arguments */23export type EnricherFactory = fn(&Array[(Str, Str)]) -> Result[Enricher.Enricher, Str]2425/** @Log.Settings.Registry.Catalog — factories by name */26export type Registry = { sinks: Array[(Str, SinkFactory)], enrichers: Array[(Str, EnricherFactory)] }272829export fn empty() -> Registry { Registry{sinks: [], enrichers: []} }3031323334export fn standard() -> Registry {35 empty()36 .withSink("Console", console)37 .withSink("File", file)38 .withSink("Http", http)39 .withEnricher("MachineName", fixed(Environment.machineName))40 .withEnricher("EnvironmentUserName", fixed(Environment.environmentUserName))41 .withEnricher("EnvironmentName", fn(arguments: &Array[(Str, Str)]) -> Result[Enricher.Enricher, Str] { Ok(Environment.environmentName(textOr(arguments, "variable", Environment.ENVIRONMENT_VARIABLE))) })42 .withEnricher("EnvironmentVariable", fn(arguments: &Array[(Str, Str)]) -> Result[Enricher.Enricher, Str] { Ok(Environment.environmentVariable(required(arguments, "variable") ?)) })43 .withEnricher("ProcessId", fixed(Environment.processId))44 .withEnricher("ProcessName", fixed(Environment.processName))45 .withEnricher("FailureDetail", fixed(Environment.failureDetail))46}4748/** @Log.Settings.Registry.Registering — adding factories under names */49export trait Registering {50 fn withSink(self: &Self, name: Str, factory: SinkFactory) -> Self51 fn withEnricher(self: &Self, name: Str, factory: EnricherFactory) -> Self52}5354impl Registering for Registry {55 56 fn withSink(self: &Self, name: Str, factory: SinkFactory) -> Self {57 Registry{..*self, sinks: self.sinks.filter(|entry: (Str, SinkFactory)| entry[0].toLower() != name.toLower()).push((name, factory))}58 }5960 61 62 fn withEnricher(self: &Self, name: Str, factory: EnricherFactory) -> Self {63 Registry{..*self, enrichers: self.enrichers.filter(|entry: (Str, EnricherFactory)| entry[0].toLower() != name.toLower()).push((name, factory))}64 }65}666768export fn sinkNamed(registry: &Registry, name: Str) -> Option[SinkFactory] {69 let found = List.find(®istry.sinks, |entry: (Str, SinkFactory)| entry[0].toLower() == name.toLower()) ?70 Some(found[1])71}727374export fn enricherNamed(registry: &Registry, name: Str) -> Option[EnricherFactory] {75 let found = List.find(®istry.enrichers, |entry: (Str, EnricherFactory)| entry[0].toLower() == name.toLower()) ?76 Some(found[1])77}787980fn fixed(build: fn() -> Enricher.Enricher) -> EnricherFactory {81 fn(_arguments: &Array[(Str, Str)]) -> Result[Enricher.Enricher, Str] { Ok(build()) }82}83848586fn console(arguments: &Array[(Str, Str)]) -> Result[Sink.Sink, Str] {87 let base = Console.defaults()88 let theme = match lookup(arguments, "theme") {89 case Some(name) => themeOf(name) ?90 case None => base.theme91 }92 let standardError = match lookup(arguments, "standardErrorFromLevel") {93 case Some(text) => Some(levelOf(text) ?)94 case None => None95 }96 Ok(Console.sink(Console.Options{..base, outputTemplate: textOr(arguments, "outputTemplate", base.outputTemplate), theme: theme, standardErrorFromLevel: standardError, formatter: namedFormatter(arguments) ?}))97}9899100101fn file(arguments: &Array[(Str, Str)]) -> Result[Sink.Sink, Str] {102 let base = File.defaults(required(arguments, "path") ?)103 let formatter = match (namedFormatter(arguments) ?, lookup(arguments, "outputTemplate")) {104 case (Some(named), _) => named105 case (None, Some(output)) => Text.template(output)106 case _ => base.formatter107 }108 let interval = match lookup(arguments, "rollingInterval") {109 case Some(text) => optionOf(Plan.intervalOf(text), "rolling interval", text) ?110 case None => base.rollingInterval111 }112 Ok(File.sink(File.Options {113 ..base,114 formatter: formatter,115 rollingInterval: interval,116 fileSizeLimitBytes: countOr(arguments, "fileSizeLimitBytes", base.fileSizeLimitBytes) ?,117 rollOnFileSizeLimit: flagOr(arguments, "rollOnFileSizeLimit", base.rollOnFileSizeLimit) ?,118 retainedFileCountLimit: countOr(arguments, "retainedFileCountLimit", base.retainedFileCountLimit) ?,119 buffered: flagOr(arguments, "buffered", base.buffered) ?120 }))121}122123124fn http(arguments: &Array[(Str, Str)]) -> Result[Sink.Sink, Str] {125 let base = Http.defaults(required(arguments, "endpoint") ?)126 let formatter = match namedFormatter(arguments) ? {127 case Some(named) => named128 case None => base.formatter129 }130 let batchSize = match lookup(arguments, "batchSizeLimit") {131 case Some(text) => optionOf(Plan.count(text), "count", text) ?132 case None => base.batching.batchSizeLimit133 }134 let batching = Batching.Options{..base.batching, batchSizeLimit: batchSize, queueLimit: countOr(arguments, "queueLimit", base.batching.queueLimit) ?}135 Ok(Http.sink(Http.Options{..base, formatter: formatter, batching: batching}))136}137138139fn namedFormatter(arguments: &Array[(Str, Str)]) -> Result[Option[Log.Formatter], Str] {140 let name = match lookup(arguments, "formatter") {141 case Some(text) => text.trim().toLower()142 case None => { return Ok(None) }143 }144 let found = List.find(&formatters(), |entry: (Str, Log.Formatter)| entry[0] == name)145 match found {146 case Some(entry) => Ok(Some(entry[1]))147 case None => Err("Unknown formatter `" + name + "`")148 }149}150151152fn formatters() -> Array[(Str, Log.Formatter)] { [("compact", Compact.compact()), ("rendered", Compact.rendered()), ("json", Json.formatter(Json.defaults()))] }153154155fn themeOf(name: Str) -> Result[Theme.Theme, Str] {156 let themes = [("none", Theme.none()), ("literate", Theme.literate()), ("grayscale", Theme.grayscale()), ("code", Theme.code()), ("sixteen", Theme.sixteen())]157 match List.find(&themes, |entry: (Str, Theme.Theme)| entry[0] == name.trim().toLower()) {158 case Some(entry) => Ok(entry[1])159 case None => Err("Unknown theme `" + name + "`")160 }161}162163164fn lookup(arguments: &Array[(Str, Str)], name: Str) -> Option[Str] { Plan.argument(&Plan.Target{name: "", arguments: *arguments}, name) }165166167fn textOr(arguments: &Array[(Str, Str)], name: Str, fallback: Str) -> Str {168 match lookup(arguments, name) {169 case Some(text) => text170 case None => fallback171 }172}173174175fn required(arguments: &Array[(Str, Str)], name: Str) -> Result[Str, Str] {176 match lookup(arguments, name) {177 case Some(text) => if text.trim().isEmpty() { Err("The argument `" + name + "` is empty") } else { Ok(text) }178 case None => Err("The argument `" + name + "` is required")179 }180}181182183fn levelOf(text: Str) -> Result[Log.Level, Str] { optionOf(Plan.levelOf(text), "level", text) }184185186fn countOr(arguments: &Array[(Str, Str)], name: Str, fallback: Option[Int]) -> Result[Option[Int], Str] {187 match lookup(arguments, name) {188 case Some(text) => if text.trim().toLower() == "none" { Ok(None) } else { Ok(Some(optionOf(Plan.count(text), "count", text) ?)) }189 case None => Ok(fallback)190 }191}192193194fn flagOr(arguments: &Array[(Str, Str)], name: Str, fallback: Bool) -> Result[Bool, Str] {195 match lookup(arguments, name) {196 case Some(text) => optionOf(Plan.flag(text), "flag", text)197 case None => Ok(fallback)198 }199}200201202fn optionOf[T](parsed: Option[T], what: Str, text: Str) -> Result[T, Str] {203 match parsed {204 case Some(found) => Ok(found)205 case None => Err("`" + text + "` is not a " + what)206 }207}208