stdouts = Arrays.asList("SYSTEM.OUT", "SYSTEM_OUT", "STDOUT");
+ return stdouts.contains(target.toUpperCase());
+ }
+
+ /** setup and configure the logging. */
+ public static synchronized void setup(LogLevel level, String logLocation,
+ boolean logFormatRfc3339) {
String target = "CONSOLE";
+ final String dateFormat = logFormatRfc3339 ? DATE_JDK14_LAYOUT_RFC3339 : DATE_JDK14_LAYOUT;
+ final SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormat,
+ Locale.getDefault());
+
+ // log format
+ SimpleFormatter formatter = new SimpleFormatter() {
+ private static final String format = JDK14_LAYOUT;
+
+ private String simpleClassName(String str) {
+ int start = str.lastIndexOf('.');
+ int end = str.indexOf('$');
+ if (start == -1 || start + 1 == str.length()) {
+ return str;
+ }
+ if (end == -1 || end <= start || end > str.length()) {
+ end = str.length();
+ }
+ return str.substring(start + 1, end);
+ }
- String logPattern = logFormatRfc3339 ? LAYOUT_RFC3339 : LAYOUT;
-
- PatternLayout layout = PatternLayout.newBuilder()
- .withConfiguration(config)
- .withPattern(logPattern)
- .build();
-
- if (logLocation != null
- && !ConsoleAppender.Target.SYSTEM_ERR.toString().equals(logLocation)
- && !SYSTEM_ERR_ALT.equals(logLocation)
- && !ConsoleAppender.Target.SYSTEM_OUT.toString().equals(logLocation)
- && !SYSTEM_OUT_ALT.equals(logLocation)) {
-
- target = "FileLogger";
-
- RollingFileAppender fa = RollingFileAppender.newBuilder()
- .setConfiguration(config)
- .withName(target)
- .withLayout(layout)
- .withFileName(logLocation)
- .withFilePattern(logLocation + ".%d")
- .withPolicy(SizeBasedTriggeringPolicy.createPolicy("5MB"))
- .withStrategy(DefaultRolloverStrategy.newBuilder().withMax("1").build())
- .build();
-
- fa.start();
- config.addAppender(fa);
- ctx.getRootLogger().addAppender(config.getAppender(fa.getName()));
-
- log.info("File Handler set");
- } else {
-
- if (logLocation != null
- && (ConsoleAppender.Target.SYSTEM_ERR.toString().equals(logLocation)
- || SYSTEM_ERR_ALT.equals(logLocation))) {
-
- ConsoleAppender console = (ConsoleAppender)config.getAppender("CONSOLE");
- console.stop();
- config.getRootLogger().removeAppender("CONSOLE");
- ctx.updateLoggers();
-
- ConsoleAppender ca = ConsoleAppender.newBuilder()
- .setConfiguration(config)
- .withName(logLocation)
- .setTarget(ConsoleAppender.Target.SYSTEM_ERR)
- .withLayout(layout)
- .build();
-
- ca.start();
- config.addAppender(ca);
- ctx.getRootLogger().addAppender(config.getAppender(ca.getName()));
+ @Override
+ public synchronized String format(LogRecord lr) {
+ String exception = "";
+ if (lr.getThrown() != null) {
+ StringWriter writer = new StringWriter();
+ PrintWriter stream = new PrintWriter(writer);
+ stream.println();
+ lr.getThrown().printStackTrace(stream);
+ stream.close();
+ exception = writer.toString();
+ }
+ return String.format(format,
+ dateFormatter.format(new Date()).toString(),
+ LogLevel.fromJulLevel(lr.getLevel()).toString(),
+ simpleClassName(lr.getSourceClassName()),
+ lr.getMessage(),
+ exception
+ );
+ }
+ };
+
+ // log level
+ Level julLevel = level.toJulLevel();
+
+ // Reset logging (removes all existing handlers, including on the root logger)
+ // Note: at some point we'd likely want to be more fine-grained and allow
+ // log handlers on other loggers instead, with some control on their log level
+ final LogManager manager = LogManager.getLogManager();
+ manager.reset();
+
+ // prepare the different handlers
+ ConsoleHandler stdoutHandler = null;
+ ConsoleHandler stderrHandler = null;
+ FileHandler fileHandler = null;
+
+ // the logLocation isn't always containing a file, it is sometimes
+ // referring to a standard output. We want to create a FileHandler only
+ // if the logLocation is a file on the FS.
+ if (logLocation != null && logLocation.length() > 0) {
+ if (!isStdOut(logLocation) && !isStdErr(logLocation)) {
+ // file logging
+ try {
+ // maximum one 5MB file
+ fileHandler = new FileHandler(logLocation, MAX_FILE_SIZE, FILE_COUNT);
+ fileHandler.setFormatter(formatter);
+ // note: fileHandler defaults to Level.ALL, so no need to set its log level
+ } catch (Exception e) {
+ fileHandler = null;
+ log.error("can't open the file handler:", e);
+ }
+ } else if (isStdErr(logLocation)) {
+ // console handler sending on stderr
+ // note that ConsoleHandler is sending on System.err
+ stderrHandler = new ConsoleHandler();
+ stderrHandler.setFormatter(formatter);
+ stderrHandler.setLevel(julLevel);
}
}
- // replace default appender with the correct layout
- LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
- loggerConfig.removeAppender(target);
+ // always have a console handler sending the logs to stdout
+ stdoutHandler = new StdoutConsoleHandler();
+ stdoutHandler.setFormatter(formatter);
+ stdoutHandler.setLevel(julLevel);
- Appender appender = ConsoleAppender.newBuilder()
- .setConfiguration(config)
- .withName(target)
- .withLayout(layout)
- .build();
- appender.start();
+ // Create our Logger, and set our configured handlers on it
+ jmxfetchLogger = Logger.getLogger("org.datadog.jmxfetch");
+ jmxfetchLogger.setLevel(julLevel);
- loggerConfig.addAppender(appender, null, null);
- loggerConfig.setLevel(level);
+ if (fileHandler != null) {
+ jmxfetchLogger.addHandler(fileHandler);
+ }
+ if (stdoutHandler != null) { // always non-null but doesn't cost much
+ jmxfetchLogger.addHandler(stdoutHandler);
+ }
+ if (stderrHandler != null) {
+ jmxfetchLogger.addHandler(stderrHandler);
+ }
+ }
- ctx.updateLoggers();
+ /** closeHandlers closes all opened handlers. */
+ public static synchronized void shutdown() {
+ for (Handler handler : jmxfetchLogger.getHandlers()) {
+ if (handler != null) {
+ handler.close();
+ jmxfetchLogger.removeHandler(handler);
+ }
+ }
}
/** Laconic logging for reduced verbosity. */
- public static void laconic(org.slf4j.Logger logger, Level level, String message, int max) {
+ public static void laconic(org.slf4j.Logger logger, LogLevel level, String message, int max) {
if (shouldLog(message, max)) {
- if (level.isInRange(Level.ERROR, Level.ALL)) {
+ if (level == LogLevel.ERROR) {
logger.error(message);
- } else if (level == Level.WARN) {
+ } else if (level == LogLevel.WARN) {
logger.warn(message);
- } else if (level == Level.INFO) {
+ } else if (level == LogLevel.INFO) {
logger.info(message);
- } else if (level == Level.DEBUG) {
+ } else if (level == LogLevel.DEBUG) {
logger.debug(message);
}
}
diff --git a/src/main/java/org/datadog/jmxfetch/util/LogLevel.java b/src/main/java/org/datadog/jmxfetch/util/LogLevel.java
new file mode 100644
index 000000000..0f9c77418
--- /dev/null
+++ b/src/main/java/org/datadog/jmxfetch/util/LogLevel.java
@@ -0,0 +1,110 @@
+package org.datadog.jmxfetch.util;
+
+import java.util.logging.Level;
+
+/**
+ * LogLevel used for internal logging to match Datadog Agent levels.
+ * Comparison table with java.util.logging:
+ *
+ * JUL | JMXFetch LogLevel
+ * ----------------------------
+ * OFF | OFF
+ * SEVERE | ERROR
+ * WARNING | WARN
+ * INFO | INFO
+ * CONFIG | DEBUG
+ * FINE | DEBUG
+ * FINER | TRACE
+ * FINEST | TRACE
+ * ALL | ALL
+ *
+ *
+ * `FATAL` from previous bindings used by JMXFetch is now converted
+ * to `LogLevel.ERROR`.
+ *
+ */
+public enum LogLevel {
+ OFF(0, "OFF"),
+ ERROR(1, "ERROR"),
+ WARN(2, "WARN"),
+ INFO(3, "INFO"),
+ DEBUG(4, "DEBUG"),
+ TRACE(5, "TRACE"),
+ ALL(6, "ALL");
+
+ private int level;
+ private String label;
+ private LogLevel(int level, String label) {
+ this.level = level;
+ this.label = label;
+ }
+
+ // --
+
+ /** fromJulLevel converts a java.util.logging.Level into a LogLevel. */
+ public static LogLevel fromJulLevel(Level julLevel) {
+ if (julLevel == Level.ALL) {
+ return ALL;
+ } else if (julLevel == Level.SEVERE) {
+ return ERROR;
+ } else if (julLevel == Level.WARNING) {
+ return WARN;
+ } else if (julLevel == Level.INFO) {
+ return INFO;
+ } else if (julLevel == Level.CONFIG) {
+ return DEBUG;
+ } else if (julLevel == Level.FINE) {
+ return DEBUG;
+ } else if (julLevel == Level.FINER) {
+ return TRACE;
+ } else if (julLevel == Level.FINEST) {
+ return TRACE;
+ } else if (julLevel == Level.OFF) {
+ return OFF;
+ }
+
+ // should never happen but defaults to INFO
+ return INFO;
+ }
+
+ /** fromString converts a string into a LogLevel, when not possible, it returns `INFO`. */
+ public static LogLevel fromString(String str) {
+ // compatibility
+ if (str.toUpperCase().equals("FATAL")) {
+ return ERROR;
+ }
+ for (LogLevel l : LogLevel.class.getEnumConstants()) {
+ if (str.toUpperCase().equals(l.toString().toUpperCase())) {
+ return l;
+ }
+ }
+
+ // default to INFO
+ return INFO;
+ }
+
+ /**
+ * toJulLevel converts a LogLevel to a `java.util.logging.Level`.
+ * This mapping needs to match http://slf4j.org/api/org/slf4j/impl/JDK14LoggerAdapter.html
+ **/
+ public Level toJulLevel() {
+ switch (this) {
+ case ALL:
+ return Level.ALL;
+ case ERROR:
+ return Level.SEVERE;
+ case WARN:
+ return Level.WARNING;
+ case INFO:
+ return Level.INFO;
+ case DEBUG:
+ return Level.FINE;
+ case TRACE:
+ return Level.FINEST;
+ case OFF:
+ return Level.OFF;
+ default:
+ return Level.INFO;
+ }
+ }
+}
diff --git a/src/main/java/org/datadog/jmxfetch/util/StdoutConsoleHandler.java b/src/main/java/org/datadog/jmxfetch/util/StdoutConsoleHandler.java
new file mode 100644
index 000000000..2ff880bc5
--- /dev/null
+++ b/src/main/java/org/datadog/jmxfetch/util/StdoutConsoleHandler.java
@@ -0,0 +1,13 @@
+package org.datadog.jmxfetch.util;
+
+import java.io.OutputStream;
+import java.lang.SecurityException;
+import java.util.logging.ConsoleHandler;
+
+public class StdoutConsoleHandler extends ConsoleHandler {
+ protected void setOutputStream(OutputStream out) throws SecurityException {
+ // force ConsoleHandler to set its output to System.out
+ // instead of System.err
+ super.setOutputStream(System.out);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/datadog/jmxfetch/validator/Log4JLevelValidator.java b/src/main/java/org/datadog/jmxfetch/validator/LogLevelValidator.java
similarity index 50%
rename from src/main/java/org/datadog/jmxfetch/validator/Log4JLevelValidator.java
rename to src/main/java/org/datadog/jmxfetch/validator/LogLevelValidator.java
index c92cc5330..3ecdac033 100644
--- a/src/main/java/org/datadog/jmxfetch/validator/Log4JLevelValidator.java
+++ b/src/main/java/org/datadog/jmxfetch/validator/LogLevelValidator.java
@@ -8,20 +8,26 @@
import java.util.Arrays;
import java.util.List;
-public class Log4JLevelValidator implements IParameterValidator {
- public static final List LOG4J_LEVELS =
+public class LogLevelValidator implements IParameterValidator {
+ // for history, there is a `FATAL` log level supported here as we were supporting it
+ // before moving to the `java.util.logging` log system.
+ // we keep it here since we still want it to be valid, but we consider it as ERROR
+ // in jmxfetch
+ // FIXME: remove the "LEVEL" log level, which was introduced by mistake early in JMXFetch's
+ // development (currently defaults to INFO).
+ public static final List LOGLEVELS =
Arrays.asList(
"ALL", "DEBUG", "ERROR", "FATAL", "INFO", "OFF", "TRACE", "LEVEL", "WARN");
- /** Validates a string as a valid Log4J logging level. */
+ /** Validates a string as a valid logging level. */
public void validate(String name, String value) throws ParameterException {
- if (!LOG4J_LEVELS.contains(value)) {
+ if (!LOGLEVELS.contains(value.toUpperCase())) {
String message =
"Parameter "
+ name
+ " should be in ("
- + StringUtils.join(",", LOG4J_LEVELS)
+ + StringUtils.join(",", LOGLEVELS)
+ ")";
throw new ParameterException(message);
}
diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties
deleted file mode 100644
index 03bbf5f21..000000000
--- a/src/main/resources/log4j2.properties
+++ /dev/null
@@ -1,10 +0,0 @@
-rootLogger.level = all
-rootLogger.appenderRef.stdout.ref = CONSOLE
-
-appender.console.type = Console
-appender.console.name = CONSOLE
-appender.console.target = SYSTEM_OUT
-appender.console.layout.type = PatternLayout
-appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss z} | JMX | %-5p | %c{1} | %m%n
-appender.console.filter.threshold.type = ThresholdFilter
-appender.console.filter.threshold.level = all
\ No newline at end of file
diff --git a/src/test/java/org/datadog/jmxfetch/TestCommon.java b/src/test/java/org/datadog/jmxfetch/TestCommon.java
index 535b81c83..ab9416ac9 100644
--- a/src/test/java/org/datadog/jmxfetch/TestCommon.java
+++ b/src/test/java/org/datadog/jmxfetch/TestCommon.java
@@ -24,10 +24,10 @@
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
-import org.apache.logging.log4j.Level;
import org.datadog.jmxfetch.reporter.ConsoleReporter;
import org.datadog.jmxfetch.reporter.Reporter;
import org.datadog.jmxfetch.util.CustomLogger;
+import org.datadog.jmxfetch.util.LogLevel;
import org.junit.After;
import org.junit.BeforeClass;
@@ -46,7 +46,7 @@ public static void init() throws Exception {
if (level == null) {
level = "ALL";
}
- CustomLogger.setup(Level.toLevel(level), "/tmp/jmxfetch_test.log", false);
+ CustomLogger.setup(LogLevel.ALL, "/tmp/jmxfetch_test.log", false);
}
/**
diff --git a/src/test/java/org/datadog/jmxfetch/TestInstance.java b/src/test/java/org/datadog/jmxfetch/TestInstance.java
index fcf303415..ccb9489e8 100644
--- a/src/test/java/org/datadog/jmxfetch/TestInstance.java
+++ b/src/test/java/org/datadog/jmxfetch/TestInstance.java
@@ -14,12 +14,10 @@
import java.util.List;
import java.util.Map;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.LogManager;
import org.junit.Test;
public class TestInstance extends TestCommon {
- private static final Logger log = LogManager.getLogger("Test Instance");
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger("Test Instance");
@Test
public void testMinCollectionInterval() throws Exception {
diff --git a/src/test/java/org/datadog/jmxfetch/TestParsingJCommander.java b/src/test/java/org/datadog/jmxfetch/TestParsingJCommander.java
index 92999d251..42bb133ef 100644
--- a/src/test/java/org/datadog/jmxfetch/TestParsingJCommander.java
+++ b/src/test/java/org/datadog/jmxfetch/TestParsingJCommander.java
@@ -12,7 +12,7 @@
import org.datadog.jmxfetch.reporter.ConsoleReporter;
import org.datadog.jmxfetch.reporter.StatsdReporter;
import org.datadog.jmxfetch.util.StringUtils;
-import org.datadog.jmxfetch.validator.Log4JLevelValidator;
+import org.datadog.jmxfetch.validator.LogLevelValidator;
import org.junit.Test;
public class TestParsingJCommander {
@@ -53,7 +53,7 @@ public void testParsingHelp() {
@Test
public void testParsingLogLevel() {
- for (String logLevel : Log4JLevelValidator.LOG4J_LEVELS) {
+ for (String logLevel : LogLevelValidator.LOGLEVELS) {
String[] params =
new String[] {
"--reporter",
diff --git a/src/test/java/org/datadog/jmxfetch/util/CustomLoggerPerfTest.java b/src/test/java/org/datadog/jmxfetch/util/CustomLoggerPerfTest.java
new file mode 100644
index 000000000..876d4f664
--- /dev/null
+++ b/src/test/java/org/datadog/jmxfetch/util/CustomLoggerPerfTest.java
@@ -0,0 +1,142 @@
+package com.timgroup.statsd;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.datadog.jmxfetch.util.CustomLogger;
+import org.datadog.jmxfetch.util.LogLevel;
+
+import java.io.IOException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
+import java.util.logging.Logger;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+
+@Slf4j
+@RunWith(Parameterized.class)
+public final class CustomLoggerPerfTest {
+
+ private final int duration; // Duration in secs
+ private final int testWorkers;
+ private final int msgSize; // length of log message in bytes
+ private final int uPause; // length of log message in bytes
+ private final boolean rfc3339; // length of log message in bytes
+
+ private AtomicBoolean running;
+ private final ExecutorService executor;
+
+ private static Logger log = Logger.getLogger("CustomLoggerPerfTest");
+
+ @Parameters
+ public static Collection