From e9e5f2cb4f46b4a10d4b3a6da49d457c1870f47d Mon Sep 17 00:00:00 2001 From: David BRASSELY Date: Thu, 19 Dec 2013 14:21:33 +0100 Subject: [PATCH] Add a custom file logger and a custom log manager to be able to use system.env in the log configuration --- .../standalone/logger/FileHandler.java | 400 ++++++++++++++++++ .../standalone/logger/OpenESBLogManager.java | 55 +++ .../src/main/resources/bin/openesb.bat | 2 +- .../src/main/resources/bin/openesb.sh | 1 + .../main/resources/config/logger.properties | 17 +- 5 files changed, 467 insertions(+), 8 deletions(-) create mode 100644 openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/FileHandler.java create mode 100644 openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/OpenESBLogManager.java diff --git a/openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/FileHandler.java b/openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/FileHandler.java new file mode 100644 index 0000000..bb891e8 --- /dev/null +++ b/openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/FileHandler.java @@ -0,0 +1,400 @@ +package net.openesb.standalone.logger; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.sql.Timestamp; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.ErrorManager; +import java.util.logging.Filter; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; + +/** + * Implementation of Handler that appends log messages to a file + * named {prefix}{date}{suffix} in a configured directory. + * + *

The following configuration properties are available:

+ * + * + * + */ + +public class FileHandler + extends Handler { + + + // ------------------------------------------------------------ Constructor + + + public FileHandler() { + this(null, null, null); + } + + + public FileHandler(String directory, String prefix, String suffix) { + this.directory = directory; + this.prefix = prefix; + this.suffix = suffix; + configure(); + openWriter(); + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The as-of date for the currently open log file, or a zero-length + * string if there is no open log file. + */ + private volatile String date = ""; + + + /** + * The directory in which log files are created. + */ + private String directory = null; + + + /** + * The prefix that is added to log file filenames. + */ + private String prefix = null; + + + /** + * The suffix that is added to log file filenames. + */ + private String suffix = null; + + + /** + * Determines whether the logfile is rotatable + */ + private boolean rotatable = true; + + + /** + * The PrintWriter to which we are currently logging, if any. + */ + private volatile PrintWriter writer = null; + + + /** + * Lock used to control access to the writer. + */ + protected ReadWriteLock writerLock = new ReentrantReadWriteLock(); + + + /** + * Log buffer size. + */ + private int bufferSize = -1; + + + // --------------------------------------------------------- Public Methods + + + /** + * Format and publish a LogRecord. + * + * @param record description of the log event + */ + @Override + public void publish(LogRecord record) { + + if (!isLoggable(record)) { + return; + } + + // Construct the timestamp we will use, if requested + Timestamp ts = new Timestamp(System.currentTimeMillis()); + String tsString = ts.toString().substring(0, 19); + String tsDate = tsString.substring(0, 10); + + try { + writerLock.readLock().lock(); + // If the date has changed, switch log files + if (rotatable && !date.equals(tsDate)) { + try { + // Update to writeLock before we switch + writerLock.readLock().unlock(); + writerLock.writeLock().lock(); + + // Make sure another thread hasn't already done this + if (!date.equals(tsDate)) { + closeWriter(); + date = tsDate; + openWriter(); + } + } finally { + writerLock.writeLock().unlock(); + // Down grade to read-lock. This ensures the writer remains valid + // until the log message is written + writerLock.readLock().lock(); + } + } + + String result = null; + try { + result = getFormatter().format(record); + } catch (Exception e) { + reportError(null, e, ErrorManager.FORMAT_FAILURE); + return; + } + + try { + if (writer!=null) { + writer.write(result); + if (bufferSize < 0) { + writer.flush(); + } + } else { + reportError("FileHandler is closed or not yet initialized, unable to log ["+result+"]", null, ErrorManager.WRITE_FAILURE); + } + } catch (Exception e) { + reportError(null, e, ErrorManager.WRITE_FAILURE); + return; + } + } finally { + writerLock.readLock().unlock(); + } + } + + + // -------------------------------------------------------- Private Methods + + + /** + * Close the currently open log file (if any). + */ + @Override + public void close() { + closeWriter(); + } + + protected void closeWriter() { + + writerLock.writeLock().lock(); + try { + if (writer == null) + return; + writer.write(getFormatter().getTail(this)); + writer.flush(); + writer.close(); + writer = null; + date = ""; + } catch (Exception e) { + reportError(null, e, ErrorManager.CLOSE_FAILURE); + } finally { + writerLock.writeLock().unlock(); + } + } + + + /** + * Flush the writer. + */ + @Override + public void flush() { + + writerLock.readLock().lock(); + try { + if (writer == null) + return; + writer.flush(); + } catch (Exception e) { + reportError(null, e, ErrorManager.FLUSH_FAILURE); + } finally { + writerLock.readLock().unlock(); + } + + } + + + /** + * Configure from LogManager properties. + */ + private void configure() { + + Timestamp ts = new Timestamp(System.currentTimeMillis()); + String tsString = ts.toString().substring(0, 19); + date = tsString.substring(0, 10); + + String className = this.getClass().getName(); //allow classes to override + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + // Retrieve configuration of logging file name + rotatable = Boolean.parseBoolean(getProperty(className + ".rotatable", "true")); + if (directory == null) + directory = getProperty(className + ".directory", "logs"); + if (prefix == null) + prefix = getProperty(className + ".prefix", "openesb."); + if (suffix == null) + suffix = getProperty(className + ".suffix", ".log"); + String sBufferSize = getProperty(className + ".bufferSize", String.valueOf(bufferSize)); + try { + bufferSize = Integer.parseInt(sBufferSize); + } catch (NumberFormatException ignore) { + //no op + } + // Get encoding for the logging file + String encoding = getProperty(className + ".encoding", null); + if (encoding != null && encoding.length() > 0) { + try { + setEncoding(encoding); + } catch (UnsupportedEncodingException ex) { + // Ignore + } + } + + // Get logging level for the handler + setLevel(Level.parse(getProperty(className + ".level", "" + Level.ALL))); + + // Get filter configuration + String filterName = getProperty(className + ".filter", null); + if (filterName != null) { + try { + setFilter((Filter) cl.loadClass(filterName).newInstance()); + } catch (Exception e) { + // Ignore + } + } + + // Set formatter + String formatterName = getProperty(className + ".formatter", null); + if (formatterName != null) { + try { + setFormatter((Formatter) cl.loadClass(formatterName).newInstance()); + } catch (Exception e) { + // Ignore and fallback to defaults + setFormatter(new SimpleFormatter()); + } + } else { + setFormatter(new SimpleFormatter()); + } + + // Set error manager + setErrorManager(new ErrorManager()); + + } + + + private String getProperty(String name, String defaultValue) { + String value = LogManager.getLogManager().getProperty(name); + if (value == null) { + value = defaultValue; + } else { + value = value.trim(); + } + return value; + } + + + /** + * Open the new log file for the date specified by date. + */ + protected void open() { + openWriter(); + } + + protected void openWriter() { + + // Create the directory if necessary + File dir = new File(directory); + if (!dir.mkdirs() && !dir.isDirectory()) { + reportError("Unable to create [" + dir + "]", null, + ErrorManager.OPEN_FAILURE); + writer = null; + return; + } + + // Open the current log file + writerLock.writeLock().lock(); + FileOutputStream fos = null; + OutputStream os = null; + try { + File pathname = new File(dir.getAbsoluteFile(), prefix + + (rotatable ? date : "") + suffix); + File parent = pathname.getParentFile(); + if (!parent.mkdirs() && !parent.isDirectory()) { + reportError("Unable to create [" + parent + "]", null, + ErrorManager.OPEN_FAILURE); + writer = null; + return; + } + String encoding = getEncoding(); + fos = new FileOutputStream(pathname, true); + os = bufferSize>0?new BufferedOutputStream(fos,bufferSize):fos; + writer = new PrintWriter( + (encoding != null) ? new OutputStreamWriter(os, encoding) + : new OutputStreamWriter(os), false); + writer.write(getFormatter().getHead(this)); + } catch (Exception e) { + reportError(null, e, ErrorManager.OPEN_FAILURE); + writer = null; + if (fos != null) { + try { + fos.close(); + } catch (IOException e1) { + // Ignore + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e1) { + // Ignore + } + } + } finally { + writerLock.writeLock().unlock(); + } + + } + + +} diff --git a/openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/OpenESBLogManager.java b/openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/OpenESBLogManager.java new file mode 100644 index 0000000..e9f9691 --- /dev/null +++ b/openesb-standalone-bootstrap/src/main/java/net/openesb/standalone/logger/OpenESBLogManager.java @@ -0,0 +1,55 @@ +package net.openesb.standalone.logger; + +/** + * + * @author David BRASSELY (brasseld at gmail.com) + * @author OpenESB Community + */ +public class OpenESBLogManager extends java.util.logging.LogManager { + + @Override + public String getProperty(String name) { + String result = super.getProperty(name); + + if (result != null) { + result = replace(result); + } + + return result; + } + + /** + * System property replacement in the given string. + * + * @param str The original string + * @return the modified string + */ + protected String replace(String str) { + String result = str; + int pos_start = str.indexOf("${"); + if (pos_start >= 0) { + StringBuilder builder = new StringBuilder(); + int pos_end = -1; + while (pos_start >= 0) { + builder.append(str, pos_end + 1, pos_start); + pos_end = str.indexOf('}', pos_start + 2); + if (pos_end < 0) { + pos_end = pos_start - 1; + break; + } + String propName = str.substring(pos_start + 2, pos_end); + String replacement = propName.length() > 0 ? System + .getProperty(propName) : null; + if (replacement != null) { + builder.append(replacement); + } else { + builder.append(str, pos_start, pos_end + 1); + } + pos_start = str.indexOf("${", pos_end + 1); + } + builder.append(str, pos_end + 1, str.length()); + result = builder.toString(); + } + return result; + } +} diff --git a/openesb-standalone-packaging/src/main/resources/bin/openesb.bat b/openesb-standalone-packaging/src/main/resources/bin/openesb.bat index dfda481..e677318 100644 --- a/openesb-standalone-packaging/src/main/resources/bin/openesb.bat +++ b/openesb-standalone-packaging/src/main/resources/bin/openesb.bat @@ -63,7 +63,7 @@ echo OPENESB_HOME is set with the value: %OPENESB_HOME% cd %OPENESB_HOME% :: Start OpenESB in a new Dos window -START /MIN "OpenESB SE" %JAVA_HOME%\bin\java -Djava.util.logging.config.file=%OPENESB_HOME%/config/logger.properties -Djavax.net.ssl.keyStore=%OPENESB_HOME%/keystore.jks -Djavax.net.ssl.trustStore=%OPENESB_HOME%/cacerts.jks -Djavax.net.ssl.keyStorePassword=changeit -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=9009,suspend=n -Djmx.invoke.getters=true -Dinstall.root=%OPENESB_HOME% -jar %OPENESB_HOME%\lib\openesb-standalone-bootstrap.jar +START /MIN "OpenESB SE" %JAVA_HOME%\bin\java -Djava.util.logging.config.file=%OPENESB_HOME%/config/logger.properties -Djava.util.logging.manager=net.openesb.standalone.logger.OpenESBLogManager -Djavax.net.ssl.keyStore=%OPENESB_HOME%/keystore.jks -Djavax.net.ssl.trustStore=%OPENESB_HOME%/cacerts.jks -Djavax.net.ssl.keyStorePassword=changeit -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=9009,suspend=n -Djmx.invoke.getters=true -Dinstall.root=%OPENESB_HOME% -jar %OPENESB_HOME%\lib\openesb-standalone-bootstrap.jar echo. echo. diff --git a/openesb-standalone-packaging/src/main/resources/bin/openesb.sh b/openesb-standalone-packaging/src/main/resources/bin/openesb.sh index 9ac9bc8..c9dd78d 100644 --- a/openesb-standalone-packaging/src/main/resources/bin/openesb.sh +++ b/openesb-standalone-packaging/src/main/resources/bin/openesb.sh @@ -77,6 +77,7 @@ echo "" # Execute the JVM in the foreground "$JAVA" $JAVA_OPTS \ -Djava.util.logging.config.file=$OPENESB_HOME/config/logger.properties \ + -Djava.util.logging.manager=net.openesb.standalone.logger.OpenESBLogManager \ -Djmx.invoke.getters=true \ -Dinstall.root=$OPENESB_HOME \ -jar "$OPENESB_BOOT_CLASSPATH" \ diff --git a/openesb-standalone-packaging/src/main/resources/config/logger.properties b/openesb-standalone-packaging/src/main/resources/config/logger.properties index c0b638c..5415655 100644 --- a/openesb-standalone-packaging/src/main/resources/config/logger.properties +++ b/openesb-standalone-packaging/src/main/resources/config/logger.properties @@ -16,7 +16,7 @@ # The set of handlers to be loaded upon startup. # Comma-separated list of class names. # (? LogManager docs say no comma here, but JDK example has comma.) -handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler +handlers=net.openesb.standalone.logger.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # Loggers and Handlers may override this level @@ -27,25 +27,28 @@ handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler # --- ConsoleHandler --- # Override of global logging level -java.util.logging.ConsoleHandler.level=INFO +java.util.logging.ConsoleHandler.level=FINEST java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter # --- FileHandler --- # Override of global logging level -java.util.logging.FileHandler.level=INFO +net.openesb.standalone.logger.FileHandler.level=INFO # Naming style for the output file: -java.util.logging.FileHandler.pattern=logs/openesb%u.log +net.openesb.standalone.logger.FileHandler.directory=${install.root}/logs + +# Encoding +net.openesb.standalone.logger.FileHandler.encoding=UTF-8 # Limiting size of output file in bytes: -java.util.logging.FileHandler.limit=50000 +net.openesb.standalone.logger.FileHandler.limit=50000 # Number of output files to cycle through, by appending an # integer to the base file name: -java.util.logging.FileHandler.count=1 +net.openesb.standalone.logger.FileHandler.count=5 # Style of output (Simple or XML): -java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter +net.openesb.standalone.logger.FileHandler.formatter=java.util.logging.SimpleFormatter # Loggers # ------------------------------------------