feat: log through SLF4J and report errors to Sentry - #208
Open
TheMeinerLP wants to merge 1 commit into
Open
Conversation
Titan shipped without an SLF4J binding. slf4j-api arrived transitively
through Minestom, no provider was ever declared, and the fat jars answered
every log call with "No SLF4J providers were found" and dropped it. On top
of that, Minestom's default ExceptionManager handler is
Throwable::printStackTrace, so a listener that threw left a bare stack
trace on stdout and nothing else. A production lobby was effectively
running blind.
Logging
- logback-classic as runtimeOnly in :app and :setup, slf4j-api declared
explicitly where code compiles against it
- one logback.xml in :common, so both fat jars get the same configuration
without copying it
- TitanObservability.installExceptionHandler() replaces Minestom's
printStackTrace default, so exceptions reach the log like everything else
Sentry
- optional, exactly as Sturnus does it: no TITAN_SENTRY_DSN means
Sentry.init is never called and the process behaves as it does today.
One jar serves operators with and without a Sentry instance.
- release comes from the jar's Implementation-Version manifest attribute
(added in titan.java-conventions), so an issue points at a deploy
- environment from TITAN_SENTRY_ENVIRONMENT
- reporting has exactly one entry point, the logback appender at ERROR.
No second path that could double-report or drift out of sync.
Player attribution
EventNodeImpl catches what a listener throws and hands it to the exception
manager one frame up, on the same thread. TitanObservability.guard() sits
inside that frame and records the failing event's player, which the handler
puts into the MDC as player.uuid / player.name. Recording happens in a
catch block only - a listener that returns normally pays for an entered try
and nothing else, which matters because the guarded set includes
PlayerMoveEvent and PlayerPacketEvent.
sendDefaultPii stays false: the SDK's PII defaults are request headers and
IP addresses, which say nothing about a Minestom crash. The player identity
that does is attached deliberately.
Verified against the real fat jar. Without a DSN:
INFO n.o.t.c.o.TitanObservability - Sentry reporting disabled - ...
INFO luckperms - Loading configuration...
ERROR n.o.t.c.o.TitanObservability - Unhandled exception
java.nio.file.NoSuchFileException: worlds
With TITAN_SENTRY_DSN and TITAN_SENTRY_ENVIRONMENT set:
INFO n.o.t.c.o.TitanObservability - Sentry reporting enabled -
release 1.14.0, environment smoketest
Before this change the same run printed "No SLF4J providers were found"
and a bare stack trace.
The appender is declared with an empty DSN on purpose. It is constructed
when the first logger is created, necessarily before bootstrap() can call
Sentry.init, and an appender that finds no DSN then logs "DSN is required"
at WARN - which makes Logback dump its entire configuration status on every
start.
7 new tests in :common (63 total, 0 failures).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtbnMbAj6m2q3vrDYFizDr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warum
Titan lief in Produktion faktisch blind:
slf4j-apikam nur transitiv über Minestom, ein Provider war nie deklariert. Der Fat Jar antwortete auf jeden Log-Aufruf mitNo SLF4J providers were foundund verwarf ihn.Throwable::printStackTrace. Eine Exception in einem Listener landete roh auf stdout, an SLF4J vorbei.Zusammen hieß das: ein abstürzender Listener hinterließ einen nackten Stacktrace und sonst nichts.
Logging
logback-classicalsruntimeOnlyin:appund:setup,slf4j-apiexplizit deklariertlogback.xmlin:common— beide Fat Jars bündeln:common, also eine Konfiguration ohne KopieTitanObservability.installExceptionHandler()ersetzt MinestomsprintStackTrace-DefaultSentry
Optional, nach dem Muster von Sturnus: ohne
TITAN_SENTRY_DSNwirdSentry.initnie aufgerufen und der Prozess verhält sich exakt wie heute. Ein Jar bedient Betreiber mit und ohne Sentry-Instanz.TITAN_SENTRY_DSNTITAN_SENTRY_ENVIRONMENTproduction,beta, … — DefaultunknownDie Release-Angabe kommt aus dem
Implementation-Version-Manifest-Attribut des Fat Jars (neu intitan.java-conventions), ein Sentry-Issue zeigt also auf einen konkreten Deploy.Reporting hat genau einen Eingang: den Logback-Appender ab
ERROR. Kein zweiter, paralleler Pfad, der doppelt melden oder auseinanderlaufen könnte.Spieler-Zuordnung
Wie besprochen mit UUID und Name.
EventNodeImplfängt ab, was ein Listener wirft, und reicht es einen Frame höher an den ExceptionManager — auf demselben Thread.TitanObservability.guard()sitzt in diesem Frame, merkt sich den Spieler des fehlgeschlagenen Events und wirft weiter; der Handler legt ihn alsplayer.uuid/player.namein den MDC, von wo der Sentry-Appender ihn übernimmt.Kein Kostenpunkt auf dem gesunden Pfad. Aufgezeichnet wird ausschließlich im
catch-Block. Ein Listener, der normal zurückkehrt, zahlt ein betretenestryund sonst nichts — relevant, weil zu den 18 gewrappten ListenernPlayerMoveEventundPlayerPacketEventgehören.sendDefaultPiibleibtfalse: die PII-Defaults des SDK sind Request-Header und IP-Adressen, die über einen Minestom-Absturz nichts aussagen. Die Spieleridentität, die etwas aussagt, wird bewusst gesetzt.Verifikation am echten Fat Jar
Ohne DSN:
Mit DSN:
Vor dieser Änderung gab derselbe Lauf
SLF4J(W): No SLF4J providers were foundaus und danach einen nackten Stacktrace. Die Release-Angabe1.14.0belegt, dass das Manifest-Attribut greift.Tests: 63 gesamt, 0 Fehler — davon 7 neue in
:common:assertSame)Sentry.isEnabled()falsedevzurück statt aufnullEin Detail, das Erklärung braucht
Der Appender wird mit leerem DSN deklariert:
Er wird konstruiert, sobald der erste Logger entsteht — zwangsläufig bevor
bootstrap()Sentry.initaufrufen kann. Ein Appender, der zu diesem Zeitpunkt keinen DSN findet, loggtDSN is requiredauf WARN, und das lässt Logback bei jedem Start seinen kompletten Konfigurations-Status ausgeben (~25 Zeilen). Mit leerem DSN ist der Start sauber;bootstrap()entscheidet ein paar Anweisungen später über die globale SDK-Instanz, ob tatsächlich gesendet wird.Anmerkung
Commit-Typ ist
feat:— anders als #206/#207 erscheint das hier also im Changelog. Das ist beabsichtigt: Betreiber müssen wissen, ab welcher VersionTITAN_SENTRY_DSNgesetzt werden kann.🤖 Generated with Claude Code
https://claude.ai/code/session_01QtbnMbAj6m2q3vrDYFizDr