AppLog.java 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. package com.example.client.util;
  2. import java.io.File;
  3. import java.io.FileOutputStream;
  4. import java.io.PrintStream;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Date;
  7. /**
  8. * Lightweight local logging for a standalone Java desktop client.
  9. * Change APP_NAME and package before use.
  10. */
  11. public final class AppLog {
  12. private static final String APP_NAME = "client";
  13. private static final Object LOCK = new Object();
  14. private static final SimpleDateFormat TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
  15. private static PrintStream out;
  16. private static volatile String exitReason;
  17. private static volatile String uncaughtThread;
  18. private static volatile Throwable uncaughtException;
  19. private AppLog() { }
  20. public static void init() {
  21. synchronized (LOCK) {
  22. if (out != null) return;
  23. try {
  24. File code = new File(AppLog.class.getProtectionDomain().getCodeSource().getLocation().toURI());
  25. File base = code.isFile() ? code.getParentFile() : new File(System.getProperty("user.dir"));
  26. File dir = new File(base, "logs");
  27. if (!dir.exists() && !dir.mkdirs()) return;
  28. String day = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
  29. out = new PrintStream(new FileOutputStream(new File(dir, APP_NAME + "-" + day + ".log"), true), true, "UTF-8");
  30. Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
  31. public void uncaughtException(Thread thread, Throwable exception) {
  32. exitReason = "UNCAUGHT_EXCEPTION";
  33. uncaughtThread = thread.getName();
  34. uncaughtException = exception;
  35. }
  36. });
  37. Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
  38. public void run() {
  39. if ("USER_CLOSE".equals(exitReason)) {
  40. log("INFO", "Client exited, reason=user-close", null);
  41. } else if ("UNCAUGHT_EXCEPTION".equals(exitReason)) {
  42. log("ERROR", "Client exited, reason=uncaught-exception, thread=" + clean(uncaughtThread), uncaughtException);
  43. }
  44. }
  45. }, APP_NAME + "-log-shutdown"));
  46. } catch (Exception ignored) {
  47. // Logging is best-effort and must not stop client startup.
  48. }
  49. }
  50. }
  51. public static void login(String userId, String method, boolean success) {
  52. log(success ? "INFO" : "WARN", "Login " + (success ? "succeeded" : "failed")
  53. + ", method=" + clean(method) + ", user=" + clean(userId), null);
  54. }
  55. public static void scan(String type, String code) {
  56. log("INFO", "Scan, type=" + clean(type) + ", code=" + clean(code), null);
  57. }
  58. public static void work(String action, String code, boolean success) {
  59. log(success ? "INFO" : "WARN", "Operation " + (success ? "succeeded" : "failed")
  60. + ", action=" + clean(action) + ", code=" + clean(code), null);
  61. }
  62. public static void markUserClose() {
  63. exitReason = "USER_CLOSE";
  64. }
  65. private static String clean(String value) {
  66. return value == null ? "" : value.replace('\r', ' ').replace('\n', ' ');
  67. }
  68. private static void log(String level, String message, Throwable exception) {
  69. synchronized (LOCK) {
  70. if (out == null) return;
  71. out.println("[" + TIME.format(new Date()) + "][" + level + "]["
  72. + Thread.currentThread().getName() + "] " + message);
  73. if (exception != null) exception.printStackTrace(out);
  74. out.flush();
  75. }
  76. }
  77. }