feat: configure logging and add OpenTelemetry support (#49)
Build & Test (NowChessSystems) TeamCity build failed

Reviewed-on: #49
This commit was merged in pull request #49.
This commit is contained in:
2026-05-10 20:31:48 +02:00
parent 989ac312d9
commit d57c488661
13 changed files with 344 additions and 98 deletions
@@ -1,6 +1,7 @@
package de.nowchess.ws.resource
import de.nowchess.ws.config.RedisConfig
import io.micrometer.core.instrument.{Counter, Gauge, MeterRegistry}
import io.quarkus.redis.datasource.RedisDataSource
import io.quarkus.redis.datasource.pubsub.PubSubCommands
import io.quarkus.websockets.next.*
@@ -26,10 +27,27 @@ class GameWebSocketResource:
@Inject
var jwtParser: JWTParser = uninitialized
@Inject
var meterRegistry: MeterRegistry = uninitialized
// scalafix:on DisableSyntax.var
private val connections = new ConcurrentHashMap[String, ConnectionMeta]()
private lazy val connectionsOpened: Counter =
meterRegistry.counter("nowchess.ws.connections.opened")
private lazy val connectionsClosed: Counter =
meterRegistry.counter("nowchess.ws.connections.closed")
private lazy val messagesReceived: Counter =
meterRegistry.counter("nowchess.ws.messages.received")
private lazy val activeGauge: Unit =
Gauge
.builder("nowchess.ws.connections.active", connections, _.size().toDouble)
.register(meterRegistry)
private def s2cTopic(gameId: String): String =
s"${redisConfig.prefix}:game:$gameId:s2c"
@@ -38,22 +56,19 @@ class GameWebSocketResource:
@OnOpen
def onOpen(connection: WebSocketConnection, handshake: HandshakeRequest): Unit =
val gameId = connection.pathParam("gameId")
val playerId = Option(handshake.header("Authorization"))
.filter(_.nonEmpty)
.flatMap(token => Try(jwtParser.parse(token)).toOption)
.map(_.getSubject)
activeGauge
val gameId = connection.pathParam("gameId")
val playerId = resolvePlayerId(handshake)
log.infof("Game WebSocket opened — gameId=%s playerId=%s", gameId, playerId.getOrElse("anonymous"))
val handler: Consumer[String] = msg => connection.sendText(msg).subscribe().`with`(_ => (), _ => ())
val subscriber = redis.pubsub(classOf[String]).subscribe(s2cTopic(gameId), handler)
connections.put(connection.id(), ConnectionMeta(gameId, subscriber, playerId))
val connectedMsg = playerId match
case Some(pid) => s"""{"type":"CONNECTED","gameId":"$gameId","playerId":"$pid"}"""
case None => s"""{"type":"CONNECTED","gameId":"$gameId"}"""
redis.pubsub(classOf[String]).publish(c2sTopic(gameId), connectedMsg)
connectionsOpened.increment()
publishConnected(gameId, playerId)
@OnTextMessage
def onTextMessage(connection: WebSocketConnection, message: String): Unit =
messagesReceived.increment()
Option(connections.get(connection.id())).foreach { meta =>
val enriched = meta.playerId match
case Some(pid) => injectPlayerId(message, pid)
@@ -66,8 +81,21 @@ class GameWebSocketResource:
Option(connections.remove(connection.id())).foreach { meta =>
log.infof("Game WebSocket closed — gameId=%s", meta.gameId)
meta.subscriber.unsubscribe(s2cTopic(meta.gameId))
connectionsClosed.increment()
}
private def resolvePlayerId(handshake: HandshakeRequest): Option[String] =
Option(handshake.header("Authorization"))
.filter(_.nonEmpty)
.flatMap(token => Try(jwtParser.parse(token)).toOption)
.map(_.getSubject)
private def publishConnected(gameId: String, playerId: Option[String]): Unit =
val connectedMsg = playerId match
case Some(pid) => s"""{"type":"CONNECTED","gameId":"$gameId","playerId":"$pid"}"""
case None => s"""{"type":"CONNECTED","gameId":"$gameId"}"""
redis.pubsub(classOf[String]).publish(c2sTopic(gameId), connectedMsg)
private def injectPlayerId(msg: String, pid: String): String =
val trimmed = msg.trim
if trimmed.endsWith("}") then trimmed.dropRight(1) + s""","playerId":"$pid"}"""