Add MDC to log4j

This commit is contained in:
2025-03-16 01:42:57 +01:00
parent ca95344ca9
commit 804ccd785e
14 changed files with 196 additions and 244 deletions

View File

@@ -11,6 +11,7 @@ import eventDemo.app.notification.Notification
import eventDemo.libs.command.CommandId import eventDemo.libs.command.CommandId
import eventDemo.libs.command.CommandStreamChannel import eventDemo.libs.command.CommandStreamChannel
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import java.util.UUID import java.util.UUID
@@ -56,35 +57,29 @@ class GameCommandHandler(
player: Player, player: Player,
incomingCommandChannel: ReceiveChannel<GameCommand>, incomingCommandChannel: ReceiveChannel<GameCommand>,
channelNotification: SendChannel<Notification>, channelNotification: SendChannel<Notification>,
) = ) {
commandStreamChannel.process(incomingCommandChannel) { command -> commandStreamChannel.process(incomingCommandChannel) { command ->
if (command.payload.player.id != player.id) { withLoggingContext("command" to command.toString()) {
logger.atWarn { if (command.payload.player.id != player.id) {
message = "Handle command Refuse, the player of the command is not the same: $command" logger.warn { "Handle command Refuse, the player of the command is not the same" }
payload = mapOf("command" to command) channelNotification.sendError(command)("You are not the author of this command\n")
} } else {
channelNotification.sendError(command)("You are not the author of this command\n") logger.info { "Handle command" }
} else { try {
logger.atInfo { val eventBuilder = runner.run(command)
message = "Handle command: $command"
payload = mapOf("command" to command)
}
try {
val eventBuilder = runner.run(command)
eventHandler.handle(command.payload.aggregateId) { version -> eventHandler.handle(command.payload.aggregateId) { version ->
eventBuilder(version) eventBuilder(version)
.also { eventCommandMap.set(it.eventId, channelNotification, command.id) } .also { eventCommandMap.set(it.eventId, channelNotification, command.id) }
}
} catch (e: CommandException) {
logger.warn(e) { e.message }
channelNotification.sendError(command)(e.message)
} }
} catch (e: CommandException) {
logger.atWarn {
message = e.message
payload = mapOf("command" to command)
}
channelNotification.sendError(command)(e.message)
} }
} }
} }
}
} }
private fun SendChannel<Notification>.sendSuccess(commandId: CommandId): suspend () -> Unit = private fun SendChannel<Notification>.sendSuccess(commandId: CommandId): suspend () -> Unit =
@@ -92,15 +87,10 @@ private fun SendChannel<Notification>.sendSuccess(commandId: CommandId): suspend
val logger = KotlinLogging.logger { } val logger = KotlinLogging.logger { }
CommandSuccessNotification(commandId = commandId) CommandSuccessNotification(commandId = commandId)
.also { notification -> .also { notification ->
logger.atDebug { withLoggingContext("notification" to notification.toString(), "commandId" to commandId.toString()) {
message = "Notification SUCCESS sent" logger.debug { "Notification SUCCESS sent" }
payload = send(notification)
mapOf(
"notification" to notification,
"commandId" to commandId,
)
} }
send(notification)
} }
} }
@@ -109,15 +99,10 @@ private fun SendChannel<Notification>.sendError(command: GameCommand): suspend (
val logger = KotlinLogging.logger { } val logger = KotlinLogging.logger { }
CommandErrorNotification(message = it, command = command) CommandErrorNotification(message = it, command = command)
.also { notification -> .also { notification ->
logger.atWarn { withLoggingContext("notification" to notification.toString(), "command" to command.toString()) {
message = "Notification ERROR sent: ${notification.message}" logger.warn { "Notification ERROR sent: ${notification.message}" }
payload = send(notification)
mapOf(
"notification" to notification,
"command" to command,
)
} }
send(notification)
} }
} }

View File

