Logging
Detailní dokumentace k logovacímu systému Hytale.
---
Přehled Architektury
Logging System
│
├── HytaleLogger (Flogger API)
│ ├── forEnclosingClass() - Logger pro aktuální třídu
│ ├── get(name) - Pojmenovaný logger
│ └── getLogger() - Root logger
│
├── HytaleLoggerBackend
│ ├── Level management
│ ├── Sentry integration
│ └── Log subscribers
│
└── Output Handlers
├── HytaleConsole - Konzole
├── HytaleFileHandler - Log soubory
└── HytaleSentryHandler - Sentry error tracking
---
HytaleLogger
Hlavní třída pro logování využívající Google Flogger:
public class HytaleLogger extends AbstractLogger {
private static final Map CACHE = new ConcurrentHashMap<>();
private static final HytaleLogger LOGGER; // Root logger // Získání loggeru
public static HytaleLogger forEnclosingClass() { ... }
public static HytaleLogger get(String loggerName) { ... }
public static HytaleLogger getLogger() { ... }
// Logging API
public HytaleLogger.Api at(@Nonnull Level level) { ... }
// Level management
public Level getLevel() { ... }
public void setLevel(@Nonnull Level level) { ... }
// Sub-logger
public HytaleLogger getSubLogger(String name) { ... }
// Sentry integration
public void setSentryClient(@Nonnull IScopes scope) { ... }
}
---
Získání Loggeru
V JavaPlugin
public class MyPlugin extends JavaPlugin { @Override
protected void setup() {
// Automaticky dostupný přes getLogger()
getLogger().atInfo().log("Plugin setup starting");
}
}
Statický Logger
public class MyService {
// Logger pro aktuální třídu
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass(); public void doSomething() {
LOGGER.atInfo().log("Doing something");
}
}
Pojmenovaný Logger
// Logger s konkrétním názvem
HytaleLogger dbLogger = HytaleLogger.get("Database");
dbLogger.atInfo().log("Connected to database");// Sub-logger
HytaleLogger queryLogger = dbLogger.getSubLogger("Query");
queryLogger.atInfo().log("Executing query...");
// Výstup: [Database][Query] Executing query...
---
Log Levels
Standardní Úrovně
import java.util.logging.Level;// Od nejvyšší priority
Level.SEVERE // Kritické chyby
Level.WARNING // Varování
Level.INFO // Informace
Level.CONFIG // Konfigurační informace
Level.FINE // Debug
Level.FINER // Detailnější debug
Level.FINEST // Nejdetailnější debug
Level.ALL // Vše
Level.OFF // Nic
Použití Úrovní
| Level | Kdy použít | Příklad |
|-------|------------|---------|
| SEVERE | Kritické chyby, nelze pokračovat | Database connection failed |
| WARNING | Problémy, ale lze pokračovat | Config not found, using defaults |
| INFO | Důležité informace | Plugin started, Player joined |
| FINE | Debug informace | Processing entity X |
| FINER | Detailní debug | Step 3 of algorithm |
| FINEST | Maximální detail | Variable value: X |
---
Logging API
Základní Použití
// Jednoduchá zpráva
getLogger().atInfo().log("Plugin started");// S parametry (printf formát)
getLogger().atInfo().log("Player %s joined world %s", playerName, worldName);
getLogger().atInfo().log("Loaded %d warps in %d ms", warpCount, loadTime);
// Čísla s formátováním
getLogger().atInfo().log("Progress: %.2f%%", progress * 100);
S Výjimkou
try {
loadConfig();
} catch (Exception e) {
// withCause() přidá stack trace
getLogger().at(Level.SEVERE).withCause(e).log("Failed to load config:");
}// Zkrácená forma
getLogger().at(Level.WARNING).withCause(exception).log("Error occurred:");
Podmíněné Logování
// Logger automaticky ignoruje log pokud level není aktivní
getLogger().at(Level.FINE).log("Debug: %s", expensiveOperation());// Explicitní kontrola
if (getLogger().getLevel().intValue() <= Level.FINE.intValue()) {
String debugInfo = computeExpensiveDebugInfo();
getLogger().at(Level.FINE).log("Debug: %s", debugInfo);
}
---
HytaleLoggerBackend
Backend zodpovědný za zpracování logů:
public class HytaleLoggerBackend extends LoggerBackend {
public static final PrintStream REAL_SOUT = System.out; // Originální System.out
public static final PrintStream REAL_SERR = System.err; // Originální System.err // Level management
public Level getLevel() { ... }
public void setLevel(@Nonnull Level newLevel) { ... }
public boolean isLoggable(@Nonnull Level lvl) { ... }
// Logging
public void log(@Nonnull LogData data) { ... }
public void log(@Nonnull LogRecord logRecord) { ... }
// Sub-logger
public HytaleLoggerBackend getSubLogger(String name) { ... }
// Level loading
public void loadLogLevel() { ... }
public static void loadLevels(@Nonnull List> list) { ... }
public static void reloadLogLevels() { ... }
// Subscribers
public static void subscribe(CopyOnWriteArrayList subscriber) { ... }
public static void unsubscribe(CopyOnWriteArrayList subscriber) { ... }
}
Level Management
// Nastavení úrovně loggeru
HytaleLogger logger = HytaleLogger.get("MyPlugin");
logger.setLevel(Level.FINE); // Povolit debug logy// Reload všech log levels z konfigurace
HytaleLoggerBackend.reloadLogLevels();
// Hromadné nastavení
List> levels = List.of(
Map.entry("Database", Level.WARNING),
Map.entry("Network", Level.FINE)
);
HytaleLoggerBackend.loadLevels(levels);
Log Subscribers
// Přihlášení k odběru logů
CopyOnWriteArrayList myLogs = new CopyOnWriteArrayList<>();
HytaleLoggerBackend.subscribe(myLogs);// Zpracování logů
for (LogRecord record : myLogs) {
String message = record.getMessage();
Level level = record.getLevel();
// Zpracuj log...
}
// Odhlášení
HytaleLoggerBackend.unsubscribe(myLogs);
---
Sentry Integration
Hytale podporuje Sentry pro error tracking:
// Nastavení Sentry klienta
HytaleLogger logger = HytaleLogger.get("MyPlugin");
logger.setSentryClient(sentryScope);// Kontrola propagace k parent loggeru
logger.setPropagatesSentryToParent(false);
// Chyby s Level.SEVERE se automaticky posílají do Sentry
getLogger().at(Level.SEVERE).withCause(exception).log("Critical error:");
SkipSentryException
// Výjimka která se nepošle do Sentry
public class SkipSentryException {
public static boolean hasSkipSentry(Throwable throwable) {
// Kontroluje zda výjimka má skip sentry flag
}
}
---
Uncaught Exception Handler
Hytale automaticky zachytává nezachycené výjimky:
public class HytaleUncaughtExceptionHandler implements UncaughtExceptionHandler {
public static final HytaleUncaughtExceptionHandler INSTANCE = new HytaleUncaughtExceptionHandler(); public static void setup() {
Thread.setDefaultUncaughtExceptionHandler(INSTANCE);
System.setProperty(
"java.util.concurrent.ForkJoinPool.common.exceptionHandler",
HytaleUncaughtExceptionHandler.class.getName()
);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
HytaleLogger.getLogger().at(Level.SEVERE).withCause(e)
.log("Exception in thread: %s", t);
}
}
---
Vzory pro Pluginy
Plugin Logger Wrapper
public class MyPlugin extends JavaPlugin { // Debug mode z konfigurace
private boolean debugMode = false;
public void debug(String message, Object... args) {
if (debugMode) {
getLogger().at(Level.FINE).log(message, args);
}
}
public void info(String message, Object... args) {
getLogger().atInfo().log(message, args);
}
public void warn(String message, Object... args) {
getLogger().atWarning().log(message, args);
}
public void error(String message, Throwable cause) {
getLogger().at(Level.SEVERE).withCause(cause).log(message);
}
public void setDebugMode(boolean enabled) {
this.debugMode = enabled;
if (enabled) {
getLogger().setLevel(Level.FINE);
} else {
getLogger().setLevel(Level.INFO);
}
}
}
Service Logger
public class DatabaseService {
private final HytaleLogger logger; public DatabaseService(JavaPlugin plugin) {
this.logger = plugin.getLogger().getSubLogger("Database");
}
public void connect() {
logger.atInfo().log("Connecting to database...");
try {
// ...
logger.atInfo().log("Connected successfully");
} catch (Exception e) {
logger.at(Level.SEVERE).withCause(e).log("Connection failed:");
throw e;
}
}
public void query(String sql) {
logger.at(Level.FINE).log("Executing: %s", sql);
}
}
Timed Operation Logging
public void measureAndLog(String operation, Runnable task) {
getLogger().at(Level.FINE).log("Starting: %s", operation);
long start = System.nanoTime(); try {
task.run();
long elapsed = System.nanoTime() - start;
getLogger().atInfo().log("%s completed in %.2f ms", operation, elapsed / 1_000_000.0);
} catch (Exception e) {
long elapsed = System.nanoTime() - start;
getLogger().at(Level.SEVERE).withCause(e)
.log("%s failed after %.2f ms:", operation, elapsed / 1_000_000.0);
throw e;
}
}
---
Formátování Výstupu
HytaleLogFormatter
Formátuje logy pro konzoli a soubory:
// Výstup formát:
// [HH:mm:ss] [LEVEL] [ModuleName] Message
// [14:23:45] [INFO] [MyPlugin] Plugin started
// [14:23:46] [WARNING] [MyPlugin][Database] Connection slow// Nastavení maximální délky názvu modulu
HytaleLoggerBackend.setIndent(20);
Raw Log
// Log bez formátování
HytaleLoggerBackend.rawLog("=".repeat(50));
HytaleLoggerBackend.rawLog("Server Starting...");
HytaleLoggerBackend.rawLog("=".repeat(50));
---
Inicializace
Automatická Inicializace
public class HytaleLogger {
static {
// Nastavení log manageru
System.setProperty("java.util.logging.manager", HytaleLogManager.class.getName()); // Setup uncaught exception handleru
HytaleUncaughtExceptionHandler.setup();
// Inicializace cache a root loggeru
CACHE = new ConcurrentHashMap<>();
LOGGER = new HytaleLogger(HytaleLoggerBackend.getLogger());
NO_OP = new HytaleLogger.NoOp();
}
public static void init() {
// Inicializuje file handler a console
HytaleFileHandler fileHandler = HytaleFileHandler.INSTANCE;
HytaleConsole console = HytaleConsole.INSTANCE;
LOGGER.at(Level.INFO).log("Logger Initialized");
}
public static void replaceStd() {
// Přesměruje System.out/err na logger
System.setOut(new LoggerPrintStream(get("SOUT"), Level.INFO));
System.setErr(new LoggerPrintStream(get("SERR"), Level.SEVERE));
}
}
---
Best Practices
1. Používej Správné Úrovně
// ŠPATNĚ - vše na INFO
getLogger().atInfo().log("Debug: processing entity");
getLogger().atInfo().log("Error occurred");// SPRÁVNĚ
getLogger().at(Level.FINE).log("Debug: processing entity");
getLogger().at(Level.SEVERE).log("Error occurred");
2. Vždy Loguj Výjimky s withCause()
// ŠPATNĚ - stack trace se ztratí
getLogger().at(Level.SEVERE).log("Error: " + e.getMessage());// SPRÁVNĚ
getLogger().at(Level.SEVERE).withCause(e).log("Error occurred:");
3. Používej Parametry místo Konkatenace
// ŠPATNĚ - konkatenace se provede vždy
getLogger().at(Level.FINE).log("Player " + player.getName() + " at " + position);// SPRÁVNĚ - parametry se zpracují jen pokud se loguje
getLogger().at(Level.FINE).log("Player %s at %s", player.getName(), position);
4. Pojmenuj Loggery Smysluplně
// Sub-loggery pro různé části pluginu
HytaleLogger dbLogger = getLogger().getSubLogger("Database");
HytaleLogger apiLogger = getLogger().getSubLogger("API");
HytaleLogger cacheLogger = getLogger().getSubLogger("Cache");// Výstup bude přehledný
// [MyPlugin][Database] Connecting...
// [MyPlugin][API] Request received
// [MyPlugin][Cache] Hit rate: 85%
---
Shrnutí
| Třída | Účel |
|-------|------|
| HytaleLogger | Hlavní logging API |
| HytaleLoggerBackend | Backend pro zpracování logů |
| HytaleConsole | Výstup do konzole |
| HytaleFileHandler | Výstup do souborů |
| HytaleSentryHandler | Sentry error tracking |
| HytaleUncaughtExceptionHandler | Zachycení nezachycených výjimek |
| Metoda | Použití |
|--------|---------|
| forEnclosingClass() | Statický logger pro třídu |
| get(name) | Pojmenovaný logger |
| getLogger() | Root logger |
| at(Level) | Začátek log řetězce |
| withCause(e) | Přidání výjimky |
| log(format, args) | Zapsání logu |
| getSubLogger(name) | Vytvoření sub-loggeru |
| setLevel(level) | Nastavení úrovně |