-
Notifications
You must be signed in to change notification settings - Fork 595
MdcLoggingDirectives (withMdcLogging, withMdcEntry, withMdcEntries, extractMarkerLog) #3974
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
65eb272
629938f
6f6c4e0
d434e00
776f4ef
c652021
d0ac46e
efb56e0
e2f0da6
17c6540
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package akka.http.scaladsl.server.directives | ||
|
||
import akka.event.{ DiagnosticMarkerBusLoggingAdapter, MarkerLoggingAdapter } | ||
import akka.http.scaladsl.model.StatusCodes | ||
import akka.http.scaladsl.server.RoutingSpec | ||
import scala.jdk.CollectionConverters._ | ||
|
||
class MdcLoggingDirectivesSpec extends RoutingSpec { | ||
alexklibisz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
"The `withMdcLogging` directive" should { | ||
"provide a DiagnosticMarkerBusLoggingAdapter" in { | ||
Get() ~> withMdcLogging { | ||
extractLog { | ||
case _: DiagnosticMarkerBusLoggingAdapter => completeOk | ||
case other => failTest(s"expected a DiagnosticMarkerBusLoggingAdapter but found $other") | ||
} | ||
} ~> check { | ||
response shouldEqual Ok | ||
} | ||
} | ||
"provide a new DiagnosticMarkerBusLoggingAdapter for each request" in { | ||
val route = withMdcLogging { | ||
extractLog { | ||
case l: DiagnosticMarkerBusLoggingAdapter => complete(l.hashCode().toString) | ||
case other => failTest(s"expected a DiagnosticMarkerBusLoggingAdapter but found $other") | ||
} | ||
} | ||
val reps = 100 | ||
val responseEntities = (1 to reps).map(_ => Get() ~> route ~> check { | ||
status shouldEqual StatusCodes.OK | ||
entityAs[String] | ||
}) | ||
responseEntities.distinct.length shouldBe reps | ||
} | ||
"provide the same DiagnosticMarkerBusLoggingAdapter when nested multiple times" in { | ||
Get() ~> withMdcLogging { | ||
extractLog { log1 => | ||
withMdcLogging { | ||
extractLog { log2 => | ||
if (log1 == log2) completeOk | ||
else failTest(s"$log1 != $log2") | ||
} | ||
} | ||
} | ||
} ~> check { | ||
response shouldEqual Ok | ||
} | ||
} | ||
} | ||
|
||
"The `extractMarkerLog` directive" should { | ||
"provide a MarkerLoggingAdapter" in { | ||
Get() ~> extractMarkerLog { | ||
case _: MarkerLoggingAdapter => completeOk | ||
case other => failTest(s"expected a MarkerLoggingAdapter but found $other") | ||
} | ||
} | ||
"provide a new MarkerLoggingAdapter for each request" in { | ||
val route = extractMarkerLog { | ||
case log: MarkerLoggingAdapter => complete(log.hashCode().toString) | ||
case other => failTest(s"expected a MarkerLoggingAdapter but found $other") | ||
} | ||
val reps = 100 | ||
val responseEntities = (1 to reps).map(_ => Get() ~> route ~> check { | ||
status shouldEqual StatusCodes.OK | ||
entityAs[String] | ||
}) | ||
responseEntities.distinct.length shouldBe reps | ||
} | ||
"provide the same MarkerLoggingAdapter when nested multiple times" in { | ||
Get() ~> extractMarkerLog { log1 => | ||
withMdcLogging { | ||
extractMarkerLog { log2 => | ||
if (log1 == log2) completeOk | ||
else failTest(s"$log1 != $log2") | ||
} | ||
} | ||
} ~> check { | ||
response shouldEqual Ok | ||
} | ||
} | ||
} | ||
|
||
"The `withMdcEntries` directive" should { | ||
"append entries to the DiagnosticMarkerBusLoggingAdapter's MDC map" in { | ||
Get() ~> withMdcEntries(("foo", "foo entry"), ("bar", "bar entry")) { | ||
extractMarkerLog { | ||
case log: DiagnosticMarkerBusLoggingAdapter => | ||
val map = log.getMDC.asScala | ||
if (!map.get("foo").contains("foo entry")) failTest(s"missing or incorrect key 'foo' in $map") | ||
else if (!map.get("bar").contains("bar entry")) failTest(s"missing or incorrect key 'bar' in $map") | ||
else completeOk | ||
case other => failTest(s"expected a DiagnosticMarkerBusLoggingAdapter but found $other") | ||
} | ||
} ~> check { | ||
response shouldEqual Ok | ||
} | ||
} | ||
"replace entries with same key when nested" in { | ||
Get() ~> withMdcEntries(("foo", "foo entry 1")) { | ||
extractMarkerLog { | ||
case log: DiagnosticMarkerBusLoggingAdapter => | ||
val map = log.getMDC.asScala | ||
if (!map.get("foo").contains("foo entry 1")) failTest(s"'foo' should be 'foo entry 1'") | ||
else withMdcEntries(("foo", "foo entry 2")) { | ||
val map = log.getMDC.asScala | ||
if (!map.get("foo").contains("foo entry 2")) failTest(s"'foo' should be 'foo entry 2'") | ||
else completeOk | ||
} | ||
case other => failTest(s"expected a DiagnosticMarkerBusLoggingAdapter but found $other") | ||
} | ||
} ~> check { | ||
response shouldEqual Ok | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,62 @@ | ||||
package akka.http.scaladsl.server.directives | ||||
|
||||
import akka.event.{ | ||||
DefaultLoggingFilter, | ||||
DiagnosticMarkerBusLoggingAdapter, | ||||
LogSource, | ||||
MarkerLoggingAdapter | ||||
} | ||||
import akka.http.scaladsl.server.Directives._ | ||||
import akka.http.scaladsl.server._ | ||||
|
||||
/** | ||||
* @groupname mdc Mdc logging directives | ||||
* @groupprio mdc 240 | ||||
*/ | ||||
trait MdcLoggingDirectives { | ||||
|
||||
import MdcLoggingDirectives.extractDiagnosticMarkerLog | ||||
|
||||
def withMdcLogging: Directive0 = extractDiagnosticMarkerLog | ||||
.flatMap { dmbla: DiagnosticMarkerBusLoggingAdapter => | ||||
mapRequestContext { ctx => | ||||
ctx.withLog(dmbla) | ||||
} | ||||
} | ||||
|
||||
def extractMarkerLog: Directive1[MarkerLoggingAdapter] = | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need that, aren't all logger also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think so. I'm assuming you're implying we could just use
akka-http/akka-http/src/main/scala/akka/http/scaladsl/server/directives/BasicDirectives.scala Line 279 in 90b5ecb
Maybe it's true that the |
||||
withMdcLogging.tflatMap { _ => | ||||
alexklibisz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
extractDiagnosticMarkerLog | ||||
.map(identity[MarkerLoggingAdapter]) | ||||
} | ||||
|
||||
def withMdcEntries(entries: (String, Any)*): Directive0 = | ||||
withMdcLogging.tflatMap { _ => | ||||
extractDiagnosticMarkerLog | ||||
.map(log => log.mdc(log.mdc ++ entries.toMap)) | ||||
} | ||||
|
||||
def withMdcEntry(key: String, value: Any): Directive0 = | ||||
withMdcEntries((key, value)) | ||||
} | ||||
|
||||
object MdcLoggingDirectives extends MdcLoggingDirectives { | ||||
private val extractDiagnosticMarkerLog: Directive1[DiagnosticMarkerBusLoggingAdapter] = | ||||
extractActorSystem | ||||
.flatMap { sys => | ||||
extractRequestContext | ||||
.flatMap { ctx => | ||||
provide { | ||||
ctx.log match { | ||||
// If it's already a DiagnosticMarkerBusLoggingAdapter, this is a no-op. | ||||
case dmbla: DiagnosticMarkerBusLoggingAdapter => dmbla | ||||
// Otherwise, we need to construct a DiagnosticLoggingAdapter. | ||||
case _ => | ||||
val (str, cls) = LogSource.fromAnyRef(sys, sys) | ||||
val filter = new DefaultLoggingFilter(sys.settings, sys.eventStream) | ||||
new DiagnosticMarkerBusLoggingAdapter(sys.eventStream, str, cls, filter) | ||||
} | ||||
} | ||||
} | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# extractMarkerLog | ||
|
||
@@@ div { .group-scala } | ||
|
||
## Signature | ||
|
||
@@signature [MdcLoggingDirectives.scala](/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MdcLoggingDirectives.scala) { #extractMarkerLog } | ||
|
||
@@@ | ||
|
||
## Description | ||
|
||
Calls @ref[withMdcLogging](./withMdcLogging.md) and provides the resulting @apidoc[MarkerLoggingAdapter] from the @apidoc[RequestContext]. | ||
|
||
Nested calls will provide the same instance of `MarkerLoggingAdapter`. | ||
|
||
## Example | ||
|
||
Scala | ||
: @@snip [MdcLoggingDirectivesExamplesSpec.scala](/docs/src/test/scala/docs/http/scaladsl/server/directives/MdcLoggingDirectivesExamplesSpec.scala) { #extractMarkerLog } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# MdcLoggingDirectives | ||
|
||
Directives for request-level mapped diagnostic context logging using the @apidoc[MarkerLoggingAdapter]. | ||
|
||
These directives provide an API to setup a new MDC-compatible logger for every request and append (key, value) MDC entries to be included in any emitted logs for the duration of the request | ||
|
||
For example, one might extract a request ID from a header at the beginning of the request and append it as a (key, value) MDC entry. | ||
Any subsequent logs emitted by this request would include the request ID as an entry. | ||
|
||
@@toc { depth=1 } | ||
|
||
@@@ index | ||
|
||
* [withMdcLogging](withMdcLogging.md) | ||
* [withMdcEntries](withMdcEntries.md) | ||
* [withMdcEntry](withMdcEntry.md) | ||
* [extractMarkerLog](extractMarkerLog.md) | ||
|
||
@@@ | ||
|
||
## Structured JSON MDC Logging | ||
|
||
In order to get structured (i.e., JSON) MDC logging, some additional configurations are necessary. | ||
One possible configuration is as follows: | ||
|
||
1. Add akka-slf4j, logback-classic, and logstash-logback-encoder as dependencies. | ||
2. Add the configuration `akka.loggers = ["akka.event.slf4j.Slf4jLogger"]` to application.conf. | ||
3. Create a `logback.xml` file with an appender that uses the LogstashEncoder: | ||
|
||
```xml | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<configuration> | ||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | ||
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/> | ||
</appender> | ||
<root level="INFO"> | ||
<appender-ref ref="CONSOLE" /> | ||
</root> | ||
</configuration> | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# withMdcEntries | ||
|
||
@@@ div { .group-scala } | ||
|
||
## Signature | ||
|
||
@@signature [MdcLoggingDirectives.scala](/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MdcLoggingDirectives.scala) { #withMdcEntries } | ||
|
||
@@@ | ||
|
||
## Description | ||
|
||
Adds one or more (key, value) entries to the current MDC logging context. | ||
|
||
Nested calls will accumulate entries. | ||
|
||
## Example | ||
|
||
Scala | ||
: @@snip [MdcLoggingDirectivesExamplesSpec.scala](/docs/src/test/scala/docs/http/scaladsl/server/directives/MdcLoggingDirectivesExamplesSpec.scala) { #withMdcEntries } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# withMdcEntry | ||
|
||
@@@ div { .group-scala } | ||
|
||
## Signature | ||
|
||
@@signature [MdcLoggingDirectives.scala](/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MdcLoggingDirectives.scala) { #withMdcEntry } | ||
|
||
@@@ | ||
|
||
## Description | ||
|
||
Adds a single (key, value) entry to the current MDC logging context. | ||
|
||
Nested calls will accumulate entries. | ||
|
||
## Example | ||
|
||
Scala | ||
: @@snip [MdcLoggingDirectivesExamplesSpec.scala](/docs/src/test/scala/docs/http/scaladsl/server/directives/MdcLoggingDirectivesExamplesSpec.scala) { #withMdcEntry } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# withMdcLogging | ||
|
||
@@@ div { .group-scala } | ||
|
||
## Signature | ||
|
||
@@signature [MdcLoggingDirectives.scala](/akka-http/src/main/scala/akka/http/scaladsl/server/directives/MdcLoggingDirectives.scala) { #withMdcLogging } | ||
|
||
@@@ | ||
|
||
## Description | ||
|
||
Replaces the @apidoc[RequestContext]'s existing @apidoc[LoggingAdapter] with an MDC-compatible @apidoc[MarkerLoggingAdapter]. | ||
|
||
Nested calls will provide the same instance of `MarkerLoggingAdapter`. | ||
|
||
## Example | ||
|
||
Scala | ||
: @@snip [MdcLoggingDirectivesExamplesSpec.scala](/docs/src/test/scala/docs/http/scaladsl/server/directives/MdcLoggingDirectivesExamplesSpec.scala) { #withMdcLogging } |
Uh oh!
There was an error while loading. Please reload this page.