@@ -5,6 +5,7 @@ import eventDemo.app.eventListener.PlayerNotificationEventListener
import eventDemo.app.notification.Notification import eventDemo.app.notification.Notification
import eventDemo.libs.fromFrameChannel import eventDemo.libs.fromFrameChannel
import eventDemo.libs.toObjectChannel import eventDemo.libs.toObjectChannel
import io.github.oshai.kotlinlogging.withLoggingContext
import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCall
import io.ktor.server.auth.authenticate import io.ktor.server.auth.authenticate
import io.ktor.server.auth.jwt.JWTPrincipal import io.ktor.server.auth.jwt.JWTPrincipal
@@ -25,14 +26,16 @@ fun Route.gameSocket(
webSocket("/game") { webSocket("/game") {
val currentPlayer = call.getPlayer() val currentPlayer = call.getPlayer()
val outgoingFrameChannel: SendChannel<Notification> = fromFrameChannel(outgoing) val outgoingFrameChannel: SendChannel<Notification> = fromFrameChannel(outgoing)
GlobalScope.launch { withLoggingContext("currentPlayer" to currentPlayer.toString()) {
commandHandler.handle( GlobalScope.launch {
currentPlayer, commandHandler.handle(
toObjectChannel(incoming), currentPlayer,
outgoingFrameChannel, toObjectChannel(incoming),
) outgoingFrameChannel,
)
}
playerNotificationListener.startListening(outgoingFrameChannel, currentPlayer)
} }
playerNotificationListener.startListening(outgoingFrameChannel, currentPlayer)
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package eventDemo.app.event
import eventDemo.app.entity.GameId import eventDemo.app.entity.GameId
import eventDemo.app.event.event.GameEvent import eventDemo.app.event.event.GameEvent
import eventDemo.libs.event.VersionBuilder import eventDemo.libs.event.VersionBuilder
import io.github.oshai.kotlinlogging.withLoggingContext
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
@@ -31,20 +32,28 @@ class GameEventHandler(
aggregateId: GameId, aggregateId: GameId,
buildEvent: (version: Int) -> GameEvent, buildEvent: (version: Int) -> GameEvent,
): GameEvent = ): GameEvent =
locks withLoggingContext("aggregateId" to aggregateId.toString()) {
// Get lock for the aggregate locks
.computeIfAbsent(aggregateId) { ReentrantLock() } // Get lock for the aggregate
.withLock { .computeIfAbsent(aggregateId) { ReentrantLock() }
// Build event with the version .withLock {
buildEvent(versionBuilder.buildNextVersion(aggregateId)) // Build event with the version
// then publish it to the event store buildEvent(versionBuilder.buildNextVersion(aggregateId))
.also { eventStore.publish(it) } // then publish it to the event store
}.also { event -> .also {
// Build the projections withLoggingContext("event" to it.toString()) {
projectionsBuilders.forEach { it(event) } eventStore.publish(it)
// Publish to the bus }
eventBus.publish(event) }
} }.also { event ->
withLoggingContext("event" to event.toString()) {
// Build the projections
projectionsBuilders.forEach { it(event) }
// Publish to the bus
eventBus.publish(event)
}
}
}
} }
typealias GameProjectionBuilder = (GameEvent) -> Unit typealias GameProjectionBuilder = (GameEvent) -> Unit

View File

@@ -1,15 +0,0 @@
package eventDemo.app.event
import eventDemo.app.event.event.GameEvent
import eventDemo.libs.event.EventStream
/**
* A stream to publish and read the played card event.
*/
class GameEventStream(
private val eventStream: EventStream<GameEvent>,
) : EventStream<GameEvent> by eventStream {
override fun publish(event: GameEvent) {
eventStream.publish(event)
}
}

View File

