package com.example.client.util; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.Date; /** * Lightweight local logging for a standalone Java desktop client. * Change APP_NAME and package before use. */ public final class AppLog { private static final String APP_NAME = "client"; private static final Object LOCK = new Object(); private static final SimpleDateFormat TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); private static PrintStream out; private static volatile String exitReason; private static volatile String uncaughtThread; private static volatile Throwable uncaughtException; private AppLog() { } public static void init() { synchronized (LOCK) { if (out != null) return; try { File code = new File(AppLog.class.getProtectionDomain().getCodeSource().getLocation().toURI()); File base = code.isFile() ? code.getParentFile() : new File(System.getProperty("user.dir")); File dir = new File(base, "logs"); if (!dir.exists() && !dir.mkdirs()) return; String day = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); out = new PrintStream(new FileOutputStream(new File(dir, APP_NAME + "-" + day + ".log"), true), true, "UTF-8"); Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread thread, Throwable exception) { exitReason = "UNCAUGHT_EXCEPTION"; uncaughtThread = thread.getName(); uncaughtException = exception; } }); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { if ("USER_CLOSE".equals(exitReason)) { log("INFO", "Client exited, reason=user-close", null); } else if ("UNCAUGHT_EXCEPTION".equals(exitReason)) { log("ERROR", "Client exited, reason=uncaught-exception, thread=" + clean(uncaughtThread), uncaughtException); } } }, APP_NAME + "-log-shutdown")); } catch (Exception ignored) { // Logging is best-effort and must not stop client startup. } } } public static void login(String userId, String method, boolean success) { log(success ? "INFO" : "WARN", "Login " + (success ? "succeeded" : "failed") + ", method=" + clean(method) + ", user=" + clean(userId), null); } public static void scan(String type, String code) { log("INFO", "Scan, type=" + clean(type) + ", code=" + clean(code), null); } public static void work(String action, String code, boolean success) { log(success ? "INFO" : "WARN", "Operation " + (success ? "succeeded" : "failed") + ", action=" + clean(action) + ", code=" + clean(code), null); } public static void markUserClose() { exitReason = "USER_CLOSE"; } private static String clean(String value) { return value == null ? "" : value.replace('\r', ' ').replace('\n', ' '); } private static void log(String level, String message, Throwable exception) { synchronized (LOCK) { if (out == null) return; out.println("[" + TIME.format(new Date()) + "][" + level + "][" + Thread.currentThread().getName() + "] " + message); if (exception != null) exception.printStackTrace(out); out.flush(); } } }