Johannes Wirde
04/27/2024, 1:19 PMJames Richardson
04/27/2024, 1:29 PMJohannes Wirde
04/27/2024, 1:30 PMJohannes Wirde
04/27/2024, 1:30 PMJohannes Wirde
04/27/2024, 1:31 PMJames Richardson
04/27/2024, 1:35 PMJames Richardson
04/27/2024, 1:41 PMobject UncaughtExceptionFilter {
operator fun invoke(events: Events): Filter {
return Filter { next ->
{
try {
next(it)
} catch (t: Throwable) {
events(UncaughtExceptionEvent(t))
Response(Status.INTERNAL_SERVER_ERROR)
}
}
}
}
}
or you could use ServerFilters.CatchAll( { .... your code } )
James Richardson
04/27/2024, 1:49 PMapp = Filter.NoOp
.then(ServerFilters.InitialiseRequestContext(contexts))
.then(maybeLogRequestsAndResponsesFilter)
.then(OpenTelemetry.openTelemetryFilter(environment, serviceName))
.then(
ResponseFilters.ReportHttpTransaction {
if (shouldLogTransaction(it)) {
events(HttpEvent.Incoming(it).plus("user-agent" to (it.request.header("user-agent") ?: "none")))
}
})
.then(UncaughtExceptionFilter(events))
.then(ServerFilters.CatchLensFailure)
.then(
routes(
Health(checks = checks()),
versionRoute("/api/version", appVersion()),
createRoutes()
)
)
here you can see that
• initialise request context - so that later filters can attach identity to requests
• maybe debug logging of request and responses (but not in production)
• open telemetry reporting for sending stuff to honeycomb
• inbound http request logging - path, status etc
• uncaught exceptions reporting - i consider every single error 500 a bug
• catch lens failure -> automatically return 400
• then adding some heath endoints, including api version and health checks
• then finally, whatever the app itself is doing...James Richardson
04/27/2024, 1:50 PMJames Richardson
04/27/2024, 1:52 PMdave
04/27/2024, 3:23 PMJohannes Wirde
04/27/2024, 3:24 PMAndrew O'Hara
04/27/2024, 6:55 PMval client = ResponseFilters.logSummary()
.then(ClientFilters.mdcToRequestId("CC-Request-ID"))
.then(JavaHttpClient())
.let { ExternalApi(it, apiHost) }
val server = ServerFilters
.then(ResponseFilters.logSummary())
.then(ServerFilters.requestIdToMdc("CC-Request-ID", generateRequestId = { IdGenerator.nextBase36(8) }))
.then(ServerFilters.logErrors())
.then(routes)
the MDC filters are my "poor man's" tracing, where a request id is generated and added to the SLF4j MDC to be included in all logs for that transaction (based on your logger config).Johannes Wirde
04/28/2024, 5:18 PMJohannes Wirde
04/28/2024, 5:32 PMJames Richardson
04/28/2024, 5:37 PMAndrew O'Hara
04/28/2024, 5:49 PMlogSummary
was in the Report HTTP Transactions link, which I've now renamed to match. All the filters used in my example are custom ones I've written, which you're free to copy from the links I've provided.Bogdan Costea
03/20/2025, 11:50 AMAndrew O'Hara
03/20/2025, 2:40 PMval internet = ClientFilters
.mdcToRequestId("My-Trace-Id")
.then(JavaHttpClient())
// pass internet into clients
val api = TODO("build service graph and generate API HttpHandler")
ResponseFilters.logSummary()
.then(ServerFilters.requestIdToMdc("My-Trace-Id", generateRequestId = { IdGenerator.nextBase36(8) }))
.then(ServerFilters.logErrors())
.then(myApi)
.asServer(Jetty(8000))
.start()
This assumes you're not using a tracing framework and just want to have a trace id appear in all your request logs. The server filter puts the request id into the slf4j MDC context (from the request headers, or generates a new one), and the client filter puts the trace id from the MDC context into the outgoing request headers; this, of course, only works on the same thread.
You'll need to update your logger's conf to output the MDC content. For example, with log4j2, it's the %X
tag:
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} %X %F:%L %-5level %logger{36} - %msg%n"/>
</Console>
You'll end up with something like
2025-03-20T10:43:01,185 {My-Trace-Id=FWI9BVLC} slf4jExtensions.kt:91 INFO root - GET /: 200 OK in 4 ms from [0:0:0:0:0:0:0:1]
EDIT: I see I've already posted something very much like this in this thread. If it's not what you're looking for, can you clarify?Bogdan Costea
03/21/2025, 6:21 AMJames Richardson
03/21/2025, 7:34 AMBogdan Costea
03/21/2025, 10:14 AMdave
03/22/2025, 11:15 AMdave
03/22/2025, 11:15 AMServerFilters.RequestTracing()
and ClientFilters.RequestTracing()
for inspiration 🙂