使用Java Logger时锁定文件

时间:2014-02-11 20:33:06

标签: java logging locking java.util.logging

我正在创建一个记录器,它将记录整个程序中的内容。它似乎工作正常。这就是班级的样子。

public class TESTLogger {

  protected static File file;
  protected static Logger logger = Logger.getLogger("");

  public TESTLogger(String logName) {
      setupLogger(logName);
  }

  private static void setupLogger(String logName) {
      String basePath  = Utils.getBasePath();

      File logsDir = new File(basePath);
      if(logsDir.exists() == false) {
          logsDir.mkdir();
      }
      String filePath = basePath + File.separator + logName + ".%g.log";
      file = new File(filePath);
      try {
          FileHandler fileHandler = new FileHandler(filePath, 5242880, 5, true);
          fileHandler.setFormatter(new java.util.logging.Formatter() {
              @Override
              public String format(LogRecord logRecord) {
                  if(logRecord.getLevel() == Level.INFO) {
                      return "[INFO  " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                  } else if(logRecord.getLevel() == Level.WARNING) {
                    return "[WARN  " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                  } else if(logRecord.getLevel() == Level.SEVERE) {
                    return "[ERROR " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                  } else {
                    return "[OTHER " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                }

              }
          });
          logger.addHandler(fileHandler);
      } catch (IOException e) {
      }
  }

  private static void writeToFile(Level level, String writeThisToFile) {
      logger.log(level, writeThisToFile);
  }

  private static String createDateTimeLog() {
      String dateTime = "";
      Date date = new Date();
      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd H:mm:ss");
      dateTime = simpleDateFormat.format(date);
      return dateTime;
  }

  public void error(String message) {
      writeToFile(Level.SEVERE, message);
  }

  public void warn(String message) {
      writeToFile(Level.WARNING, message);
  }

  public void info(String message) {
      writeToFile(Level.INFO, message);
  }
}

当我的应用程序启动时,它会创建TESTLogger对象。然后每当我登录时,我都会使用我的日志消息运行logger.info / logger.warn / logger.error。这很有效。但是,我的jar的多个实例可以同时运行。发生这种情况时,它会创建一个新的日志实例。 IE:我可以拥有myLog.0.log。当jar的第二个实例记录它时,它将在myLog.0.log.1下,然后是myLog.0.log.2,依此类推。

我不想创建我的日志文件的所有这些不同实例。我以为我可能会使用File Lock(来自java.nio.channels包)。但是,我无法弄清楚如何使用我正在使用的Java Logger类(java.util.logging)。

如何防止这种情况发生的任何想法都会很棒。提前谢谢。

编辑: 好。所以我重写了writeToFile,似乎工作得更好一点。但是,我一次又一次地得到.1日志。它没有像过去那样发生。它永远不会达到.2(它曾经一直到达.100)。我仍然希望阻止这个.1,但是。

这就是我现在的代码:

private static void writeToFile(Level level, String writeThisToFile) {

    try {
        File file = new File("FileLock");
        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
        FileLock lock = null;
        try {
            lock = channel.tryLock(0, Long.MAX_VALUE, true);
            if(lock != null) {
                logger.log(level, writeThisToFile);
            }
        } catch (OverlappingFileLockException e) {

        }
        finally {
            if(lock != null) {
                lock.release();
            }
            channel.close();
        }

    } catch (IOException e) {}
}

编辑#2:目前的样子。

进入我的JAR的入口点:

public class StartingPoint {

  public static void main(String[] args) {
    MyLogger logger = new MyLogger("myFirstLogger");
    logger.info("Info test message");
    logger.warn("Warning test message");
    logger.error("Error test message");
  }
}

MyLogger类:

public class MyLogger {

  protected static File file;
  protected static Logger logger = Logger.getLogger("");

  public MyLogger(String loggerName) {
    setupLogger(loggerName);
  }

  private void setupLogger(String loggerName) {

    String filePath = loggerName + "_%g" + ".log";
    file = new File(filePath);
    try {
        FileHandler fileHandler = new FileHandler(filePath, 5242880, 5, true);
        fileHandler.setFormatter(new java.util.logging.Formatter() {
            @Override
            public String format(LogRecord logRecord) {
                if(logRecord.getLevel() == Level.INFO) {
                    return "[INFO  " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                } else if(logRecord.getLevel() == Level.WARNING) {
                    return "[WARN  " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                } else if(logRecord.getLevel() == Level.SEVERE) {
                    return "[ERROR " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                } else {
                    return "[OTHER " + createDateTimeLog() + "]  " + logRecord.getMessage() + "\r\n";
                }

            }
        });
        logger.addHandler(fileHandler);
        logger.addHandler(new SharedFileHandler());  // <--- SharedFileHandler added

    } catch (IOException e) {}
  }

  private void writeToFile(Level level, String writeThisToFile) {
    logger.log(level, writeThisToFile);
  }

  private static String createDateTimeLog() {
    String dateTime = "";
    Date date = new Date();
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd H:mm:ss");
    dateTime = simpleDateFormat.format(date);
    return dateTime;
  }