@@ -18,7 +18,7 @@ fun GameState.apply(event: GameEvent): GameState =
if (event is PlayerActionEvent) { if (event is PlayerActionEvent) {
if (state.currentPlayerTurn != event.player) { if (state.currentPlayerTurn != event.player) {
logger.atError { logger.atError {
message = "Inconsistent player turn. CurrentPlayerTurn: $state.currentPlayerTurn | Player: ${event.player}" message = "Inconsistent player turn"
payload = payload =
mapOf( mapOf(
"CurrentPlayerTurn" to (state.currentPlayerTurn ?: "No currentPlayerTurn"), "CurrentPlayerTurn" to (state.currentPlayerTurn ?: "No currentPlayerTurn"),

View File

@@ -5,6 +5,7 @@ import eventDemo.libs.event.Event
import eventDemo.libs.event.EventStore import eventDemo.libs.event.EventStore
import eventDemo.libs.event.EventStream import eventDemo.libs.event.EventStream
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -176,20 +177,21 @@ class ProjectionSnapshotRepositoryInMemory<E : Event<ID>, P : Projection<ID>, ID
aggregateId: ID, aggregateId: ID,
eventsToApply: Set<E>, eventsToApply: Set<E>,
): P = ): P =
eventsToApply eventsToApply.fold(this ?: initialStateBuilder(aggregateId), applyToProjectionSecure)
.fold(this ?: initialStateBuilder(aggregateId), applyToProjectionSecure)
/** /**
* Wrap the [applyToProjection] lambda to avoid duplicate apply of the same event. * Wrap the [applyToProjection] lambda to avoid duplicate apply of the same event.
*/ */
private val applyToProjectionSecure: P.(event: E) -> P = { event -> private val applyToProjectionSecure: P.(event: E) -> P = { event ->
if (event.version == lastEventVersion + 1) { withLoggingContext("event" to event.toString(), "projection" to this.toString()) {
applyToProjection(event) if (event.version == lastEventVersion + 1) {
} else if (event.version <= lastEventVersion) { applyToProjection(event)
KotlinLogging.logger { }.warn { "Event is already is the Projection, skip apply." } } else if (event.version <= lastEventVersion) {
this KotlinLogging.logger { }.warn { "Event is already is the Projection, skip apply." }
} else { this
error("The version of the event must follow directly after the version of the projection.") } else {
error("The version of the event must follow directly after the version of the projection.")
}
} }
} }
} }

View File

@@ -24,6 +24,7 @@ import eventDemo.app.notification.TheGameWasStartedNotification
import eventDemo.app.notification.WelcomeToTheGameNotification import eventDemo.app.notification.WelcomeToTheGameNotification
import eventDemo.app.notification.YourNewCardNotification import eventDemo.app.notification.YourNewCardNotification
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
@@ -38,106 +39,104 @@ class PlayerNotificationEventListener(
currentPlayer: Player, currentPlayer: Player,
) { ) {
eventBus.subscribe { event: GameEvent -> eventBus.subscribe { event: GameEvent ->
val currentState = gameStateRepository.getUntil(event) withLoggingContext("event" to event.toString()) {
val currentState = gameStateRepository.getUntil(event)
fun Notification.send() { fun Notification.send() {
if (currentState.players.contains(currentPlayer)) { withLoggingContext("notification" to this.toString()) {
// Only notify players who have already joined the game. if (currentState.players.contains(currentPlayer)) {
outgoingNotificationChannel.trySendBlocking(this) // Only notify players who have already joined the game.
logger.atInfo { outgoingNotificationChannel.trySendBlocking(this)
message = "Notification for player ${currentPlayer.name} was SEND: ${this@send}" logger.info { "Notification was SEND" }
payload = mapOf("notification" to this@send, "event" to event) } else {
} // Rare use case, when a connexion is created with the channel,
} else { // but the player was not already join in the game
// Rare use case, when a connexion is created with the channel, logger.warn { "Notification was SKIP, no player on the game" }
// but the player was not already join in the game }
logger.atWarn {
message = "Notification for player ${currentPlayer.name} was SKIP, No player on the game: ${this@send}"
payload = mapOf("notification" to this@send, "event" to event)
}
}
}
fun sendNextTurnNotif() =
ItsTheTurnOfNotification(
player = currentState.currentPlayerTurn ?: error("No player turn defined"),
).send()
when (event) {
is NewPlayerEvent -> {
if (currentPlayer != event.player) {
PlayerAsJoinTheGameNotification(
player = event.player,
).send()
} else {
WelcomeToTheGameNotification(
players = currentState.players,
).send()
} }
} }
is CardIsPlayedEvent -> { fun sendNextTurnNotif() =
if (currentPlayer != event.player) { ItsTheTurnOfNotification(
PlayerAsPlayACardNotification( player = currentState.currentPlayerTurn ?: error("No player turn defined"),
player = event.player,
card = event.card,
).send()
}
if (event.card !is Card.AllColorCard) {
ItsTheTurnOfNotification(
player = currentState.currentPlayerTurn ?: error("No player turn defined"),
).send()
}
}
is GameStartedEvent -> {
TheGameWasStartedNotification(
hand =
event.deck.playersHands.getHand(currentPlayer)
?: error("You are not in the game"),
).send() ).send()
sendNextTurnNotif() when (event) {
} is NewPlayerEvent -> {
if (currentPlayer != event.player) {
is PlayerChoseColorEvent -> { PlayerAsJoinTheGameNotification(
if (currentPlayer != event.player) { player = event.player,
PlayerWasChoseTheCardColorNotification( ).send()
player = event.player, } else {
color = event.color, WelcomeToTheGameNotification(
).send() players = currentState.players,
).send()
}
} }
sendNextTurnNotif() is CardIsPlayedEvent -> {
} if (currentPlayer != event.player) {
PlayerAsPlayACardNotification(
player = event.player,
card = event.card,
).send()
}
is PlayerHavePassEvent -> { if (event.card !is Card.AllColorCard) {
if (currentPlayer == event.player) { ItsTheTurnOfNotification(
YourNewCardNotification( player = currentState.currentPlayerTurn ?: error("No player turn defined"),
card = event.takenCard, ).send()
}
}
is GameStartedEvent -> {
TheGameWasStartedNotification(
hand =
event.deck.playersHands.getHand(currentPlayer)
?: error("You are not in the game"),
).send() ).send()
} else {
PlayerHavePassNotification( sendNextTurnNotif()
}
is PlayerChoseColorEvent -> {
if (currentPlayer != event.player) {
PlayerWasChoseTheCardColorNotification(
player = event.player,
color = event.color,
).send()
}
sendNextTurnNotif()
}
is PlayerHavePassEvent -> {
if (currentPlayer == event.player) {
YourNewCardNotification(
card = event.takenCard,
).send()
} else {
PlayerHavePassNotification(
player = event.player,
).send()
}
sendNextTurnNotif()
}
is PlayerReadyEvent -> {
if (currentPlayer != event.player) {
PlayerWasReadyNotification(
player = event.player,
).send()
}
}
is PlayerWinEvent -> {
PlayerWinNotification(
player = event.player, player = event.player,
).send() ).send()
} }
sendNextTurnNotif()
}
is PlayerReadyEvent -> {
if (currentPlayer != event.player) {
PlayerWasReadyNotification(
player = event.player,
).send()
}
}
is PlayerWinEvent -> {
PlayerWinNotification(
player = event.player,
).send()
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import eventDemo.app.event.event.PlayerWinEvent
import eventDemo.app.event.projection.GameState import eventDemo.app.event.projection.GameState
import eventDemo.app.event.projection.GameStateRepository import eventDemo.app.event.projection.GameStateRepository
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
class ReactionEventListener( class ReactionEventListener(
private val eventBus: GameEventBus, private val eventBus: GameEventBus,
@@ -24,13 +25,15 @@ class ReactionEventListener(
fun init() { fun init() {
eventBus.subscribe(priority) { event: GameEvent -> eventBus.subscribe(priority) { event: GameEvent ->
val state = gameStateRepository.getUntil(event) withLoggingContext("event" to event.toString()) {
sendStartGameEvent(state, event) val state = gameStateRepository.getUntil(event)
sendWinnerEvent(state, event) sendStartGameEvent(state, event)
sendWinnerEvent(state)
}
} }
} }
private suspend fun sendStartGameEvent( private fun sendStartGameEvent(
state: GameState, state: GameState,
event: GameEvent, event: GameEvent,
) { ) {
@@ -44,12 +47,8 @@ class ReactionEventListener(
) )
} }
logger.atInfo { logger.atInfo {
message = "Reaction event was Send $reactionEvent on reaction of: $event" message = "Reaction event was Send"
payload = payload = mapOf("reactionEvent" to reactionEvent)
mapOf(
"event" to event,
"reactionEvent" to reactionEvent,
)
} }
} else { } else {
if (event is PlayerReadyEvent) { if (event is PlayerReadyEvent) {
@@ -58,10 +57,7 @@ class ReactionEventListener(
} }
} }
private fun sendWinnerEvent( private fun sendWinnerEvent(state: GameState) {
state: GameState,
event: GameEvent,
) {
val winner = state.playerHasNoCardLeft().firstOrNull() val winner = state.playerHasNoCardLeft().firstOrNull()
if (winner != null) { if (winner != null) {
val reactionEvent = val reactionEvent =
@@ -74,12 +70,8 @@ class ReactionEventListener(
} }
logger.atInfo { logger.atInfo {
message = "Reaction event was Send $reactionEvent on reaction of: $event" message = "Reaction event was Send"
payload = payload = mapOf("reactionEvent" to reactionEvent)
mapOf(
"event" to event,
"reactionEvent" to reactionEvent,
)
} }
} }
} }

View File

@@ -1,24 +0,0 @@
package eventDemo.app.eventListener
import eventDemo.app.notification.CommandSuccessNotification
import eventDemo.app.notification.Notification
import eventDemo.libs.command.CommandId
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.channels.SendChannel
private fun SendChannel<Notification>.successNotifier(commandId: CommandId): suspend () -> Unit =
{
val logger = KotlinLogging.logger { }
CommandSuccessNotification(commandId = commandId)
.let { notification ->
logger.atDebug {
message = "Notification SUCCESS sent"
payload =
mapOf(
"notification" to notification,
"commandId" to commandId,
)
}
send(notification)
}
}

View File

@@ -1,6 +1,7 @@
package eventDemo.libs.command package eventDemo.libs.command
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
/** /**
@@ -20,15 +21,14 @@ class CommandStreamChannel<C : Command>(
action: CommandBlock<C>, action: CommandBlock<C>,
) { ) {
for (command in incoming) { for (command in incoming) {
try { withLoggingContext("command" to command.toString()) {
controller.runOnlyOnce(command) { try {
// Wrap action to add logs controller.runOnlyOnce(command) {
runAndLogStatus(command, action) // Wrap action to add logs
} runAndLogStatus(command, action)
} catch (e: CommandRunnerController.Exception) { }
logger.atWarn { } catch (e: CommandRunnerController.Exception) {
message = e.message logger.warn { e.message }
payload = mapOf("command" to command)
} }
} }
} }
@@ -40,16 +40,9 @@ class CommandStreamChannel<C : Command>(
) { ) {
val actionResult = runCatching { action(command) } val actionResult = runCatching { action(command) }
if (actionResult.isFailure) { if (actionResult.isFailure) {
logger.atWarn { logger.warn(actionResult.exceptionOrNull()) { "Compute command FAILED" }
message = "Compute command FAILED: $command"
payload = mapOf("command" to command)
cause = actionResult.exceptionOrNull()
}
} else if (actionResult.isSuccess) { } else if (actionResult.isSuccess) {
logger.atInfo { logger.info { "Compute command SUCCESS" }
message = "Compute command SUCCESS: $command"
payload = mapOf("command" to command)
}
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package eventDemo.libs.event package eventDemo.libs.event
import io.github.oshai.kotlinlogging.withLoggingContext
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class EventBusInMemory<E : Event<ID>, ID : AggregateId> : EventBus<E, ID> { class EventBusInMemory<E : Event<ID>, ID : AggregateId> : EventBus<E, ID> {
@@ -10,7 +11,9 @@ class EventBusInMemory<E : Event<ID>, ID : AggregateId> : EventBus<E, ID> {
.sortedByDescending { (priority, _) -> priority } .sortedByDescending { (priority, _) -> priority }
.forEach { (_, block) -> .forEach { (_, block) ->
runBlocking { runBlocking {
block(event) withLoggingContext("event" to event.toString()) {
block(event)
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package eventDemo.libs.event package eventDemo.libs.event
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
import java.util.Queue import java.util.Queue
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
@@ -16,15 +17,16 @@ class EventStreamInMemory<E : Event<*>> : EventStream<E> {
override fun publish(event: E) { override fun publish(event: E) {
if (events.none { it.eventId == event.eventId }) { if (events.none { it.eventId == event.eventId }) {
events.add(event) events.add(event)
logger.atInfo { logger.info { "Event published" }
message = "Event published: $event"
payload = mapOf("event" to event)
}
} }
} }
override fun publish(vararg events: E) { override fun publish(vararg events: E) {
events.forEach { publish(it) } events.forEach {
withLoggingContext("event" to it.toString()) {
publish(it)
}
}
} }
override fun readAll(): Set<E> = override fun readAll(): Set<E> =

View File

@@ -1,6 +1,7 @@
package eventDemo.libs.event package eventDemo.libs.event
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.oshai.kotlinlogging.withLoggingContext
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@@ -9,9 +10,11 @@ class VersionBuilderLocal : VersionBuilder {
private val versions: ConcurrentHashMap<AggregateId, AtomicInteger> = ConcurrentHashMap() private val versions: ConcurrentHashMap<AggregateId, AtomicInteger> = ConcurrentHashMap()
override fun buildNextVersion(aggregateId: AggregateId): Int = override fun buildNextVersion(aggregateId: AggregateId): Int =
versionOfAggregate(aggregateId) withLoggingContext("aggregateId" to aggregateId.toString()) {
.addAndGet(1) versionOfAggregate(aggregateId)
.also { logger.debug { "New version $it" } } .addAndGet(1)
.also { logger.debug { "New event version $it" } }
}
override fun getLastVersion(aggregateId: AggregateId): Int = override fun getLastVersion(aggregateId: AggregateId): Int =
versionOfAggregate(aggregateId).toInt() versionOfAggregate(aggregateId).toInt()

View File

@@ -1,7 +1,7 @@
<configuration> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n > MDC=%mdc%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="trace"> <root level="trace">