
TelemetryTest.pudu
Pudu82 lines5.2 KB
1/** @Test.Telemetry.Suite — reporting, severity overrides, metering, and log lines */2module PuduLangResilience.Telemetry.TelemetryTest34import Std.Io as Io5import Std.Sync as Sync6import Std.Test as Test7import PuduLangResilience.Clock as Clock8import PuduLangResilience.Context as Context9import PuduLangResilience.Pipeline as Pipeline10import PuduLangResilience as Resilience11import PuduLangResilience.Retry as Retry12import PuduLangResilience.Telemetry.Log as Log13import PuduLangResilience.Telemetry.Meter as Meter14import PuduLangResilience.Telemetry as Telemetry151617fn main() -> Int {18 let meter = Meter.enriched(fn(event: Telemetry.Event) -> Array[(Str, Str)] { [("severity", Telemetry.severityName(&event.severity))] })19 let lines: Sync.Cell[Array[Str]] = Sync.cell([])20 let writer = Log.listener(Telemetry.Warning, fn(line: Str) -> () {21 let held = match Sync.get(&lines) { case Ok(list) => list case Err(_) => [] }22 let _kept = Sync.set(&lines, held.push(line))23 })24 let clock = Clock.manual(0)25 let settings = Pipeline.Options {26 ..Pipeline.defaults(),27 name: Some("orders"),28 instance: Some("eu"),29 clock: Clock.ofManual(&clock),30 listeners: [Meter.listener(&meter), writer],31 severityOf: Some(fn(event: Telemetry.Event) -> Telemetry.Severity {32 if event.name == "PipelineExecuting" { Telemetry.Silent } else { event.severity }33 })34 }35 let pipeline = match Pipeline.buildWith(&settings, [Retry.strategy(Retry.Options{..Retry.defaults(), name: Some("retry"), maxRetryAttempts: 1, delay: 10})]) {36 case Ok(built) => built37 case Err(invalid) => panic(Pipeline.explain(&invalid))38 }39 let _ran = Pipeline.executeWith(&pipeline, Context.keyed("checkout"), fn(_context: Context.Context) -> Resilience.Outcome[Int, Str] {40 Clock.advance(&clock, 5)41 Resilience.raise("down")42 })43 let quiet: Telemetry.Reporter = Telemetry.silent()44 Telemetry.report(&quiet, &Context.create(), "Ignored", Telemetry.Critical, None, Telemetry.FellBack)45 let unnamed = Telemetry.Source{pipelineName: None, pipelineInstance: None, strategyName: None}46 let checks = Test.suite("Telemetry", &[47 Test.equals("a silenced event reaches no listener", &Meter.count(&meter, "PipelineExecuting"), &0),48 Test.equals("attempts are counted", &Meter.count(&meter, "ExecutionAttempt"), &2),49 Test.equals("retries are counted", &Meter.count(&meter, "OnRetry"), &1),50 Test.equals("attempt durations are recorded", &Meter.durations(&meter, "ExecutionAttempt"), &[5, 5]),51 Test.equals("execution durations are recorded", &Meter.durations(&meter, "PipelineExecuted"), &[20]),52 Test.equals("an event without durations records none", &Meter.durations(&meter, "OnRetry"), &[]),53 Test.equals("series name the source and enriched tags", &Meter.series(&meter), &[54 ("ExecutionAttempt source=orders/eu/retry,severity=Error", 1),55 ("ExecutionAttempt source=orders/eu/retry,severity=Warning", 1),56 ("OnRetry source=orders/eu/retry,severity=Warning", 1),57 ("PipelineExecuted source=orders/eu,severity=Warning", 1)58 ]),59 Test.equals("the log writes events at or above its severity", &Sync.get(&lines), &Ok([60 "[Warning] ExecutionAttempt source=orders/eu/retry operation=checkout attempt=0 duration=5 handled=true outcome=The operation failed: \"down\"",61 "[Warning] OnRetry source=orders/eu/retry operation=checkout attempt=0 delay=10 outcome=The operation failed: \"down\"",62 "[Error] ExecutionAttempt source=orders/eu/retry operation=checkout attempt=1 duration=5 handled=true outcome=The operation failed: \"down\"",63 "[Warning] PipelineExecuted source=orders/eu operation=checkout duration=20 outcome=The operation failed: \"down\""64 ])),65 Test.equals("a source without names reads as unnamed", &Telemetry.sourceName(&unnamed), &"(unnamed)"),66 Test.equals("severities rank from silent to critical", &[Telemetry.Silent, Telemetry.Debug, Telemetry.Information, Telemetry.Warning, Telemetry.Error, Telemetry.Critical].map(|level: Telemetry.Severity| Telemetry.rank(&level)), &[0, 1, 2, 3, 4, 5]),67 Test.equals("every detail formats", &[68 Telemetry.Opened(500, true), Telemetry.Closed(false), Telemetry.HalfOpened, Telemetry.Hedged(2), Telemetry.TimeoutElapsed(30),69 Telemetry.Rejected(Some(7)), Telemetry.Rejected(None), Telemetry.LatencyInjected(9), Telemetry.FaultInjected, Telemetry.OutcomeInjected, Telemetry.BehaviorInjected, Telemetry.Executing70 ].map(|detail: Telemetry.Detail| Log.format(&Telemetry.Event{name: "E", severity: Telemetry.Debug, source: unnamed, operationKey: None, outcome: None, detail: detail})), &[71 "[Debug] E source=(unnamed) break=500 manual=true", "[Debug] E source=(unnamed) manual=false", "[Debug] E source=(unnamed)", "[Debug] E source=(unnamed) attempt=2",72 "[Debug] E source=(unnamed) timeout=30", "[Debug] E source=(unnamed) retryAfter=7", "[Debug] E source=(unnamed)", "[Debug] E source=(unnamed) latency=9",73 "[Debug] E source=(unnamed)", "[Debug] E source=(unnamed)", "[Debug] E source=(unnamed)", "[Debug] E source=(unnamed)"74 ])75 ])76 let ran = Test.run(&checks)77 for failure in Test.failuresOf(&ran) {78 let _reported = Io.writeErrorLine(failure)79 }80 Test.report(&ran)81}82