HyCodeYourTale

Logging

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ě |

Last updated: 20. ledna 2026