  public void error(String message) {
    writeToFile(Level.SEVERE, message);
  }

  public void warn(String message) {
    writeToFile(Level.WARNING, message);
  }

  public void info(String message) {
    writeToFile(Level.INFO, message);
  }
}

最后...... SharedFileHandler:

public class SharedFileHandler extends Handler {
  private final FileChannel mutex;
  private final String pattern;

  public SharedFileHandler() throws IOException {
    this("loggerLockFile");
  }

  public SharedFileHandler(String pattern) throws IOException {
    setFormatter(new SimpleFormatter());
    this.pattern = pattern;
    mutex = new RandomAccessFile(pattern, "rw").getChannel();
  }

  @Override
  public void publish(LogRecord record) {
    if (isLoggable(record)) {
        record.getSourceMethodName(); //Infer caller.
        try {
            FileLock ticket = mutex.lock();
            try {
                doPublish(record);
            } finally {
                ticket.release();
            }
        } catch (IOException e) {}
        catch (OverlappingFileLockException e) {}
        catch (NullPointerException e) {}
    }
  }

  private void doPublish(LogRecord record) throws IOException {
    final FileHandler h = new FileHandler(pattern, 5242880, 5, true);
    try {
        h.setEncoding(getEncoding());
        h.setErrorManager(getErrorManager());
        h.setFilter(getFilter());
        h.setFormatter(getFormatter());
        h.setLevel(getLevel());
        h.publish(record);
        h.flush();
    } finally {
        h.close();
    }
  }

  @Override
  public void flush() {}

  @Override
  public synchronized void close() throws SecurityException {
    super.setLevel(Level.OFF);
    try {
        mutex.close();
    } catch (IOException ioe) {}
  }
}

1 个答案:

答案 0 :(得分:1)

FileHandler尽其所能阻止两个并发运行的JVM写入同一个日志文件。如果允许这种行为,则几乎不可能阅读和理解日志文件。

如果您真的想将所有内容写入一个日志文件,则必须执行以下操作之一:

  1. 通过更改启动方式阻止并发JVM进程启动。
  2. 让代码检测另一个JVM是否正在运行您的代码并在创建FileHandler之前退出。
  3. 让每个JVM写入不同的日志文件并创建代码以安全地将文件合并为一个。
  4. 创建一个代理处理程序,为每个日志记录创建和关闭FileHandler。代理处理程序将使用预定义的文件名(与日志文件不同)和FileLock来序列化对来自不同JVM的日志文件的访问。
  5. 使用专用进程写入日志文件,让所有JVM向该进程发送日志消息。
  6. 这是一个未经测试的代理处理程序示例:

    import java.io.IOException;
    import java.nio.channels.FileChannel;
    import java.nio.channels.FileLock;
    import java.nio.channels.OverlappingFileLockException;
    import java.nio.file.Paths;
    import java.util.logging.*;
    import static java.nio.file.StandardOpenOption.*;
    
    public class SharedFileHandler extends Handler {
        private final FileChannel mutex;
        private final String pattern;
    
        public SharedFileHandler() throws IOException {
            this("%hjava%g.log");
        }
    
        public SharedFileHandler(String pattern) throws IOException {
            setFormatter(new SimpleFormatter());
            this.pattern = pattern;
            Path p = Paths.get(new File(".").getCanonicalPath(), 
                pattern.replace("%", "") + ".lck");
            mutex = FileChannel.open(p, CREATE, WRITE, DELETE_ON_CLOSE);
        }
    
        @Override
        public void publish(LogRecord record) {
            if (isLoggable(record)) {
                record.getSourceMethodName(); //Infer caller.
                try {
                    FileLock ticket = mutex.lock();
                    try {
                        doPublish(ticket, record);
                    } finally {
                        ticket.release();
                    }
                } catch (IOException | OverlappingFileLockException ex) {
                    reportError(null, ex, ErrorManager.WRITE_FAILURE);
                }
            }
        }
    
        private synchronized void doPublish(FileLock ticket, LogRecord record) throws IOException {
            if (!ticket.isValid()) {
               return;
            }
            final FileHandler h = new FileHandler(pattern, 5242880, 5, true);
            try {
                h.setEncoding(getEncoding());
                h.setErrorManager(getErrorManager());
                h.setFilter((Filter) null);
                h.setFormatter(getFormatter());
                h.setLevel(getLevel());
                h.publish(record);
                h.flush();
            } finally {
                h.close();
            }
        }
    
        @Override
        public void flush() {
        }
    
        @Override
        public synchronized void close() throws SecurityException {
            super.setLevel(Level.OFF);
            try {
                 mutex.close();
            } catch (IOException ioe) {
                this.reportError(null, ioe, ErrorManager.CLOSE_FAILURE);
            }
        }
    }
    

    这是一个简单的测试用例

    public static void main(String[] args) throws Exception {
        Random rnd = new Random();
        logger.addHandler(new SharedFileHandler());
        String id = ManagementFactory.getRuntimeMXBean().getName();
        for (int i = 0; i < 600; i++) {
            logger.log(Level.INFO, id);
            Thread.sleep(rnd.nextInt(100));
        }
    }