Skip to content

Logging Propagation And Metrics

Chris Michael edited this page Oct 6, 2026 · 1 revision

Pudu

Logging, Propagation, And Metrics

Handlers.Logging.observer(logger) writes pudu-lang-log events where a request enters and leaves the pipeline (PuduLangHttpClient.<name>.LogicalHandler) and where it reaches the primary handler (PuduLangHttpClient.<name>.ClientHandler), with headers at the verbose level. Observers never see the values of authorization, proxy-authorization, cookie, or set-cookie, nor of the headers named with redactingHeaders or matched by redactingWhen.

module Ex09Observability

import Std.Http as Http
import Std.Io as Io
import Std.Json as Json
import PuduLangLog.Configuration as Configuration
import PuduLangLog as Log
import PuduLangLog.Sinks.Memory as Memory
import PuduLangHttpClient as HttpClient
import PuduLangHttpClient.Client as Client
import PuduLangHttpClient.Factory as Factory
import PuduLangHttpClient.Factory.Builder as Builder
import PuduLangHttpClient.Handlers.Authorization as Authorization
import PuduLangHttpClient.Handlers.Logging as Logging
import PuduLangHttpClient.Handlers.Metrics as Metrics
import PuduLangHttpClient.Handlers.Propagation as Propagation
import PuduLangHttpClient.Request as Request
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Stub as Stub

export fn main() -> Int {
  let memory = Memory.create()
  let logger = Configuration.create().minimumLevel(Log.Verbose).writeTo(Memory.sink(&memory)).createLogger()
  let meter = Metrics.meter()
  let stub = Stub.create()
  Stub.when(&stub, |request: Request.Request| Request.header(&request, "authorization") == Some("Bearer fresh"), |request: Request.Request| Ok(Response.answer(&request, 200).withText(match Request.header(&request, "x-correlation-id") {
        case Some(found) => found
        case None => "none"
      })))
  Stub.when(&stub, |_request: Request.Request| true, |request: Request.Request| Ok(Response.answer(&request, 401)))
  let factory = match Factory.build([
      Builder.defaults().observedBy(Logging.observer(logger)).redactingHeaders(&["x-api-key"]),
      Builder.named("orders").withBaseAddress("https://orders.test/").withPrimary(|| Stub.send(&stub)).withDefaultHeader("x-api-key", "k-123")
        .withHandler(Metrics.handler(&meter))
        .withHandler(Propagation.handler(&["x-correlation-id"], || [("x-correlation-id", "order-77")]))
        .withHandler(Authorization.bearer(|refresh: Bool| Ok(if refresh { "fresh" } else { "expired" })))
    ]) {
    case Ok(built) => built
    case Err(invalid) => panic(Factory.explain(&invalid))
  }
  let answered = Client.getText(&Factory.createClient(&factory, "orders"), "status")
  let snapshot = Metrics.snapshot(&meter)
  let _a = Io.writeLine(show(answered) + " " + Json.encode(&snapshot.encode()))
  for line in Memory.messages(&memory) { let _said = Io.writeLine(line) }
  if answered == Ok("order-77") && snapshot.successful == 1 && Memory.messages(&memory).filter(|line: Str| line.contains("k-123")).isEmpty() { 0 } else { 1 }
}

Check it and run it:

pudu check src/Ex09Observability.pudu
pudu run src/Ex09Observability.pudu

Output:

Ok("order-77") {"requests":1,"active":0,"informational":0,"successful":1,"redirected":0,"clientErrors":0,"serverErrors":0,"failed":0,"totalMillis":3,"longestMillis":3}
Start processing HTTP request "GET" "https://orders.test/status"
Request headers: "x-api-key: *"
Sending HTTP request "GET" "https://orders.test/status"
Request headers: "x-api-key: *, x-correlation-id: order-77, authorization: *"
Received HTTP response headers after 1 ms - 401
Response headers: ""
Sending HTTP request "GET" "https://orders.test/status"
Request headers: "x-api-key: *, x-correlation-id: order-77, authorization: *"
Received HTTP response headers after 0 ms - 200
Response headers: "content-type: text/plain; charset=utf-8"
End processing HTTP request after 3 ms - 200
Response headers: "content-type: text/plain; charset=utf-8"

Handlers.Propagation.handler(names, source) copies headers of the operation in progress onto every outgoing request that lacks them, and renaming copies them under another name. Handlers.Authorization.basic and bearer add credentials; a bearer token is fetched again once when the server answers 401. Handlers.Metrics counts requests by status class, failures, requests in flight, and durations into a snapshot that derives Json.Encode.

Related

Clone this wiki locally