diff --git a/README.md b/README.md index 48bb155..e71b918 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ More details are at the [Project site](http://log4mongo.org/display/PUB/Log4mong * Jay Patel # Pre-requisites -* JDK 1.5+ -* MongoDB Server v2.0+ (tested with 2.6.5) -* MongoDB Java Driver v2.7+ (tested with 2.12.4) -* Log4J 1.2+ (tested with 1.2.16 - note: tests won't work on earlier versions due to Log4J API changes) +* JDK 1.8+ +* MongoDB Server v3.0+ (tested with 3.4.1) +* MongoDB Java Driver v3.0+ (tested with 3.4.1) +* Log4J 1.2+ (tested with 1.2.17 - note: tests won't work on earlier versions due to Log4J API changes) * Privateer (used only in unit tests - a copy is in the lib dir, in case you can't get it from the central Maven repo) diff --git a/lib/privateer-0.1.1.jar b/lib/privateer-0.1.1.jar deleted file mode 100644 index 7471931..0000000 Binary files a/lib/privateer-0.1.1.jar and /dev/null differ diff --git a/pom.xml b/pom.xml index 11e03cb..706546c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,175 +1,171 @@ - 4.0.0 - org.log4mongo - log4mongo-java - jar - log4mongo-java - Log4J Appender for MongoDB - 0.7.5 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + org.log4mongo + log4mongo-java + jar + log4mongo-java + Log4J Appender for MongoDB + 0.8.0 - - org.sonatype.oss - oss-parent - 7 - + + org.sonatype.oss + oss-parent + 7 + - - - UTF-8 - - - UTF-8 - - + + + UTF-8 + + + UTF-8 + + - - - Apache 2 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - - - pmonks - Peter Monks - pmonks@gmail.com - - - jsk - Jozef Sevcik - sevcik@styxys.com - - - wombatnation - Robert Stewart - robert@wombatnation.com - - + + + pmonks + Peter Monks + pmonks@gmail.com + + + jsk + Jozef Sevcik + sevcik@styxys.com + + + wombatnation + Robert Stewart + robert@wombatnation.com + + + ScheRas + Šimon Schierreich + simon.schierreich@messenger.cz + + - - Github - https://github.com/log4mongo/log4mongo-java/issues - + + Github + https://github.com/log4mongo/log4mongo-java/issues + - - scm:git:git@github.com:log4mongo/log4mongo-java.git - scm:git:git@github.com:log4mongo/log4mongo-java.git - scm:git:git@github.com:log4mongo/log4mongo-java - + + scm:git:git@github.com:log4mongo/log4mongo-java.git + scm:git:git@github.com:log4mongo/log4mongo-java.git + scm:git:git@github.com:log4mongo/log4mongo-java + - - - junit - junit - 4.8.2 - test - - - log4j - log4j - 1.2.16 - provided - - - com.sun.jmx - jmxri - - - com.sun.jdmk - jmxtools - - - - - org.mongodb - mongo-java-driver - 2.12.4 - - - com.wombatnation - privateer - 0.1.1 - test - - + + + log4j + log4j + 1.2.17 + provided + + + org.mongodb + mongo-java-driver + 3.4.2 + - - - - org.apache.maven.wagon - wagon-webdav - 1.0-beta-2 - - - - - maven-compiler-plugin - 3.2 - - 1.5 - 1.5 - - - - org.apache.maven.plugins - maven-release-plugin - 2.0-beta-9 - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - - attach-sources - - jar - - - - - - + + junit + junit + 4.12 + test + + + com.wombatnation + privateer + 0.1.1 + test + + - - - release - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - - - + + + + org.apache.maven.wagon + wagon-webdav + 1.0-beta-2 + + + + + maven-compiler-plugin + 3.6.0 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar + + + + + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + + diff --git a/src/main/java/org/log4mongo/BsonAppender.java b/src/main/java/org/log4mongo/BsonAppender.java index 76da620..045c617 100644 --- a/src/main/java/org/log4mongo/BsonAppender.java +++ b/src/main/java/org/log4mongo/BsonAppender.java @@ -19,16 +19,15 @@ import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; +import org.bson.BSONObject; -import com.mongodb.DBObject; /** - * Abstract Log4J Appender class that stores log events in the BSON format. Concrete - * implementation classes must implement append(DBObject) to store the BSON - * representation of a LoggingEvent. + * Abstract Log4J Appender class that stores log events in the BSON format. Concrete implementation classes must + * implement append(DBObject) to store the BSON representation of a LoggingEvent. *

* An example BSON structure for a single log entry is as follows: - * + *

*

  * {
  *   "_id"        : ObjectId("f1c0895fd5eee04a445deb00"),
@@ -58,7 +57,8 @@
  *                                         "method"     : "testLogWithChainedExceptions",
  *                                         "lineNumber" : 147,
  *                                         "class"      : {
- *                                                          "fullyQualifiedClassName" : "org.log4mongo.TestMongoDbAppender",
+ *                                                          "fullyQualifiedClassName" :
+ * "org.log4mongo.TestMongoDbAppender",
  *                                                          "package"                 : [ "org", "log4mongo" ],
  *                                                          "className"               : "TestMongoDbAppender"
  *                                                        }
@@ -67,7 +67,8 @@
  *                                         "method"     : "invoke0",
  *                                         "lineNumber" : -2,
  *                                         "class"      : {
- *                                                          "fullyQualifiedClassName" : "sun.reflect.NativeMethodAccessorImpl",
+ *                                                          "fullyQualifiedClassName" :
+ * "sun.reflect.NativeMethodAccessorImpl",
  *                                                          "package"                 : [ "sun", "reflect" ],
  *                                                          "className"               : "NativeMethodAccessorImpl"
  *                                                        }
@@ -95,47 +96,49 @@
  * }
  * 
* - * @see Log4J Appender Interface + * @see Log4J Appender + * Interface * @see MongoDB */ public abstract class BsonAppender extends AppenderSkeleton { - private LoggingEventBsonifier bsonifier = new LoggingEventBsonifierImpl(); - - /** - * @see org.apache.log4j.Appender#requiresLayout() - */ - public boolean requiresLayout() { - return(false); - } - /** - * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent) - */ - @Override - protected void append(final LoggingEvent loggingEvent) { - DBObject bson = bsonifier.bsonify(loggingEvent); - append(bson); - } + private LoggingEventBsonifier bsonifier = new LoggingEventBsonifierImpl(); + + /** + * @see org.apache.log4j.Appender#requiresLayout() + */ + public boolean requiresLayout() { + return ( false ); + } + + /** + * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent) + */ + @Override + protected void append( final LoggingEvent loggingEvent ) { + BSONObject bson = bsonifier.bsonify( loggingEvent ); + append( bson ); + } + + /** + * Method implemented by a concrete class to store the BSON object. + * + * @param bson The BSON representation of a Logging Event that will be stored + */ + protected abstract void append( BSONObject bson ); - /** - * Method implemented by a concrete class to store the BSON object. - * - * @param bson The BSON representation of a Logging Event that will be stored - */ - protected abstract void append(DBObject bson); + /** + * @return Object used to Bsonify LoggingEvent objects + */ + public LoggingEventBsonifier getBsonifier() { + return bsonifier; + } - /** - * @return Object used to Bsonify LoggingEvent objects - */ - public LoggingEventBsonifier getBsonifier() { - return bsonifier; - } + /** + * @param bsonifier Object used to Bsonify LoggingEvent objects + */ + public void setBsonifier( LoggingEventBsonifier bsonifier ) { + this.bsonifier = bsonifier; + } - /** - * @param bsonifier Object used to Bsonify LoggingEvent objects - */ - public void setBsonifier(LoggingEventBsonifier bsonifier) { - this.bsonifier = bsonifier; - } - } diff --git a/src/main/java/org/log4mongo/ExtendedMongoDbAppender.java b/src/main/java/org/log4mongo/ExtendedMongoDbAppender.java index 739396d..14e620f 100644 --- a/src/main/java/org/log4mongo/ExtendedMongoDbAppender.java +++ b/src/main/java/org/log4mongo/ExtendedMongoDbAppender.java @@ -2,74 +2,77 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import org.bson.BSONObject; import java.util.LinkedHashMap; import java.util.Map; + /** * This appender is designed so you can add top level elements to each logging * entry. Users can also extend MongoDbAppender themselves in order to add the * top level elements. - * + *

* Use case: A desire to use a common appender for unified logs across different * code bases, such that commonly logged elements be consistent, such as * application, eventType, etc. This is enabled by adding a property called * rootLevelProperties with a key=value list of elements to be added to the root * level log. See log4j.properties.sample for an example. - * + * * @author Mick Knutson (http://www.baselogic.com) */ public class ExtendedMongoDbAppender extends MongoDbAppender { - private DBObject constants; + private DBObject constants; - private Map rootProperties = new LinkedHashMap(); + private Map rootProperties = new LinkedHashMap (); - /** - * @see org.apache.log4j.AppenderSkeleton#activateOptions() - */ - @Override - public void activateOptions() { - super.activateOptions(); - initTopLevelProperties(); - } + /** + * @see org.apache.log4j.AppenderSkeleton#activateOptions() + */ + @Override + public void activateOptions() { + super.activateOptions(); + initTopLevelProperties(); + } - /** - * Initialize custom top level elements to appear in a log event - *

- * Allows users to create custom properties to be added to the top level - * log event. - */ - public void initTopLevelProperties() { - constants = new BasicDBObject(); - if (!rootProperties.isEmpty()){ - constants.putAll(rootProperties); - } - } + /** + * Initialize custom top level elements to appear in a log event + *

+ * Allows users to create custom properties to be added to the top level + * log event. + */ + public void initTopLevelProperties() { + constants = new BasicDBObject(); + if ( !rootProperties.isEmpty() ) { + constants.putAll( rootProperties ); + } + } - /** - * This will handle spaces and empty values - * A = minus- & C=equals= & E==F - * For XML, must escape (&) - * @param rootLevelProperties - */ - public void setRootLevelProperties(String rootLevelProperties) { - for (String keyValue : rootLevelProperties.split(" *& *")) { - String[] pairs = keyValue.split(" *= *", 2); - rootProperties.put(pairs[0], pairs.length == 1 ? "" : pairs[1]); - } - } + /** + * This will handle spaces and empty values + * A = minus- & C=equals= & E==F + * For XML, must escape (&) + * + * @param rootLevelProperties + */ + public void setRootLevelProperties( String rootLevelProperties ) { + for ( String keyValue : rootLevelProperties.split( " *& *" ) ) { + String[] pairs = keyValue.split( " *= *", 2 ); + rootProperties.put( pairs[ 0 ], pairs.length == 1 ? "" : pairs[ 1 ] ); + } + } - /** - * @param bson The BSON object to insert into a MongoDB database collection. - */ - @Override - public void append(DBObject bson) { - if (this.isInitialized() && bson != null) { - if (constants != null) { - bson.putAll(constants); - } - super.append(bson); - } - } + /** + * @param bson The BSON object to insert into a MongoDB database collection. + */ + @Override + public void append( BSONObject bson ) { + if ( this.isInitialized() && bson != null ) { + if ( constants != null ) { + bson.putAll( constants ); + } + super.append( bson ); + } + } } diff --git a/src/main/java/org/log4mongo/LoggingEventBsonifier.java b/src/main/java/org/log4mongo/LoggingEventBsonifier.java index 230b4b6..523ce25 100644 --- a/src/main/java/org/log4mongo/LoggingEventBsonifier.java +++ b/src/main/java/org/log4mongo/LoggingEventBsonifier.java @@ -18,13 +18,15 @@ package org.log4mongo; import org.apache.log4j.spi.LoggingEvent; +import org.bson.BSONObject; -import com.mongodb.DBObject; /** * Interface implemented by classes that create a BSON representation of a Log4J * LoggingEvent. LoggingEventBsonifierImpl is the default implementation. */ public interface LoggingEventBsonifier { - DBObject bsonify(LoggingEvent loggingEvent); + + BSONObject bsonify( LoggingEvent loggingEvent ); + } diff --git a/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java b/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java index 8684eff..237702d 100644 --- a/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java +++ b/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java @@ -17,6 +17,15 @@ package org.log4mongo; +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.spi.LocationInfo; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; +import org.bson.BSONObject; + import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.UnknownHostException; @@ -25,300 +34,301 @@ import java.util.List; import java.util.Map; -import org.apache.log4j.helpers.LogLog; -import org.apache.log4j.spi.LocationInfo; -import org.apache.log4j.spi.LoggingEvent; -import org.apache.log4j.spi.ThrowableInformation; - -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; /** * Default implementation class for creating a BSON representation of a Log4J LoggingEvent. */ public class LoggingEventBsonifierImpl implements LoggingEventBsonifier { - // Main log event elements - private static final String KEY_TIMESTAMP = "timestamp"; - private static final String KEY_LEVEL = "level"; - private static final String KEY_THREAD = "thread"; - private static final String KEY_MESSAGE = "message"; - private static final String KEY_LOGGER_NAME = "loggerName"; - // Source code location - private static final String KEY_FILE_NAME = "fileName"; - private static final String KEY_METHOD = "method"; - private static final String KEY_LINE_NUMBER = "lineNumber"; - private static final String KEY_CLASS = "class"; - // Class info - private static final String KEY_FQCN = "fullyQualifiedClassName"; - private static final String KEY_PACKAGE = "package"; - private static final String KEY_CLASS_NAME = "className"; - // Exceptions - private static final String KEY_THROWABLES = "throwables"; - private static final String KEY_EXCEPTION_MESSAGE = "message"; - private static final String KEY_STACK_TRACE = "stackTrace"; - // Host and Process Info - private static final String KEY_HOST = "host"; - private static final String KEY_PROCESS = "process"; - private static final String KEY_HOSTNAME = "name"; - private static final String KEY_IP = "ip"; - // MDC Properties - private static final String KEY_MDC_PROPERTIES = "properties"; - - private final DBObject hostInfo = new BasicDBObject(); - - public LoggingEventBsonifierImpl() { - setupNetworkInfo(); - } - - private void setupNetworkInfo() { - hostInfo.put(KEY_PROCESS, ManagementFactory.getRuntimeMXBean().getName()); - try { - hostInfo.put(KEY_HOSTNAME, InetAddress.getLocalHost().getHostName()); - hostInfo.put(KEY_IP, InetAddress.getLocalHost().getHostAddress()); - } catch (UnknownHostException e) { - LogLog.warn(e.getMessage()); - } - } - - /** - * BSONifies a single Log4J LoggingEvent object. - * - * @param loggingEvent - * The LoggingEvent object to BSONify (may be null). - * @return The BSONified equivalent of the LoggingEvent object (may be null). - */ - public DBObject bsonify(final LoggingEvent loggingEvent) { - DBObject result = null; - - if (loggingEvent != null) { - result = new BasicDBObject(); - - result.put(KEY_TIMESTAMP, new Date(loggingEvent.getTimeStamp())); - nullSafePut(result, KEY_LEVEL, loggingEvent.getLevel().toString()); - nullSafePut(result, KEY_THREAD, loggingEvent.getThreadName()); - nullSafePut(result, KEY_MESSAGE, loggingEvent.getRenderedMessage()); - nullSafePut(result, KEY_LOGGER_NAME, bsonifyClassName(loggingEvent.getLoggerName())); - - addMDCInformation(result, loggingEvent.getProperties()); - addLocationInformation(result, loggingEvent.getLocationInformation()); - addThrowableInformation(result, loggingEvent.getThrowableInformation()); - addHostnameInformation(result); - } - - return (result); - } - - /** - * Adds MDC Properties to the DBObject. - * - * @param bson - * The root DBObject - * @param props - * MDC Properties to be logged - */ - protected void addMDCInformation(DBObject bson, final Map props) { - if (props != null && props.size() > 0) { - - BasicDBObject mdcProperties = new BasicDBObject(); - String key; - // Copy MDC properties into document - for (Map.Entry entry : props.entrySet()) { - key = (entry.getKey().toString().contains(".")) - ? entry.getKey().toString().replaceAll("\\.", "_") - : entry.getKey().toString(); - nullSafePut(mdcProperties, key, entry.getValue().toString()); - } - bson.put(KEY_MDC_PROPERTIES, mdcProperties); - } - } - - /** - * Adds the LocationInfo object to an existing BSON object. - * - * @param bson - * The BSON object to add the location info to (must not be null). - * @param locationInfo - * The LocationInfo object to add to the BSON object (may be null). - */ - protected void addLocationInformation(DBObject bson, final LocationInfo locationInfo) { - if (locationInfo != null) { - nullSafePut(bson, KEY_FILE_NAME, locationInfo.getFileName()); - nullSafePut(bson, KEY_METHOD, locationInfo.getMethodName()); - nullSafePut(bson, KEY_LINE_NUMBER, locationInfo.getLineNumber()); - nullSafePut(bson, KEY_CLASS, bsonifyClassName(locationInfo.getClassName())); - } - } - - /** - * Adds the ThrowableInformation object to an existing BSON object. - * - * @param bson - * The BSON object to add the throwable info to (must not be null). - * @param throwableInfo - * The ThrowableInformation object to add to the BSON object (may be null). - */ - @SuppressWarnings(value = "unchecked") - protected void addThrowableInformation(DBObject bson, final ThrowableInformation throwableInfo) { - if (throwableInfo != null) { - Throwable currentThrowable = throwableInfo.getThrowable(); - List throwables = new BasicDBList(); - - while (currentThrowable != null) { - DBObject throwableBson = bsonifyThrowable(currentThrowable); - - if (throwableBson != null) { - throwables.add(throwableBson); - } - - currentThrowable = currentThrowable.getCause(); - } - - if (throwables.size() > 0) { - bson.put(KEY_THROWABLES, throwables); - } - } - } - - /** - * Adds the current process's host name, VM name and IP address - * - * @param bson - * A BSON object containing host name, VM name and IP address - */ - protected void addHostnameInformation(DBObject bson) { - nullSafePut(bson, KEY_HOST, hostInfo); - } - - /** - * BSONifies the given Throwable. - * - * @param throwable - * The throwable object to BSONify (may be null). - * @return The BSONified equivalent of the Throwable object (may be null). - */ - protected DBObject bsonifyThrowable(final Throwable throwable) { - DBObject result = null; - - if (throwable != null) { - result = new BasicDBObject(); - - nullSafePut(result, KEY_EXCEPTION_MESSAGE, throwable.getMessage()); - nullSafePut(result, KEY_STACK_TRACE, bsonifyStackTrace(throwable.getStackTrace())); - } - - return (result); - } - - /** - * BSONifies the given stack trace. - * - * @param stackTrace - * The stack trace object to BSONify (may be null). - * @return The BSONified equivalent of the stack trace object (may be null). - */ - protected DBObject bsonifyStackTrace(final StackTraceElement[] stackTrace) { - BasicDBList result = null; - - if (stackTrace != null && stackTrace.length > 0) { - result = new BasicDBList(); - - for (StackTraceElement element : stackTrace) { - DBObject bson = bsonifyStackTraceElement(element); - - if (bson != null) { - result.add(bson); - } - } - } - - return (result); - } - - /** - * BSONifies the given stack trace element. - * - * @param element - * The stack trace element object to BSONify (may be null). - * @return The BSONified equivalent of the stack trace element object (may be null). - */ - protected DBObject bsonifyStackTraceElement(final StackTraceElement element) { - DBObject result = null; - - if (element != null) { - result = new BasicDBObject(); - - nullSafePut(result, KEY_FILE_NAME, element.getFileName()); - nullSafePut(result, KEY_METHOD, element.getMethodName()); - nullSafePut(result, KEY_LINE_NUMBER, element.getLineNumber()); - nullSafePut(result, KEY_CLASS, bsonifyClassName(element.getClassName())); - } - - return (result); - } - - /** - * BSONifies the given class name. - * - * @param className - * The class name to BSONify (may be null). - * @return The BSONified equivalent of the class name (may be null). - */ - @SuppressWarnings(value = "unchecked") - protected DBObject bsonifyClassName(final String className) { - DBObject result = null; - - if (className != null && className.trim().length() > 0) { - result = new BasicDBObject(); - - result.put(KEY_FQCN, className); - - List packageComponents = new BasicDBList(); - String[] packageAndClassName = className.split("\\."); - - packageComponents.addAll(Arrays.asList(packageAndClassName)); - // Requires Java 6 - // packageComponents.addAll(Arrays.asList(Arrays.copyOf(packageAndClassName, - // packageAndClassName.length - 1))); - - if (packageComponents.size() > 0) { - result.put(KEY_PACKAGE, packageComponents); - } - - result.put(KEY_CLASS_NAME, packageAndClassName[packageAndClassName.length - 1]); - } - - return (result); - } - - /** - * Adds the given value to the given key, except if it's null (in which case this method does - * nothing). - * - * @param bson - * The BSON object to add the key/value to (must not be null). - * @param key - * The key of the object (must not be null). - * @param value - * The value of the object (may be null). - */ - protected void nullSafePut(DBObject bson, final String key, final Object value) { - if (value != null) { - if (value instanceof String) { - String stringValue = (String) value; - if (stringValue.trim().length() > 0) { - bson.put(key, stringValue); - } - } else if (value instanceof StringBuffer) { - String stringValue = ((StringBuffer) value).toString(); - if (stringValue.trim().length() > 0) { - bson.put(key, stringValue); - } - } else { - bson.put(key, value); - } - } - } + // Main log event elements + private static final String KEY_TIMESTAMP = "timestamp"; + + private static final String KEY_LEVEL = "level"; + + private static final String KEY_THREAD = "thread"; + + private static final String KEY_MESSAGE = "message"; + + private static final String KEY_LOGGER_NAME = "loggerName"; + + // Source code location + private static final String KEY_FILE_NAME = "fileName"; + + private static final String KEY_METHOD = "method"; + + private static final String KEY_LINE_NUMBER = "lineNumber"; + + private static final String KEY_CLASS = "class"; + + // Class info + private static final String KEY_FQCN = "fullyQualifiedClassName"; + + private static final String KEY_PACKAGE = "package"; + + private static final String KEY_CLASS_NAME = "className"; + + // Exceptions + private static final String KEY_THROWABLES = "throwables"; + + private static final String KEY_EXCEPTION_MESSAGE = "message"; + + private static final String KEY_STACK_TRACE = "stackTrace"; + + // Host and Process Info + private static final String KEY_HOST = "host"; + + private static final String KEY_PROCESS = "process"; + + private static final String KEY_HOSTNAME = "name"; + + private static final String KEY_IP = "ip"; + + // MDC Properties + private static final String KEY_MDC_PROPERTIES = "properties"; + + private final DBObject hostInfo = new BasicDBObject(); + + public LoggingEventBsonifierImpl() { + setupNetworkInfo(); + } + + private void setupNetworkInfo() { + hostInfo.put( KEY_PROCESS, ManagementFactory.getRuntimeMXBean().getName() ); + try { + hostInfo.put( KEY_HOSTNAME, InetAddress.getLocalHost().getHostName() ); + hostInfo.put( KEY_IP, InetAddress.getLocalHost().getHostAddress() ); + } catch ( UnknownHostException e ) { + LogLog.warn( e.getMessage() ); + } + } + + /** + * BSONifies a single Log4J LoggingEvent object. + * + * @param loggingEvent The LoggingEvent object to BSONify (may be null). + * + * @return The BSONified equivalent of the LoggingEvent object (may be null). + */ + public BSONObject bsonify( final LoggingEvent loggingEvent ) { + DBObject result = null; + + if ( loggingEvent != null ) { + result = new BasicDBObject(); + + result.put( KEY_TIMESTAMP, new Date( loggingEvent.getTimeStamp() ) ); + nullSafePut( result, KEY_LEVEL, loggingEvent.getLevel().toString() ); + nullSafePut( result, KEY_THREAD, loggingEvent.getThreadName() ); + nullSafePut( result, KEY_MESSAGE, loggingEvent.getRenderedMessage() ); + nullSafePut( result, KEY_LOGGER_NAME, bsonifyClassName( loggingEvent.getLoggerName() ) ); + + addMDCInformation( result, loggingEvent.getProperties() ); + addLocationInformation( result, loggingEvent.getLocationInformation() ); + addThrowableInformation( result, loggingEvent.getThrowableInformation() ); + addHostnameInformation( result ); + } + + return ( result ); + } + + /** + * Adds MDC Properties to the DBObject. + * + * @param bson The root DBObject + * @param props MDC Properties to be logged + */ + protected void addMDCInformation( DBObject bson, final Map props ) { + if ( props != null && props.size() > 0 ) { + + BasicDBObject mdcProperties = new BasicDBObject(); + String key; + // Copy MDC properties into document + for ( Map.Entry entry : props.entrySet() ) { + key = ( entry.getKey().toString().contains( "." ) ) + ? entry.getKey().toString().replaceAll( "\\.", "_" ) + : entry.getKey().toString(); + nullSafePut( mdcProperties, key, entry.getValue().toString() ); + } + bson.put( KEY_MDC_PROPERTIES, mdcProperties ); + } + } + + /** + * Adds the LocationInfo object to an existing BSON object. + * + * @param bson The BSON object to add the location info to (must not be null). + * @param locationInfo The LocationInfo object to add to the BSON object (may be null). + */ + protected void addLocationInformation( DBObject bson, final LocationInfo locationInfo ) { + if ( locationInfo != null ) { + nullSafePut( bson, KEY_FILE_NAME, locationInfo.getFileName() ); + nullSafePut( bson, KEY_METHOD, locationInfo.getMethodName() ); + nullSafePut( bson, KEY_LINE_NUMBER, locationInfo.getLineNumber() ); + nullSafePut( bson, KEY_CLASS, bsonifyClassName( locationInfo.getClassName() ) ); + } + } + + /** + * Adds the ThrowableInformation object to an existing BSON object. + * + * @param bson The BSON object to add the throwable info to (must not be null). + * @param throwableInfo The ThrowableInformation object to add to the BSON object (may be null). + */ + @SuppressWarnings( value = "unchecked" ) + protected void addThrowableInformation( DBObject bson, final ThrowableInformation throwableInfo ) { + if ( throwableInfo != null ) { + Throwable currentThrowable = throwableInfo.getThrowable(); + List throwables = new BasicDBList(); + + while ( currentThrowable != null ) { + DBObject throwableBson = bsonifyThrowable( currentThrowable ); + + if ( throwableBson != null ) { + throwables.add( throwableBson ); + } + + currentThrowable = currentThrowable.getCause(); + } + + if ( throwables.size() > 0 ) { + bson.put( KEY_THROWABLES, throwables ); + } + } + } + + /** + * Adds the current process's host name, VM name and IP address + * + * @param bson A BSON object containing host name, VM name and IP address + */ + protected void addHostnameInformation( DBObject bson ) { + nullSafePut( bson, KEY_HOST, hostInfo ); + } + + /** + * BSONifies the given Throwable. + * + * @param throwable The throwable object to BSONify (may be null). + * + * @return The BSONified equivalent of the Throwable object (may be null). + */ + protected DBObject bsonifyThrowable( final Throwable throwable ) { + DBObject result = null; + + if ( throwable != null ) { + result = new BasicDBObject(); + + nullSafePut( result, KEY_EXCEPTION_MESSAGE, throwable.getMessage() ); + nullSafePut( result, KEY_STACK_TRACE, bsonifyStackTrace( throwable.getStackTrace() ) ); + } + + return ( result ); + } + + /** + * BSONifies the given stack trace. + * + * @param stackTrace The stack trace object to BSONify (may be null). + * + * @return The BSONified equivalent of the stack trace object (may be null). + */ + protected DBObject bsonifyStackTrace( final StackTraceElement[] stackTrace ) { + BasicDBList result = null; + + if ( stackTrace != null && stackTrace.length > 0 ) { + result = new BasicDBList(); + + for ( StackTraceElement element : stackTrace ) { + DBObject bson = bsonifyStackTraceElement( element ); + + if ( bson != null ) { + result.add( bson ); + } + } + } + + return ( result ); + } + + /** + * BSONifies the given stack trace element. + * + * @param element The stack trace element object to BSONify (may be null). + * + * @return The BSONified equivalent of the stack trace element object (may be null). + */ + protected DBObject bsonifyStackTraceElement( final StackTraceElement element ) { + DBObject result = null; + + if ( element != null ) { + result = new BasicDBObject(); + + nullSafePut( result, KEY_FILE_NAME, element.getFileName() ); + nullSafePut( result, KEY_METHOD, element.getMethodName() ); + nullSafePut( result, KEY_LINE_NUMBER, element.getLineNumber() ); + nullSafePut( result, KEY_CLASS, bsonifyClassName( element.getClassName() ) ); + } + + return ( result ); + } + + /** + * BSONifies the given class name. + * + * @param className The class name to BSONify (may be null). + * + * @return The BSONified equivalent of the class name (may be null). + */ + @SuppressWarnings( value = "unchecked" ) + protected DBObject bsonifyClassName( final String className ) { + DBObject result = null; + + if ( className != null && className.trim().length() > 0 ) { + result = new BasicDBObject(); + + result.put( KEY_FQCN, className ); + + List packageComponents = new BasicDBList(); + String[] packageAndClassName = className.split( "\\." ); + + packageComponents.addAll( Arrays.asList( packageAndClassName ) ); + // Requires Java 6 + // packageComponents.addAll(Arrays.asList(Arrays.copyOf(packageAndClassName, + // packageAndClassName.length - 1))); + + if ( packageComponents.size() > 0 ) { + result.put( KEY_PACKAGE, packageComponents ); + } + + result.put( KEY_CLASS_NAME, packageAndClassName[ packageAndClassName.length - 1 ] ); + } + + return ( result ); + } + + /** + * Adds the given value to the given key, except if it's null (in which case this method does + * nothing). + * + * @param bson The BSON object to add the key/value to (must not be null). + * @param key The key of the object (must not be null). + * @param value The value of the object (may be null). + */ + protected void nullSafePut( DBObject bson, final String key, final Object value ) { + if ( value != null ) { + if ( value instanceof String ) { + String stringValue = (String) value; + if ( stringValue.trim().length() > 0 ) { + bson.put( key, stringValue ); + } + } else if ( value instanceof StringBuffer ) { + String stringValue = ( (StringBuffer) value ).toString(); + if ( stringValue.trim().length() > 0 ) { + bson.put( key, stringValue ); + } + } else { + bson.put( key, value ); + } + } + } } diff --git a/src/main/java/org/log4mongo/MongoDbAppender.java b/src/main/java/org/log4mongo/MongoDbAppender.java index 76cc4d4..a143810 100644 --- a/src/main/java/org/log4mongo/MongoDbAppender.java +++ b/src/main/java/org/log4mongo/MongoDbAppender.java @@ -17,364 +17,370 @@ package org.log4mongo; -import java.net.UnknownHostException; +import com.mongodb.*; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.apache.log4j.spi.ErrorCode; +import org.bson.BSONObject; +import org.bson.Document; + import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import org.apache.log4j.spi.ErrorCode; - -import com.mongodb.DB; -import com.mongodb.DBCollection; -import com.mongodb.DBObject; -import com.mongodb.Mongo; -import com.mongodb.MongoException; -import com.mongodb.ServerAddress; -import com.mongodb.WriteConcern; /** * Log4J Appender that writes log events into a MongoDB document oriented database. Log events are * fully parsed and stored as structured records in MongoDB (this appender does not require, nor use * a Log4J layout). - * + *

* The appender does not create any indexes on the data that's stored - it is assumed that if * query performance is required, those would be created externally (e.g., in the MongoDB shell or * other external application). - * + * * @author Peter Monks (pmonks@gmail.com) - * @see Log4J - * Appender Interface + * @see Log4J Appender + * Interface * @see MongoDB */ public class MongoDbAppender extends BsonAppender { - private final static String DEFAULT_MONGO_DB_HOSTNAME = "localhost"; - private final static String DEFAULT_MONGO_DB_PORT = "27017"; - private final static String DEFAULT_MONGO_DB_DATABASE_NAME = "log4mongo"; - private final static String DEFAULT_MONGO_DB_COLLECTION_NAME = "logevents"; - private WriteConcern concern; - - private String hostname = DEFAULT_MONGO_DB_HOSTNAME; - private String port = DEFAULT_MONGO_DB_PORT; - private String databaseName = DEFAULT_MONGO_DB_DATABASE_NAME; - private String collectionName = DEFAULT_MONGO_DB_COLLECTION_NAME; - private String userName = null; - private String password = null; - private String writeConcern = null; - private Mongo mongo = null; - private DBCollection collection = null; - - private boolean initialized = false; - - /** - * @see org.apache.log4j.Appender#requiresLayout() - */ - public boolean requiresLayout() { - return (false); - } - - /** - * @see org.apache.log4j.AppenderSkeleton#activateOptions() - */ - @Override - public void activateOptions() { - try { - // Close previous connections if reactivating - if (mongo != null) { - close(); - } - - List addresses = getServerAddresses(hostname, port); - mongo = getMongo(addresses); - - DB database = getDatabase(mongo, databaseName); - - if (userName != null && userName.trim().length() > 0) { - if (!database.authenticate(userName, password.toCharArray())) { - throw new RuntimeException("Unable to authenticate with MongoDB server."); - } - - // Allow password to be GCed - password = null; - } - - setCollection(database.getCollection(collectionName)); - initialized = true; - } catch (Exception e) { - errorHandler.error("Unexpected exception while initialising MongoDbAppender.", e, - ErrorCode.GENERIC_FAILURE); - } - } - - /* - * This method could be overridden to provide the DB instance from an existing connection. - */ - protected DB getDatabase(Mongo mongo, String databaseName) { - return mongo.getDB(databaseName); - } - - /* - * This method could be overridden to provide the Mongo instance from an existing connection. - */ - protected Mongo getMongo(List addresses) { - if (addresses.size() < 2) { - return new Mongo(addresses.get(0)); - } else { - // Replica set - return new Mongo(addresses); - } - } - - /** - * Note: this method is primarily intended for use by the unit tests. - * - * @param collection - * The MongoDB collection to use when logging events. - */ - public void setCollection(final DBCollection collection) { - assert collection != null : "collection must not be null"; - - this.collection = collection; - } - - /** - * @see org.apache.log4j.Appender#close() - */ - public void close() { - if (mongo != null) { - collection = null; - mongo.close(); - } - } - - /** - * @return The hostname of the MongoDB server (will not be null, empty or blank). - */ - public String getHostname() { - return hostname; - } - - /** - * @param hostname - * The MongoDB hostname to set (must not be null, empty or blank). - */ - public void setHostname(final String hostname) { - assert hostname != null : "hostname must not be null"; - assert hostname.trim().length() > 0 : "hostname must not be empty or blank"; - - this.hostname = hostname; - } - - /** - * @return The port of the MongoDB server (will be > 0). - */ - public String getPort() { - return port; - } - - /** - * @param port - * The port to set (must not be null, empty or blank). - */ - public void setPort(final String port) { - assert port != null : "port must not be null"; - assert port.trim().length() > 0 : "port must not be empty or blank"; - - this.port = port; - } - - /** - * @return The database used in the MongoDB server (will not be null, empty or blank). - */ - public String getDatabaseName() { - return databaseName; - } - - /** - * @param databaseName - * The database to use in the MongoDB server (must not be null, empty or - * blank). - */ - public void setDatabaseName(final String databaseName) { - assert databaseName != null : "database must not be null"; - assert databaseName.trim().length() > 0 : "database must not be empty or blank"; - - this.databaseName = databaseName; - } - - /** - * @return The collection used within the database in the MongoDB server (will not be null, - * empty or blank). - */ - public String getCollectionName() { - return collectionName; - } - - /** - * @param collectionName - * The collection used within the database in the MongoDB server (must not be - * null, empty or blank). - */ - public void setCollectionName(final String collectionName) { - assert collectionName != null : "collection must not be null"; - assert collectionName.trim().length() > 0 : "collection must not be empty or blank"; - - this.collectionName = collectionName; - } - - /** - * @return The userName used to authenticate with MongoDB (may be null). - */ - public String getUserName() { - return userName; - } - - /** - * @param userName - * The userName to use when authenticating with MongoDB (may be null). - */ - public void setUserName(final String userName) { - this.userName = userName; - } - - /** - * @param password - * The password to use when authenticating with MongoDB (may be null). - */ - public void setPassword(final String password) { - this.password = password; - } - - /** - * @return the writeConcern setting for Mongo. - */ - public String getWriteConcern() { + + private final static String DEFAULT_MONGO_DB_HOSTNAME = "localhost"; + + private final static String DEFAULT_MONGO_DB_PORT = "27017"; + + private final static String DEFAULT_MONGO_DB_DATABASE_NAME = "log4mongo"; + + private final static String DEFAULT_MONGO_DB_COLLECTION_NAME = "logevents"; + + private WriteConcern concern; + + private String hostname = DEFAULT_MONGO_DB_HOSTNAME; + + private String port = DEFAULT_MONGO_DB_PORT; + + private String databaseName = DEFAULT_MONGO_DB_DATABASE_NAME; + + private String collectionName = DEFAULT_MONGO_DB_COLLECTION_NAME; + + private String userName = null; + + private String password = null; + + private String writeConcern = null; + + private MongoClient mongo = null; + + private MongoCollection collection = null; + + private boolean initialized = false; + + /** + * @see org.apache.log4j.Appender#requiresLayout() + */ + public boolean requiresLayout() { + return ( false ); + } + + /** + * @see org.apache.log4j.AppenderSkeleton#activateOptions() + */ + @Override + public void activateOptions() { + try { + // Close previous connections if reactivating + if ( mongo != null ) { + close(); + } + + MongoCredential credentials = null; + if ( userName != null && userName.trim().length() > 0 ) { + credentials = MongoCredential.createCredential( userName, databaseName, password.toCharArray() ); + password = null; + } + + mongo = getMongo( getServerAddresses( hostname, port ), ( credentials != null ) ? Arrays.asList( credentials ) : null ); + + MongoDatabase database = getDatabase( mongo, databaseName ); + + setCollection( database.getCollection( collectionName ) ); + + initialized = true; + } catch ( Exception e ) { + errorHandler.error( "Unexpected exception while initialising MongoDbAppender.", e, + ErrorCode.GENERIC_FAILURE ); + } + } + + /* + * This method could be overridden to provide the DB instance from an existing connection. + */ + protected MongoDatabase getDatabase( MongoClient mongo, String databaseName ) { + return mongo.getDatabase( databaseName ); + } + + /* + * This method could be overridden to provide the Mongo instance from an existing connection. + */ + protected MongoClient getMongo( List addresses ) { + if ( addresses.size() < 2 ) { + return new MongoClient( addresses.get( 0 ) ); + } else { + // Replica set + return new MongoClient( addresses ); + } + } + + private MongoClient getMongo( List addresses, List credentials ) { + if ( credentials == null ) { + return this.getMongo( addresses ); + } + + if ( addresses.size() < 2 ) { + return new MongoClient( addresses.get( 0 ), credentials ); + } else { + // Replica set + return new MongoClient( addresses, credentials ); + } + } + + /** + * Note: this method is primarily intended for use by the unit tests. + * + * @param collection The MongoDB collection to use when logging events. + */ + public void setCollection( final MongoCollection collection ) { + assert collection != null : "collection must not be null"; + + this.collection = collection; + } + + /** + * @see org.apache.log4j.Appender#close() + */ + public void close() { + if ( mongo != null ) { + collection = null; + mongo.close(); + } + } + + /** + * @return The hostname of the MongoDB server (will not be null, empty or blank). + */ + public String getHostname() { + return hostname; + } + + /** + * @param hostname The MongoDB hostname to set (must not be null, empty or blank). + */ + public void setHostname( final String hostname ) { + assert hostname != null : "hostname must not be null"; + assert hostname.trim().length() > 0 : "hostname must not be empty or blank"; + + this.hostname = hostname; + } + + /** + * @return The port of the MongoDB server (will be > 0). + */ + public String getPort() { + return port; + } + + /** + * @param port The port to set (must not be null, empty or blank). + */ + public void setPort( final String port ) { + assert port != null : "port must not be null"; + assert port.trim().length() > 0 : "port must not be empty or blank"; + + this.port = port; + } + + /** + * @return The database used in the MongoDB server (will not be null, empty or blank). + */ + public String getDatabaseName() { + return databaseName; + } + + /** + * @param databaseName The database to use in the MongoDB server (must not be null, empty or blank). + */ + public void setDatabaseName( final String databaseName ) { + assert databaseName != null : "database must not be null"; + assert databaseName.trim().length() > 0 : "database must not be empty or blank"; + + this.databaseName = databaseName; + } + + /** + * @return The collection used within the database in the MongoDB server (will not be null, empty or blank). + */ + public String getCollectionName() { + return collectionName; + } + + /** + * @param collectionName The collection used within the database in the MongoDB server (must not be null, empty + * or blank). + */ + public void setCollectionName( final String collectionName ) { + assert collectionName != null : "collection must not be null"; + assert collectionName.trim().length() > 0 : "collection must not be empty or blank"; + + this.collectionName = collectionName; + } + + /** + * @return The userName used to authenticate with MongoDB (may be null). + */ + public String getUserName() { + return userName; + } + + /** + * @param userName The userName to use when authenticating with MongoDB (may be null). + */ + public void setUserName( final String userName ) { + this.userName = userName; + } + + /** + * @param password The password to use when authenticating with MongoDB (may be null). + */ + public void setPassword( final String password ) { + this.password = password; + } + + /** + * @return the writeConcern setting for Mongo. + */ + public String getWriteConcern() { return writeConcern; } - - /** - * @param writeConcern - * The WriteConcern setting for Mongo.(may be null). If null, set to default of dbCollection's writeConcern. - */ - public void setWriteConcern(final String writeConcern) { - this.writeConcern = writeConcern; - concern = WriteConcern.valueOf(writeConcern); + + /** + * @param writeConcern The WriteConcern setting for Mongo.(may be null). If null, set to default of + * dbCollection's writeConcern. + */ + public void setWriteConcern( final String writeConcern ) { + this.writeConcern = writeConcern; + concern = WriteConcern.valueOf( writeConcern ); } - - public WriteConcern getConcern() { - if (concern == null) { - concern = getCollection().getWriteConcern(); - } + + public WriteConcern getConcern() { + if ( concern == null ) { + concern = getCollection().getWriteConcern(); + } return concern; } - /** - * @param bson - * The BSON object to insert into a MongoDB database collection. - */ - @Override - public void append(DBObject bson) { - if (initialized && bson != null) { - try { - getCollection().insert(bson, getConcern()); - } catch (MongoException e) { - errorHandler.error("Failed to insert document to MongoDB", e, - ErrorCode.WRITE_FAILURE); - } - } - } - - /** - * Returns true if appender was successfully initialized. If this method returns false, the - * appender should not attempt to log events. - * - * @return true if appender was successfully initialized - */ - public boolean isInitialized() { - return initialized; - } - - /** - * - * @return The MongoDB collection to which events are logged. - */ - protected DBCollection getCollection() { - return collection; - } - - /** - * Returns a List of ServerAddress objects for each host specified in the hostname property. - * Returns an empty list if configuration is detected to be invalid, e.g.: - *

- * - * @param hostname - * Blank space delimited hostnames - * @param port - * Blank space delimited ports. Must specify one port for all hosts or a port per - * host. - * @return List of ServerAddresses to connect to - */ - private List getServerAddresses(String hostname, String port) { - List addresses = new ArrayList(); - - String[] hosts = hostname.split(" "); - String[] ports = port.split(" "); - - if (ports.length != 1 && ports.length != hosts.length) { - errorHandler.error( - "MongoDB appender port property must contain one port or a port per host", - null, ErrorCode.ADDRESS_PARSE_FAILURE); - } else { - List portNums = getPortNums(ports); - // Validate number of ports again after parsing - if (portNums.size() != 1 && portNums.size() != hosts.length) { - errorHandler - .error("MongoDB appender port property must contain one port or a valid port per host", - null, ErrorCode.ADDRESS_PARSE_FAILURE); - } else { - boolean onePort = (portNums.size() == 1); - - int i = 0; - for (String host : hosts) { - int portNum = (onePort) ? portNums.get(0) : portNums.get(i); - try { - addresses.add(new ServerAddress(host.trim(), portNum)); - } catch (UnknownHostException e) { - errorHandler.error( - "MongoDB appender hostname property contains unknown host", e, - ErrorCode.ADDRESS_PARSE_FAILURE); - } - i++; - } - } - } - return addresses; - } - - private List getPortNums(String[] ports) { - List portNums = new ArrayList(); - - for (String port : ports) { - try { - Integer portNum = Integer.valueOf(port.trim()); - if (portNum < 0) { - errorHandler.error( - "MongoDB appender port property can't contain a negative integer", - null, ErrorCode.ADDRESS_PARSE_FAILURE); - } else { - portNums.add(portNum); - } - } catch (NumberFormatException e) { - errorHandler.error( - "MongoDB appender can't parse a port property value into an integer", e, - ErrorCode.ADDRESS_PARSE_FAILURE); - } - - } - - return portNums; - } + /** + * @param bson The BSON object to insert into a MongoDB database collection. + */ + @Override + public void append( BSONObject bson ) { + if ( initialized && bson != null ) { + try { + getCollection().insertOne( new Document( bson.toMap() ) ); + } catch ( MongoException e ) { + errorHandler.error( "Failed to insert document to MongoDB", e, + ErrorCode.WRITE_FAILURE ); + } + } + } + + /** + * Returns true if appender was successfully initialized. If this method returns false, the + * appender should not attempt to log events. + * + * @return true if appender was successfully initialized + */ + public boolean isInitialized() { + return initialized; + } + + /** + * @return The MongoDB collection to which events are logged. + */ + protected MongoCollection getCollection() { + if ( concern == null ) { + return collection; + } + + return collection.withWriteConcern( concern ); + } + + /** + * Returns a List of ServerAddress objects for each host specified in the hostname property. + * Returns an empty list if configuration is detected to be invalid, e.g.: + *
    + *
  • Port property doesn't contain either one port or one port per host
  • + *
  • After parsing port property to integers, there isn't either one port or one port per host + *
  • + *
+ * + * @param hostname Blank space delimited hostnames + * @param port Blank space delimited ports. Must specify one port for all hosts or a port per host. + * + * @return List of ServerAddresses to connect to + */ + private List getServerAddresses( String hostname, String port ) { + List addresses = new ArrayList (); + + String[] hosts = hostname.split( " " ); + String[] ports = port.split( " " ); + + if ( ports.length != 1 && ports.length != hosts.length ) { + errorHandler.error( + "MongoDB appender port property must contain one port or a port per host", + null, ErrorCode.ADDRESS_PARSE_FAILURE ); + } else { + List portNums = getPortNumbers( ports ); + // Validate number of ports again after parsing + if ( portNums.size() != 1 && portNums.size() != hosts.length ) { + errorHandler + .error( "MongoDB appender port property must contain one port or a valid port per host", + null, ErrorCode.ADDRESS_PARSE_FAILURE ); + } else { + boolean onePort = ( portNums.size() == 1 ); + + int i = 0; + for ( String host : hosts ) { + int portNum = ( onePort ) ? portNums.get( 0 ) : portNums.get( i ); + addresses.add( new ServerAddress( host.trim(), portNum ) ); + i++; + } + } + } + return addresses; + } + + private List getPortNumbers( String[] ports ) { + List portNumbers = new ArrayList <>(); + + for ( String port : ports ) { + try { + Integer portNum = Integer.valueOf( port.trim() ); + if ( portNum < 0 ) { + errorHandler.error( + "MongoDB appender port property can't contain a negative integer", + null, ErrorCode.ADDRESS_PARSE_FAILURE ); + } else { + portNumbers.add( portNum ); + } + } catch ( NumberFormatException e ) { + errorHandler.error( + "MongoDB appender can't parse a port property value into an integer", e, + ErrorCode.ADDRESS_PARSE_FAILURE ); + } + + } + + return portNumbers; + } } diff --git a/src/main/java/org/log4mongo/MongoDbPatternLayout.java b/src/main/java/org/log4mongo/MongoDbPatternLayout.java index edf5a97..85b1506 100644 --- a/src/main/java/org/log4mongo/MongoDbPatternLayout.java +++ b/src/main/java/org/log4mongo/MongoDbPatternLayout.java @@ -22,101 +22,107 @@ import org.apache.log4j.helpers.PatternParser; import org.apache.log4j.spi.LoggingEvent; + /** * PatternLayout that must be used or extended when logging with MongoDbPatternLayoutAppender. *

* Much of the PatternLayout functionality needed to be re-implemented, because double quotes and \ * need to be escaped in the formatted String. The formatted String will later be parsed as a JSON * document, so quotes in the values must be escaped. - * + * * @author Robert Stewart (robert@wombatnation.com) */ public class MongoDbPatternLayout extends PatternLayout { - private StringBuffer buf = new StringBuffer(BUF_SIZE); - private StringBuilder builder = new StringBuilder(BUF_SIZE); - private String conversionPattern; - private PatternConverter headConverter; - - public MongoDbPatternLayout() { - this(DEFAULT_CONVERSION_PATTERN); - } - - public MongoDbPatternLayout(String pattern) { - this.conversionPattern = pattern; - headConverter = createPatternParser( - (pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse(); - } - - @Override - public void setConversionPattern(String conversionPattern) { - this.conversionPattern = conversionPattern; - headConverter = createPatternParser(conversionPattern).parse(); - } - - @Override - public String getConversionPattern() { - return conversionPattern; - } - - @Override - public PatternParser createPatternParser(String pattern) { - PatternParser parser; - if (pattern == null) - parser = new PatternParser(DEFAULT_CONVERSION_PATTERN); - else - parser = new PatternParser(pattern); - - return parser; - } - - /** - * Produces a formatted string as specified by the conversion pattern. - *

- * The PatternConverter expects to append to a StringBuffer. However, for converters other than - * a LiteralPatternConverter, double quotes need to be escaped in the characters appended to the - * StringBuffer. - */ - @Override - public String format(LoggingEvent event) { - // Reset working StringBuilder - if (builder.capacity() > MAX_CAPACITY) { - builder = new StringBuilder(BUF_SIZE); - } else { - builder.setLength(0); - } - - PatternConverter c = headConverter; - - while (c != null) { - if (buf.capacity() > MAX_CAPACITY) { - buf = new StringBuffer(BUF_SIZE); - } else { - buf.setLength(0); - } - c.format(buf, event); - - // Escape double quotes and \ in String generated by converters - // other than a LiteralPatternConverter. Can't use "instance of" - // because class is private. - if (c.getClass().getSimpleName().equals("LiteralPatternConverter")) { - builder.append(buf); - } else { - char[] chars = buf.toString().toCharArray(); - int pos = 0; - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '\"') { - builder.append(chars, pos, i - pos).append("\\\""); - pos = i + 1; - } else if (chars[i] == '\\') { - builder.append(chars, pos, i - pos).append("\\\\"); - pos = i + 1; - } - } - builder.append(chars, pos, chars.length - pos); - } - - c = c.next; - } - return builder.toString(); - } + + private StringBuffer buf = new StringBuffer( BUF_SIZE ); + + private StringBuilder builder = new StringBuilder( BUF_SIZE ); + + private String conversionPattern; + + private PatternConverter headConverter; + + public MongoDbPatternLayout() { + this( DEFAULT_CONVERSION_PATTERN ); + } + + public MongoDbPatternLayout( String pattern ) { + this.conversionPattern = pattern; + headConverter = createPatternParser( + ( pattern == null ) ? DEFAULT_CONVERSION_PATTERN : pattern ).parse(); + } + + @Override + public void setConversionPattern( String conversionPattern ) { + this.conversionPattern = conversionPattern; + headConverter = createPatternParser( conversionPattern ).parse(); + } + + @Override + public String getConversionPattern() { + return conversionPattern; + } + + @Override + public PatternParser createPatternParser( String pattern ) { + PatternParser parser; + if ( pattern == null ) { + parser = new PatternParser( DEFAULT_CONVERSION_PATTERN ); + } else { + parser = new PatternParser( pattern ); + } + + return parser; + } + + /** + * Produces a formatted string as specified by the conversion pattern. + *

+ * The PatternConverter expects to append to a StringBuffer. However, for converters other than + * a LiteralPatternConverter, double quotes need to be escaped in the characters appended to the + * StringBuffer. + */ + @Override + public String format( LoggingEvent event ) { + // Reset working StringBuilder + if ( builder.capacity() > MAX_CAPACITY ) { + builder = new StringBuilder( BUF_SIZE ); + } else { + builder.setLength( 0 ); + } + + PatternConverter c = headConverter; + + while ( c != null ) { + if ( buf.capacity() > MAX_CAPACITY ) { + buf = new StringBuffer( BUF_SIZE ); + } else { + buf.setLength( 0 ); + } + c.format( buf, event ); + + // Escape double quotes and \ in String generated by converters + // other than a LiteralPatternConverter. Can't use "instance of" + // because class is private. + if ( c.getClass().getSimpleName().equals( "LiteralPatternConverter" ) ) { + builder.append( buf ); + } else { + char[] chars = buf.toString().toCharArray(); + int pos = 0; + for ( int i = 0; i < chars.length; i++ ) { + if ( chars[ i ] == '\"' ) { + builder.append( chars, pos, i - pos ).append( "\\\"" ); + pos = i + 1; + } else if ( chars[ i ] == '\\' ) { + builder.append( chars, pos, i - pos ).append( "\\\\" ); + pos = i + 1; + } + } + builder.append( chars, pos, chars.length - pos ); + } + + c = c.next; + } + return builder.toString(); + } } diff --git a/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java b/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java index e9521da..f46e339 100644 --- a/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java +++ b/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java @@ -17,12 +17,13 @@ package org.log4mongo; -import org.apache.log4j.spi.ErrorCode; -import org.apache.log4j.spi.LoggingEvent; - import com.mongodb.DBObject; import com.mongodb.MongoException; import com.mongodb.util.JSON; +import org.apache.log4j.spi.ErrorCode; +import org.apache.log4j.spi.LoggingEvent; +import org.bson.Document; + /** * A Log4J Appender that uses a PatternLayout to write log events into a MongoDB database. @@ -38,50 +39,50 @@ * The appender does not create any indexes on the data that's stored. If query performance * is required, indexes must be created externally (e.g., in the mongo shell or an external * reporting application). - * + * * @author Robert Stewart (robert@wombatnation.com) - * @see Log4J - * Appender Interface + * @see Log4J Appender + * Interface * @see MongoDB */ public class MongoDbPatternLayoutAppender extends MongoDbAppender { - @Override - public boolean requiresLayout() { - return (true); - } - /** - * Inserts a BSON representation of a LoggingEvent into a MongoDB collection. A PatternLayout is - * used to format a JSON document containing data available in the LoggingEvent and, optionally, - * additional data returned by custom PatternConverters. - *

- * The format of the JSON document is specified in the .layout.ConversionPattern property. - * - * @param loggingEvent - * The LoggingEvent that will be formatted and stored in MongoDB - */ - @Override - protected void append(final LoggingEvent loggingEvent) { - if (isInitialized()) { - DBObject bson = null; - String json = layout.format(loggingEvent); + @Override + public boolean requiresLayout() { + return ( true ); + } + + /** + * Inserts a BSON representation of a LoggingEvent into a MongoDB collection. A PatternLayout is + * used to format a JSON document containing data available in the LoggingEvent and, optionally, + * additional data returned by custom PatternConverters. + *

+ * The format of the JSON document is specified in the .layout.ConversionPattern property. + * + * @param loggingEvent The LoggingEvent that will be formatted and stored in MongoDB + */ + @Override + protected void append( final LoggingEvent loggingEvent ) { + if ( isInitialized() ) { + DBObject bson = null; + String json = layout.format( loggingEvent ); - if (json.length() > 0) { - Object obj = JSON.parse(json); - if (obj instanceof DBObject) { - bson = (DBObject) obj; - } - } + if ( json.length() > 0 ) { + Object obj = JSON.parse( json ); + if ( obj instanceof DBObject ) { + bson = (DBObject) obj; + } + } - if (bson != null) { - try { - getCollection().insert(bson); - } catch (MongoException e) { - errorHandler.error("Failed to insert document to MongoDB", e, - ErrorCode.WRITE_FAILURE); - } - } - } - } + if ( bson != null ) { + try { + getCollection().insertOne( new Document( bson.toMap() ) ); + } catch ( MongoException e ) { + errorHandler.error( "Failed to insert document to MongoDB", e, + ErrorCode.WRITE_FAILURE ); + } + } + } + } } diff --git a/src/main/java/org/log4mongo/contrib/HostInfoPatternLayout.java b/src/main/java/org/log4mongo/contrib/HostInfoPatternLayout.java index a3f46b9..50b6ff4 100644 --- a/src/main/java/org/log4mongo/contrib/HostInfoPatternLayout.java +++ b/src/main/java/org/log4mongo/contrib/HostInfoPatternLayout.java @@ -18,25 +18,26 @@ package org.log4mongo.contrib; import org.apache.log4j.helpers.PatternParser; - import org.log4mongo.MongoDbPatternLayout; + public class HostInfoPatternLayout extends MongoDbPatternLayout { - public HostInfoPatternLayout() { - } + public HostInfoPatternLayout() { + } - public HostInfoPatternLayout(String pattern) { - super(pattern); - } + public HostInfoPatternLayout( String pattern ) { + super( pattern ); + } - public PatternParser createPatternParser(String pattern) { - PatternParser parser; - if (pattern == null) - parser = new HostInfoPatternParser(DEFAULT_CONVERSION_PATTERN); - else - parser = new HostInfoPatternParser(pattern); + public PatternParser createPatternParser( String pattern ) { + PatternParser parser; + if ( pattern == null ) { + parser = new HostInfoPatternParser( DEFAULT_CONVERSION_PATTERN ); + } else { + parser = new HostInfoPatternParser( pattern ); + } - return parser; - } + return parser; + } } diff --git a/src/main/java/org/log4mongo/contrib/HostInfoPatternParser.java b/src/main/java/org/log4mongo/contrib/HostInfoPatternParser.java index 15c97e4..9a30dc5 100644 --- a/src/main/java/org/log4mongo/contrib/HostInfoPatternParser.java +++ b/src/main/java/org/log4mongo/contrib/HostInfoPatternParser.java @@ -15,6 +15,11 @@ package org.log4mongo.contrib; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.PatternConverter; +import org.apache.log4j.helpers.PatternParser; +import org.apache.log4j.spi.LoggingEvent; + import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.UnknownHostException; @@ -22,10 +27,6 @@ import java.util.HashMap; import java.util.Map; -import org.apache.log4j.helpers.LogLog; -import org.apache.log4j.helpers.PatternConverter; -import org.apache.log4j.helpers.PatternParser; -import org.apache.log4j.spi.LoggingEvent; /** * PatternParser that adds pattern converters for logging useful @@ -37,115 +38,123 @@ * */ public class HostInfoPatternParser extends PatternParser { - static final char HOST_NAME = 'H'; - static final char VM_NAME = 'V'; - static final char IP_ADDRESS = 'I'; - static final Map converters; - static { - Map tmp = new HashMap(); - tmp.put(String.valueOf(HOST_NAME), new HostPatternConverter()); - tmp.put(String.valueOf(VM_NAME), new VMNamePatternConverter()); - tmp.put(String.valueOf(IP_ADDRESS), new IPAddressPatternConverter()); - - converters = Collections.unmodifiableMap(tmp); - } - - public HostInfoPatternParser(String pattern) { - super(pattern); - } - - /** - * This method is called on each pattern converter character while the - * PatternParser superclass is parsing the pattern. If the character is for - * a custom converter handled by this PatternParser subclass, this class - * adds the appropriate converter to a LinkedList of converters. If not, it - * allows the superclass to handle the converter character. - * - * @see org.apache.log4j.helpers.PatternParser#finalizeConverter(char) - */ - public void finalizeConverter(char formatChar) { - PatternConverter pc = null; - switch (formatChar) { - case HOST_NAME: - pc = HostInfoPatternParser.converters.get(String.valueOf(HOST_NAME)); - currentLiteral.setLength(0); - addConverter(pc); - break; - case VM_NAME: - pc = HostInfoPatternParser.converters.get(String.valueOf(VM_NAME)); - currentLiteral.setLength(0); - addConverter(pc); - break; - case IP_ADDRESS: - pc = HostInfoPatternParser.converters.get(String.valueOf(IP_ADDRESS)); - currentLiteral.setLength(0); - addConverter(pc); - break; - - default: - super.finalizeConverter(formatChar); - } - } - - /** - * Custom PatternConverter for replacing converter character 'H' with - * the host name. - */ - private static class HostPatternConverter extends PatternConverter { - private String hostname = ""; - - HostPatternConverter() { - super(); - - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - LogLog.warn(e.getMessage()); - } - } - - public String convert(LoggingEvent event) { - return hostname; - } - } - - /** - * Custom PatternConverter for replacing converter character 'V' with - * the VM name of the JVM, usually formatted as pid@host. - */ - private static class VMNamePatternConverter extends PatternConverter { - private String process = ""; - - VMNamePatternConverter() { - super(); - - process = ManagementFactory.getRuntimeMXBean().getName(); - } - - public String convert(LoggingEvent event) { - return process; - } - } - - /** - * Custom PatternConverter for replacing converter character 'I' with - * the IP Address. - */ - private static class IPAddressPatternConverter extends PatternConverter { - private String ipaddress = ""; - - IPAddressPatternConverter() { - super(); - - try { - ipaddress = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - LogLog.warn(e.getMessage()); - } - } - - public String convert(LoggingEvent event) { - return ipaddress; - } - } + + static final char HOST_NAME = 'H'; + + static final char VM_NAME = 'V'; + + static final char IP_ADDRESS = 'I'; + + static final Map converters; + + static { + Map tmp = new HashMap (); + tmp.put( String.valueOf( HOST_NAME ), new HostPatternConverter() ); + tmp.put( String.valueOf( VM_NAME ), new VMNamePatternConverter() ); + tmp.put( String.valueOf( IP_ADDRESS ), new IPAddressPatternConverter() ); + + converters = Collections.unmodifiableMap( tmp ); + } + + public HostInfoPatternParser( String pattern ) { + super( pattern ); + } + + /** + * This method is called on each pattern converter character while the + * PatternParser superclass is parsing the pattern. If the character is for + * a custom converter handled by this PatternParser subclass, this class + * adds the appropriate converter to a LinkedList of converters. If not, it + * allows the superclass to handle the converter character. + * + * @see org.apache.log4j.helpers.PatternParser#finalizeConverter(char) + */ + public void finalizeConverter( char formatChar ) { + PatternConverter pc = null; + switch ( formatChar ) { + case HOST_NAME: + pc = HostInfoPatternParser.converters.get( String.valueOf( HOST_NAME ) ); + currentLiteral.setLength( 0 ); + addConverter( pc ); + break; + case VM_NAME: + pc = HostInfoPatternParser.converters.get( String.valueOf( VM_NAME ) ); + currentLiteral.setLength( 0 ); + addConverter( pc ); + break; + case IP_ADDRESS: + pc = HostInfoPatternParser.converters.get( String.valueOf( IP_ADDRESS ) ); + currentLiteral.setLength( 0 ); + addConverter( pc ); + break; + + default: + super.finalizeConverter( formatChar ); + } + } + + /** + * Custom PatternConverter for replacing converter character 'H' with + * the host name. + */ + private static class HostPatternConverter extends PatternConverter { + + private String hostname = ""; + + HostPatternConverter() { + super(); + + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch ( UnknownHostException e ) { + LogLog.warn( e.getMessage() ); + } + } + + public String convert( LoggingEvent event ) { + return hostname; + } + } + + /** + * Custom PatternConverter for replacing converter character 'V' with + * the VM name of the JVM, usually formatted as pid@host. + */ + private static class VMNamePatternConverter extends PatternConverter { + + private String process = ""; + + VMNamePatternConverter() { + super(); + + process = ManagementFactory.getRuntimeMXBean().getName(); + } + + public String convert( LoggingEvent event ) { + return process; + } + } + + /** + * Custom PatternConverter for replacing converter character 'I' with + * the IP Address. + */ + private static class IPAddressPatternConverter extends PatternConverter { + + private String ipaddress = ""; + + IPAddressPatternConverter() { + super(); + + try { + ipaddress = InetAddress.getLocalHost().getHostAddress(); + } catch ( UnknownHostException e ) { + LogLog.warn( e.getMessage() ); + } + } + + public String convert( LoggingEvent event ) { + return ipaddress; + } + } } diff --git a/src/test/java/org/log4mongo/CustomPatternLayout.java b/src/test/java/org/log4mongo/CustomPatternLayout.java index 1f50436..9e8e23d 100644 --- a/src/test/java/org/log4mongo/CustomPatternLayout.java +++ b/src/test/java/org/log4mongo/CustomPatternLayout.java @@ -17,24 +17,27 @@ import org.apache.log4j.helpers.PatternParser; + /** * Example PatternLayout that specifies a custom PatternParser. */ public class CustomPatternLayout extends MongoDbPatternLayout { - public CustomPatternLayout() { - } - - public CustomPatternLayout(String pattern) { - super(pattern); - } - - public PatternParser createPatternParser(String pattern) { - PatternParser parser; - if (pattern == null) - parser = new CustomPatternParser(DEFAULT_CONVERSION_PATTERN); - else - parser = new CustomPatternParser(pattern); - - return parser; - } + + public CustomPatternLayout() { + } + + public CustomPatternLayout( String pattern ) { + super( pattern ); + } + + public PatternParser createPatternParser( String pattern ) { + PatternParser parser; + if ( pattern == null ) { + parser = new CustomPatternParser( DEFAULT_CONVERSION_PATTERN ); + } else { + parser = new CustomPatternParser( pattern ); + } + + return parser; + } } diff --git a/src/test/java/org/log4mongo/CustomPatternParser.java b/src/test/java/org/log4mongo/CustomPatternParser.java index a557a23..dcbc12c 100644 --- a/src/test/java/org/log4mongo/CustomPatternParser.java +++ b/src/test/java/org/log4mongo/CustomPatternParser.java @@ -20,55 +20,58 @@ import org.apache.log4j.helpers.PatternParser; import org.apache.log4j.spi.LoggingEvent; + /** * Simple PatternParser that adds a single PatternConverter for logging some extra info. The extra * info returned from convert(LoggingEvent) will replace %e in the pattern when an event is logged * if the logging style is set to PatternLayout. */ public class CustomPatternParser extends PatternParser { - static final char EXTRA_CHAR = 'e'; - public CustomPatternParser(String pattern) { - super(pattern); - } + static final char EXTRA_CHAR = 'e'; + + public CustomPatternParser( String pattern ) { + super( pattern ); + } + + /** + * This method is called on each pattern converter character while the PatternParser superclass + * is parsing the pattern. If the character is for a custom converter handled by this + * PatternParser subclass, this class adds the appropriate converter to a LinkedList of + * converters. If not, it allows the superclass to handle the converter character. + * + * @see org.apache.log4j.helpers.PatternParser#finalizeConverter(char) + */ + public void finalizeConverter( char formatChar ) { + PatternConverter pc = null; + switch ( formatChar ) { + case EXTRA_CHAR: + pc = new ExtraInfoPatternConverter( formattingInfo ); + currentLiteral.setLength( 0 ); + addConverter( pc ); + break; - /** - * This method is called on each pattern converter character while the PatternParser superclass - * is parsing the pattern. If the character is for a custom converter handled by this - * PatternParser subclass, this class adds the appropriate converter to a LinkedList of - * converters. If not, it allows the superclass to handle the converter character. - * - * @see org.apache.log4j.helpers.PatternParser#finalizeConverter(char) - */ - public void finalizeConverter(char formatChar) { - PatternConverter pc = null; - switch (formatChar) { - case EXTRA_CHAR: - pc = new ExtraInfoPatternConverter(formattingInfo); - currentLiteral.setLength(0); - addConverter(pc); - break; + default: + super.finalizeConverter( formatChar ); + } + } - default: - super.finalizeConverter(formatChar); - } - } + /** + * Custom PatternConverter for replacing a converter character (in this case, the character + * happens to be 'e') with some additional info. For a real application, this might be a session + * ID or some other piece of meaningful info. + */ + private class ExtraInfoPatternConverter extends PatternConverter { - /** - * Custom PatternConverter for replacing a converter character (in this case, the character - * happens to be 'e') with some additional info. For a real application, this might be a session - * ID or some other piece of meaningful info. - */ - private class ExtraInfoPatternConverter extends PatternConverter { - ExtraInfoPatternConverter(FormattingInfo formatInfo) { - super(formatInfo); - } + ExtraInfoPatternConverter( FormattingInfo formatInfo ) { + super( formatInfo ); + } - /** - * Returns the string that will replace %e in the pattern string. - */ - public String convert(LoggingEvent event) { - return "useful info"; - } - } + /** + * Returns the string that will replace %e in the pattern string. + */ + public String convert( LoggingEvent event ) { + return "useful info"; + } + } } diff --git a/src/test/java/org/log4mongo/TestExtendedMongoDbAppender.java b/src/test/java/org/log4mongo/TestExtendedMongoDbAppender.java index 504f31e..247af0a 100644 --- a/src/test/java/org/log4mongo/TestExtendedMongoDbAppender.java +++ b/src/test/java/org/log4mongo/TestExtendedMongoDbAppender.java @@ -1,179 +1,192 @@ -/* - * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.log4mongo; - -import com.mongodb.BasicDBObjectBuilder; -import com.mongodb.DBCollection; -import com.mongodb.DBObject; -import com.mongodb.Mongo; -import org.apache.log4j.Logger; -import org.apache.log4j.MDC; -import org.apache.log4j.PropertyConfigurator; -import org.junit.*; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; - -/** - * JUnit unit tests for ExtendedMongoDbAppender. - *

- * Note: these tests require that a MongoDB server is running, and (by default) - * assumes that server is listening on the default port (27017) on localhost. - * - * @author Mick Knutson (http://www.baselogic.com) - */ -public class TestExtendedMongoDbAppender { - private final static Logger log = Logger.getLogger(TestExtendedMongoDbAppender.class); - - private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; - private final static int TEST_MONGO_SERVER_PORT = 27017; - private final static String TEST_DATABASE_NAME = "log4mongotest"; - private final static String TEST_COLLECTION_NAME = "logevents"; - - private final static String MONGODB_APPENDER_NAME = "MongoDB"; - - private final static String LOG4J_PROPS = "src/test/resources/log4j_extended.properties"; - //private final static String LOG4J_PROPS = "src/test/resources/log4j_extended.xml"; - - private final Mongo mongo; - private final ExtendedMongoDbAppender appender; - private DBCollection collection; - - public TestExtendedMongoDbAppender() throws Exception { - PropertyConfigurator.configure(LOG4J_PROPS); - mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - appender = (ExtendedMongoDbAppender) Logger.getRootLogger().getAppender( - MONGODB_APPENDER_NAME); - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, - TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, - TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @Before - public void setUp() throws Exception { - // Ensure both the appender and the JUnit test use the same collection - // object - provides consistency across reads (JUnit) & writes (Log4J) - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection( - TEST_COLLECTION_NAME); - collection.drop(); - appender.setCollection(collection); - - mongo.getDB(TEST_DATABASE_NAME).requestStart(); - } - - @After - public void tearDown() throws Exception { - mongo.getDB(TEST_DATABASE_NAME).requestDone(); - } - - @Test - public void testInitialized() throws Exception { - if (!appender.isInitialized()) - fail(); - } - - @Test - public void testRootLevelProperties() throws Exception { - assertEquals(0L, countLogEntries()); - - Map obj = new HashMap() { - { - put("key1", "value1"); - put("key2", "value2"); - } - }; - - //slf4j style: log.warn("Testing Object in Message: {}", obj); - log.warn("Testing Object in Message: " + obj); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Testing Object in Message: {key1=value1, key2=value2}", entry.get("message")); - } - - @Test - public void testObjectAsMessage() throws Exception { - assertEquals(0L, countLogEntries()); - - log.warn("Testing Object in Message"); - - long appNameCount = countLogEntriesWhere(BasicDBObjectBuilder.start().add("applicationName", "MyProject").get()); - - assertEquals(1L, appNameCount); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("Development", entry.get("eventType")); - } - - @Test - public void testMdcProperties() throws Exception { - assertEquals(0L, countLogEntries()); - - MDC.put("uuid", "1000"); - MDC.put("recordAssociation", "xyz"); - - log.warn("Testing MDC Properties"); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Testing MDC Properties", entry.get("message")); - assertNotNull(entry.get("properties")); - DBObject mdcProperties = (DBObject) entry.get("properties"); - - assertNotNull(mdcProperties.get("uuid")); - assertNotNull(mdcProperties.get("recordAssociation")); - } - - //-----------------------------------------------------------------------// - // Private methods - //-----------------------------------------------------------------------// - private long countLogEntries() { - return (collection.getCount()); - } - - private long countLogEntriesAtLevel(final String level) { - return (countLogEntriesWhere(BasicDBObjectBuilder.start().add("level", - level.toUpperCase()).get())); - } - - private long countLogEntriesWhere(final DBObject whereClause) { - return collection.getCount(whereClause); - } -} +/* + * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.log4mongo; + +import com.mongodb.DBObject; +import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +import org.apache.log4j.PropertyConfigurator; +import org.bson.Document; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + + +/** + * JUnit unit tests for ExtendedMongoDbAppender. + *

+ * Note: these tests require that a MongoDB server is running, and (by default) + * assumes that server is listening on the default port (27017) on localhost. + * + * @author Mick Knutson (http://www.baselogic.com) + */ +public class TestExtendedMongoDbAppender { + + private final static Logger log = Logger.getLogger( TestExtendedMongoDbAppender.class ); + + private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; + + private final static int TEST_MONGO_SERVER_PORT = 27017; + + private final static String TEST_DATABASE_NAME = "log4mongotest"; + + private final static String TEST_COLLECTION_NAME = "logevents"; + + private final static String MONGODB_APPENDER_NAME = "MongoDB"; + + private final static String LOG4J_PROPS = "src/test/resources/log4j_extended.properties"; + //private final static String LOG4J_PROPS = "src/test/resources/log4j_extended.xml"; + + private final MongoClient mongo; + + private final ExtendedMongoDbAppender appender; + + private MongoCollection collection; + + public TestExtendedMongoDbAppender() throws Exception { + PropertyConfigurator.configure( LOG4J_PROPS ); + mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + appender = (ExtendedMongoDbAppender) Logger.getRootLogger().getAppender( + MONGODB_APPENDER_NAME ); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + Mongo mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, + TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + Mongo mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, + TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @Before + public void setUp() throws Exception { + // Ensure both the appender and the JUnit test use the same collection + // object - provides consistency across reads (JUnit) & writes (Log4J) + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( + TEST_COLLECTION_NAME ); + collection.drop(); + appender.setCollection( collection ); + } + + @Test + public void testInitialized() throws Exception { + if ( !appender.isInitialized() ) { + fail(); + } + } + + @Test + public void testRootLevelProperties() throws Exception { + assertEquals( 0L, countLogEntries() ); + + Map obj = new HashMap () { + + { + put( "key1", "value1" ); + put( "key2", "value2" ); + } + }; + + //slf4j style: log.warn("Testing Object in Message: {}", obj); + log.warn( "Testing Object in Message: " + obj ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Testing Object in Message: {key1=value1, key2=value2}", entry.get( "message" ) ); + } + } + + @Test + public void testObjectAsMessage() throws Exception { + assertEquals( 0L, countLogEntries() ); + + log.warn( "Testing Object in Message" ); + + long appNameCount = countLogEntriesWhere( Document.parse( "{ 'applicationName' : 'MyProject' }" ) ); + + assertEquals( 1L, appNameCount ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "Development", entry.get( "eventType" ) ); + } + } + + @Test + public void testMdcProperties() throws Exception { + assertEquals( 0L, countLogEntries() ); + + MDC.put( "uuid", "1000" ); + MDC.put( "recordAssociation", "xyz" ); + + log.warn( "Testing MDC Properties" ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Testing MDC Properties", entry.get( "message" ) ); + assertNotNull( entry.get( "properties" ) ); + DBObject mdcProperties = (DBObject) entry.get( "properties" ); + + assertNotNull( mdcProperties.get( "uuid" ) ); + assertNotNull( mdcProperties.get( "recordAssociation" ) ); + } + } + + //-----------------------------------------------------------------------// + // Private methods + //-----------------------------------------------------------------------// + private long countLogEntries() { + return ( collection.count() ); + } + + private long countLogEntriesAtLevel( final String level ) { + return ( countLogEntriesWhere( Document.parse( "{ 'level' : '" + level.toUpperCase() + "' }" ) ) ); + } + + private long countLogEntriesWhere( final Document whereClause ) { + return collection.count( whereClause ); + } + +} diff --git a/src/test/java/org/log4mongo/TestLoggingEventBsonifierImpl.java b/src/test/java/org/log4mongo/TestLoggingEventBsonifierImpl.java index 1dd56e5..f3b3da2 100644 --- a/src/test/java/org/log4mongo/TestLoggingEventBsonifierImpl.java +++ b/src/test/java/org/log4mongo/TestLoggingEventBsonifierImpl.java @@ -1,33 +1,34 @@ package org.log4mongo; -import static org.junit.Assert.assertEquals; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.junit.Test; import java.lang.reflect.InvocationTargetException; -import org.junit.Test; +import static org.junit.Assert.assertEquals; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; public class TestLoggingEventBsonifierImpl { - @Test - public void testStringBuffer() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - LoggingEventBsonifierImplSubclass bsonifier = new LoggingEventBsonifierImplSubclass(); - DBObject bson = new BasicDBObject(); - String key = "thekey"; - StringBuffer sb = new StringBuffer("thevalue"); - bsonifier.publicNullSafePut(bson, key, sb); - String retrievedValue = (String) bson.get(key); - assertEquals(sb.toString(), retrievedValue); - } - - // Create a subclass so I can test a protected method - // Replace this after extending Privateer to support superclasses in method signature - public class LoggingEventBsonifierImplSubclass extends LoggingEventBsonifierImpl { - public void publicNullSafePut(DBObject bson, final String key, final Object value) { - nullSafePut(bson, key, value); - } - - } + @Test + public void testStringBuffer() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + LoggingEventBsonifierImplSubclass bsonifier = new LoggingEventBsonifierImplSubclass(); + DBObject bson = new BasicDBObject(); + String key = "thekey"; + StringBuffer sb = new StringBuffer( "thevalue" ); + bsonifier.publicNullSafePut( bson, key, sb ); + String retrievedValue = (String) bson.get( key ); + assertEquals( sb.toString(), retrievedValue ); + } + + // Create a subclass so I can test a protected method + // Replace this after extending Privateer to support superclasses in method signature + public class LoggingEventBsonifierImplSubclass extends LoggingEventBsonifierImpl { + + public void publicNullSafePut( DBObject bson, final String key, final Object value ) { + nullSafePut( bson, key, value ); + } + + } } diff --git a/src/test/java/org/log4mongo/TestMongoDbAppender.java b/src/test/java/org/log4mongo/TestMongoDbAppender.java index 648fc9c..fb0ed8a 100644 --- a/src/test/java/org/log4mongo/TestMongoDbAppender.java +++ b/src/test/java/org/log4mongo/TestMongoDbAppender.java @@ -1,317 +1,320 @@ -/* - * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.log4mongo; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.lang.management.ManagementFactory; -import java.net.InetAddress; - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObjectBuilder; -import com.mongodb.DBCollection; -import com.mongodb.DBObject; -import com.mongodb.Mongo; - -/** - * JUnit unit tests for MongoDbAppender. - * - * Note: these tests require that a MongoDB server is running, and (by default) assumes that server - * is listening on the default port (27017) on localhost. - * - * @author Peter Monks (pmonks@gmail.com) - */ -public class TestMongoDbAppender { - private final static Logger log = Logger.getLogger(TestMongoDbAppender.class); - - private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; - private final static int TEST_MONGO_SERVER_PORT = 27017; - private final static String TEST_DATABASE_NAME = "log4mongotest"; - private final static String TEST_COLLECTION_NAME = "logevents"; - - private final static String MONGODB_APPENDER_NAME = "MongoDB"; - - private final static String LOG4J_PROPS = "src/test/resources/log4j.properties"; - - private final Mongo mongo; - private final MongoDbAppender appender; - private DBCollection collection; - - public TestMongoDbAppender() throws Exception { - PropertyConfigurator.configure(LOG4J_PROPS); - mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - appender = (MongoDbAppender) Logger.getRootLogger().getAppender(MONGODB_APPENDER_NAME); - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @Before - public void setUp() throws Exception { - // Ensure both the appender and the JUnit test use the same collection - // object - provides consistency across reads (JUnit) & writes (Log4J) - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - collection.drop(); - appender.setCollection(collection); - - mongo.getDB(TEST_DATABASE_NAME).requestStart(); - } - - @After - public void tearDown() throws Exception { - mongo.getDB(TEST_DATABASE_NAME).requestDone(); - } - - @Test - public void testInitialized() throws Exception { - if (!appender.isInitialized()) - fail(); - } - - @Test - public void testSingleLogEntry() throws Exception { - log.trace("Trace entry"); - - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("trace")); - assertEquals(0L, countLogEntriesAtLevel("debug")); - assertEquals(0L, countLogEntriesAtLevel("info")); - assertEquals(0L, countLogEntriesAtLevel("warn")); - assertEquals(0L, countLogEntriesAtLevel("error")); - assertEquals(0L, countLogEntriesAtLevel("fatal")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("TRACE", entry.get("level")); - assertEquals("Trace entry", entry.get("message")); - } - - @Test - public void testTimestampStoredNatively() throws Exception { - log.debug("Debug entry"); - - assertEquals(1L, countLogEntries()); - - // verify timestamp - presence and data type - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertTrue("Timestamp is not present in logged entry", entry.containsField("timestamp")); - assertTrue("Timestamp of logged entry is not stored as native date", - (entry.get("timestamp") instanceof java.util.Date)); - } - - @Test - public void testAllLevels() throws Exception { - log.trace("Trace entry"); - log.debug("Debug entry"); - log.info("Info entry"); - log.warn("Warn entry"); - log.error("Error entry"); - log.fatal("Fatal entry"); - - assertEquals(6L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("trace")); - assertEquals(1L, countLogEntriesAtLevel("debug")); - assertEquals(1L, countLogEntriesAtLevel("info")); - assertEquals(1L, countLogEntriesAtLevel("warn")); - assertEquals(1L, countLogEntriesAtLevel("error")); - assertEquals(1L, countLogEntriesAtLevel("fatal")); - } - - @Test - public void testLogWithException() throws Exception { - log.error("Error entry", new RuntimeException("Here is an exception!")); - - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("error")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("ERROR", entry.get("level")); - assertEquals("Error entry", entry.get("message")); - - // verify throwable presence and content - assertTrue("Throwable is not present in logged entry", entry.containsField("throwables")); - BasicDBList throwables = (BasicDBList) entry.get("throwables"); - assertEquals(1, throwables.size()); - - DBObject throwableEntry = (DBObject) throwables.get("0"); - assertTrue("Throwable message is not present in logged entry", - throwableEntry.containsField("message")); - assertEquals("Here is an exception!", throwableEntry.get("message")); - } - - @Test - public void testLogWithChainedExceptions() throws Exception { - Exception rootCause = new RuntimeException("I'm the real culprit!"); - - log.error("Error entry", new RuntimeException("I'm an innocent bystander.", rootCause)); - - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("error")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("ERROR", entry.get("level")); - assertEquals("Error entry", entry.get("message")); - - // verify throwable presence and content - assertTrue("Throwable is not present in logged entry", entry.containsField("throwables")); - BasicDBList throwables = (BasicDBList) entry.get("throwables"); - assertEquals(2, throwables.size()); - - DBObject rootEntry = (DBObject) throwables.get("0"); - assertTrue("Throwable message is not present in logged entry", - rootEntry.containsField("message")); - assertEquals("I'm an innocent bystander.", rootEntry.get("message")); - - DBObject chainedEntry = (DBObject) throwables.get("1"); - assertTrue("Throwable message is not present in logged entry", - chainedEntry.containsField("message")); - assertEquals("I'm the real culprit!", chainedEntry.get("message")); - } - - @Test - public void testQuotesInMessage() { - assertEquals(0L, countLogEntries()); - log.warn("Quotes\" \"embedded"); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Quotes\" \"embedded", entry.get("message")); - } - - @Test - public void testPerformance() throws Exception { - // Log one event to minimize start up effects on performance - log.warn("Warn entry"); - - long NUM_MESSAGES = 1000; - long now = System.currentTimeMillis(); - for (long i = 0; i < NUM_MESSAGES; i++) { - log.warn("Warn entry"); - } - long dur = System.currentTimeMillis() - now; - System.out.println("Milliseconds for MongoDbAppender to log " + NUM_MESSAGES + " messages:" - + dur); - assertEquals(NUM_MESSAGES + 1, countLogEntries()); - } - - @Test - public void testRegularLoggerRecordsLoggerNameCorrectly() { - log.info("From an unwrapped logger"); - - assertEquals(1, countLogEntries()); - assertEquals(1, countLogEntriesAtLevel("info")); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("loggerName.className", "TestMongoDbAppender").get())); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("class.className", "TestMongoDbAppender").get())); - } - - @Test - public void testWrappedLoggerRecordsLoggerNameCorrectly() { - WrappedLogger wrapped = new WrappedLogger(log); - wrapped.info("From a wrapped logger"); - - assertEquals(1, countLogEntries()); - assertEquals(1, countLogEntriesAtLevel("info")); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("loggerName.className", "TestMongoDbAppender").get())); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("class.className", "WrappedLogger").get())); - } - - @Test - public void testHostInfoRecords() throws Exception { - assertEquals(0L, countLogEntries()); - log.warn("Testing hostinfo"); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Testing hostinfo", entry.get("message")); - assertNotNull(entry.get("host")); - DBObject hostinfo = (DBObject) entry.get("host"); - assertNotNull(hostinfo.get("process")); - assertEquals(InetAddress.getLocalHost().getHostName(), hostinfo.get("name")); - assertEquals(ManagementFactory.getRuntimeMXBean().getName(), hostinfo.get("process")); - } - - @Test - /** - * Added to verify fix to GitHub issue #24. - */ - public void testLogObject() throws Exception { - Object object = new Object(); - - assertEquals(0L, countLogEntries()); - log.error(object); - assertEquals(1L, countLogEntries()); - } - - private long countLogEntries() { - return (collection.getCount()); - } - - private long countLogEntriesAtLevel(final String level) { - return (countLogEntriesWhere(BasicDBObjectBuilder.start().add("level", level.toUpperCase()) - .get())); - } - - private long countLogEntriesWhere(final DBObject whereClause) { - return collection.getCount(whereClause); - } -} +/* + * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.log4mongo; + +import com.mongodb.*; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.bson.Document; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; + +import static org.junit.Assert.*; + + +/** + * JUnit unit tests for MongoDbAppender. + *

+ * Note: these tests require that a MongoDB server is running, and (by default) assumes that server + * is listening on the default port (27017) on localhost. + * + * @author Peter Monks (pmonks@gmail.com) + */ +public class TestMongoDbAppender { + + private final static Logger log = Logger.getLogger( TestMongoDbAppender.class ); + + private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; + + private final static int TEST_MONGO_SERVER_PORT = 27017; + + private final static String TEST_DATABASE_NAME = "log4mongotest"; + + private final static String TEST_COLLECTION_NAME = "logevents"; + + private final static String MONGODB_APPENDER_NAME = "MongoDB"; + + private final static String LOG4J_PROPS = "src/test/resources/log4j.properties"; + + private final MongoClient mongo; + + private final MongoDbAppender appender; + + private MongoCollection collection; + + public TestMongoDbAppender() throws Exception { + PropertyConfigurator.configure( LOG4J_PROPS ); + mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + appender = (MongoDbAppender) Logger.getRootLogger().getAppender( MONGODB_APPENDER_NAME ); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @Before + public void setUp() throws Exception { + // Ensure both the appender and the JUnit test use the same collection + // object - provides consistency across reads (JUnit) & writes (Log4J) + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + collection.drop(); + appender.setCollection( collection ); + + } + + @Test + public void testInitialized() throws Exception { + if ( !appender.isInitialized() ) { + fail(); + } + } + + @Test + public void testSingleLogEntry() throws Exception { + log.trace( "Trace entry" ); + + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "trace" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "debug" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "info" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "warn" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "error" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "fatal" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "TRACE", entry.get( "level" ) ); + assertEquals( "Trace entry", entry.get( "message" ) ); + } + } + + @Test + public void testTimestampStoredNatively() throws Exception { + log.debug( "Debug entry" ); + + assertEquals( 1L, countLogEntries() ); + + // verify timestamp - presence and data type + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertTrue( "Timestamp is not present in logged entry", entry.containsField( "timestamp" ) ); + assertTrue( "Timestamp of logged entry is not stored as native date", + ( entry.get( "timestamp" ) instanceof java.util.Date ) ); + } + } + + @Test + public void testAllLevels() throws Exception { + log.trace( "Trace entry" ); + log.debug( "Debug entry" ); + log.info( "Info entry" ); + log.warn( "Warn entry" ); + log.error( "Error entry" ); + log.fatal( "Fatal entry" ); + + assertEquals( 6L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "trace" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "debug" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "info" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "warn" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "error" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "fatal" ) ); + } + + @Test + public void testLogWithException() throws Exception { + log.error( "Error entry", new RuntimeException( "Here is an exception!" ) ); + + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "error" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "ERROR", entry.get( "level" ) ); + assertEquals( "Error entry", entry.get( "message" ) ); + + // verify throwable presence and content + assertTrue( "Throwable is not present in logged entry", entry.containsField( "throwables" ) ); + BasicDBList throwables = (BasicDBList) entry.get( "throwables" ); + assertEquals( 1, throwables.size() ); + + DBObject throwableEntry = (DBObject) throwables.get( "0" ); + assertTrue( "Throwable message is not present in logged entry", + throwableEntry.containsField( "message" ) ); + assertEquals( "Here is an exception!", throwableEntry.get( "message" ) ); + } + } + + @Test + public void testLogWithChainedExceptions() throws Exception { + Exception rootCause = new RuntimeException( "I'm the real culprit!" ); + + log.error( "Error entry", new RuntimeException( "I'm an innocent bystander.", rootCause ) ); + + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "error" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "ERROR", entry.get( "level" ) ); + assertEquals( "Error entry", entry.get( "message" ) ); + + // verify throwable presence and content + assertTrue( "Throwable is not present in logged entry", entry.containsField( "throwables" ) ); + BasicDBList throwables = (BasicDBList) entry.get( "throwables" ); + assertEquals( 2, throwables.size() ); + + DBObject rootEntry = (DBObject) throwables.get( "0" ); + assertTrue( "Throwable message is not present in logged entry", + rootEntry.containsField( "message" ) ); + assertEquals( "I'm an innocent bystander.", rootEntry.get( "message" ) ); + + DBObject chainedEntry = (DBObject) throwables.get( "1" ); + assertTrue( "Throwable message is not present in logged entry", + chainedEntry.containsField( "message" ) ); + assertEquals( "I'm the real culprit!", chainedEntry.get( "message" ) ); + } + } + + @Test + public void testQuotesInMessage() { + assertEquals( 0L, countLogEntries() ); + log.warn( "Quotes\" \"embedded" ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Quotes\" \"embedded", entry.get( "message" ) ); + } + } + + @Test + public void testPerformance() throws Exception { + // Log one event to minimize start up effects on performance + log.warn( "Warn entry" ); + + long NUM_MESSAGES = 1000; + long now = System.currentTimeMillis(); + for ( long i = 0; i < NUM_MESSAGES; i++ ) { + log.warn( "Warn entry" ); + } + long dur = System.currentTimeMillis() - now; + System.out.println( "Milliseconds for MongoDbAppender to log " + NUM_MESSAGES + " messages:" + + dur ); + assertEquals( NUM_MESSAGES + 1, countLogEntries() ); + } + + @Test + public void testRegularLoggerRecordsLoggerNameCorrectly() { + log.info( "From an unwrapped logger" ); + + assertEquals( 1, countLogEntries() ); + assertEquals( 1, countLogEntriesAtLevel( "info" ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'loggerName.className' : 'TestMongoDbAppender' }" ) ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'class.className' : 'TestMongoDbAppender' }" ) ) ); + } + + @Test + public void testWrappedLoggerRecordsLoggerNameCorrectly() { + WrappedLogger wrapped = new WrappedLogger( log ); + wrapped.info( "From a wrapped logger" ); + + assertEquals( 1, countLogEntries() ); + assertEquals( 1, countLogEntriesAtLevel( "info" ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'loggerName.className' : 'TestMongoDbAppender' }" ) ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'class.className' : 'WrappedLogger' }" ) ) ); + } + + @Test + public void testHostInfoRecords() throws Exception { + assertEquals( 0L, countLogEntries() ); + log.warn( "Testing hostinfo" ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Testing hostinfo", entry.get( "message" ) ); + assertNotNull( entry.get( "host" ) ); + DBObject hostinfo = (DBObject) entry.get( "host" ); + assertNotNull( hostinfo.get( "process" ) ); + assertEquals( InetAddress.getLocalHost().getHostName(), hostinfo.get( "name" ) ); + assertEquals( ManagementFactory.getRuntimeMXBean().getName(), hostinfo.get( "process" ) ); + } + } + + @Test + /** + * Added to verify fix to GitHub issue #24. + */ + public void testLogObject() throws Exception { + Object object = new Object(); + + assertEquals( 0L, countLogEntries() ); + log.error( object ); + assertEquals( 1L, countLogEntries() ); + } + + private long countLogEntries() { + return ( collection.count() ); + } + + private long countLogEntriesAtLevel( final String level ) { + return ( countLogEntriesWhere( Document.parse( "{ 'level' : '" + level.toUpperCase() + "' }" ) ) ); + } + + private long countLogEntriesWhere( final Document whereClause ) { + return collection.count( whereClause ); + } +} diff --git a/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java b/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java index b191f3a..6f61d26 100644 --- a/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java +++ b/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java @@ -15,88 +15,84 @@ package org.log4mongo; -import org.apache.log4j.PropertyConfigurator; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; - -import com.mongodb.DBCollection; import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import org.apache.log4j.PropertyConfigurator; +import org.junit.*; + /** * Authentication-related JUnit unit tests for MongoDbAppender. - * + *

* Note: these tests require that a MongoDB server is running, and (by default) assumes that server * is listening on the default port (27017) on localhost. - * + * * @author Robert Stewart (robert@wombatnation.com) */ public class TestMongoDbAppenderAuth { - private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; - private final static int TEST_MONGO_SERVER_PORT = 27017; - private final static String TEST_DATABASE_NAME = "log4mongotestauth"; - private final static String TEST_COLLECTION_NAME = "logevents"; - - private final static String LOG4J_AUTH_PROPS = "src/test/resources/log4j_auth.properties"; - - private final static String username = "open"; - private final static String password = "sesame"; - - private final Mongo mongo; - private DBCollection collection; - - public TestMongoDbAppenderAuth() throws Exception { - mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @Before - public void setUp() throws Exception { - // Ensure both the appender and the JUnit test use the same - // collection object - provides consistency across reads (JUnit) & - // writes (Log4J) - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - collection.drop(); - - mongo.getDB(TEST_DATABASE_NAME).requestStart(); - } - - @After - public void tearDown() throws Exception { - mongo.getDB(TEST_DATABASE_NAME).requestDone(); - } - - /** - * Catching the RuntimeException thrown when Log4J calls the MongoDbAppender activeOptions() - * method isn't easy, since it is thrown in another thread. - */ - @Test(expected = RuntimeException.class) - @Ignore - public void testAppenderActivateNoAuth() { - PropertyConfigurator.configure(LOG4J_AUTH_PROPS); - } - - /** - * Adds the user to the test database before activating the appender. - */ - @Test - public void testAppenderActivateWithAuth() { - mongo.getDB(TEST_DATABASE_NAME).addUser(username, password.toCharArray()); - PropertyConfigurator.configure(LOG4J_AUTH_PROPS); - } + + private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; + + private final static int TEST_MONGO_SERVER_PORT = 27017; + + private final static String TEST_DATABASE_NAME = "log4mongotestauth"; + + private final static String TEST_COLLECTION_NAME = "logevents"; + + private final static String LOG4J_AUTH_PROPS = "src/test/resources/log4j_auth.properties"; + + private final static String username = "open"; + + private final static String password = "sesame"; + + private final MongoClient mongo; + + private MongoCollection collection; + + public TestMongoDbAppenderAuth() throws Exception { + mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @Before + public void setUp() throws Exception { + // Ensure both the appender and the JUnit test use the same + // collection object - provides consistency across reads (JUnit) & + // writes (Log4J) + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + collection.drop(); + + } + + /** + * Catching the RuntimeException thrown when Log4J calls the MongoDbAppender activeOptions() + * method isn't easy, since it is thrown in another thread. + */ + @Test( expected = RuntimeException.class ) + @Ignore + public void testAppenderActivateNoAuth() { + PropertyConfigurator.configure( LOG4J_AUTH_PROPS ); + } + + /** + * Adds the user to the test database before activating the appender. + */ + @Test + public void testAppenderActivateWithAuth() { + mongo.getDB( TEST_DATABASE_NAME ).addUser( username, password.toCharArray() ); + PropertyConfigurator.configure( LOG4J_AUTH_PROPS ); + } } diff --git a/src/test/java/org/log4mongo/TestMongoDbAppenderHosts.java b/src/test/java/org/log4mongo/TestMongoDbAppenderHosts.java index de10c22..420c45b 100644 --- a/src/test/java/org/log4mongo/TestMongoDbAppenderHosts.java +++ b/src/test/java/org/log4mongo/TestMongoDbAppenderHosts.java @@ -1,164 +1,167 @@ -/* - * Copyright (C) 2009 Robert Stewart (robert@wombatnation.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.log4mongo; - -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.List; -import java.util.Properties; - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.Test; - -import com.mongodb.Mongo; -import com.mongodb.ServerAddress; -import com.wombatnation.privateer.Privateer; - -/** - * JUnit unit tests for MongoDbAppender to verify proper behavior when setting the hostname and - * property properties. - * - * Note: these tests require that a MongoDB server is running, and (by default) assumes that - * server is listening on the default port (27017) on localhost. - *

- * Unless a replica set is configured with mongod instances listening on ports 27017 and 27018, - * errors will be logged. If a host is not listening on port 27018, testOneHostNonDefaultPort() will - * fail with an error. - * - * To test on localhost with a replica set, do the following (either starting mongod instances in - * separate terminals or start them with --fork): - *

    - *
  • $ mkdir -p /data/r0 - *
  • $ mkdir -p /data/r1 - *
  • $ mkdir -p /data/r2 - *
  • $ mongod --replSet foo --smallfiles --port 27017 --dbpath /data/r0 - *
  • $ mongod --replSet foo --smallfiles --port 27018 --dbpath /data/r1 - *
  • $ mongod --replSet foo --smallfiles --port 27019 --dbpath /data/r2 - *
  • $ mongo - *
  • >config = {"_id": "foo", members:[{_id: 0, host: 'localhost:27017'},{_id: 1, host: - * 'localhost:27018'},{_id: 2, host: 'localhost:27019', arbiterOnly: true}]} - *
  • >rs.initiate(config) - *
  • Then wait about a minute until replica set is established. You can run rs.status() and look - * for direct confirmation. - *
- * Since the unit tests create and drop databases several times, they run about twice as fast if - * mongod is started with the --smallfiles argument. - * - * @author Robert Stewart (robert@wombatnation.com) - */ -public class TestMongoDbAppenderHosts { - private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; - private final static int TEST_MONGO_SERVER_PORT = 27017; - private final static String TEST_DATABASE_NAME = "log4mongotest"; - - private final static String MONGODB_APPENDER_NAME = "MongoDB"; - - private final Privateer p = new Privateer(); - - @Test - public void testOneHost() throws Exception { - String hostname = TEST_MONGO_SERVER_HOSTNAME; - PropertyConfigurator.configure(getDefaultPortProperties(hostname)); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - MONGODB_APPENDER_NAME); - Mongo mongo = (Mongo) p.getField(appender, "mongo"); - assertTrue(mongo.getAddress() != null); - assertTrue(TEST_MONGO_SERVER_HOSTNAME.equals(mongo.getAddress().getHost())); - assertTrue(TEST_MONGO_SERVER_PORT == mongo.getAddress().getPort()); - - appender.close(); - } - - @Test - public void testOneHostNonDefaultPort() throws Exception { - String hostname = TEST_MONGO_SERVER_HOSTNAME; - String port = "27018"; - int portNum = Integer.parseInt(port); - PropertyConfigurator.configure(getNonDefaultPortProperties(hostname, port)); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - MONGODB_APPENDER_NAME); - - Mongo mongo = (Mongo) p.getField(appender, "mongo"); - assertTrue(mongo.getAddress() != null); - assertTrue(TEST_MONGO_SERVER_HOSTNAME.equals(mongo.getAddress().getHost())); - assertTrue(portNum == mongo.getAddress().getPort()); - - appender.close(); - } - - /** - * If this test is run without a mongod running on localhost port 27018, an error will be logged - * to the console by the appender. - * - * @throws Exception - */ - @Test - public void testTwoHostsTwoPorts() throws Exception { - String hostname = "localhost localhost"; - List hosts = Arrays.asList("localhost", "localhost"); - String port = "27017 27018"; - List ports = Arrays.asList("27017", "27018"); - PropertyConfigurator.configure(getNonDefaultPortProperties(hostname, port)); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - MONGODB_APPENDER_NAME); - - Mongo mongo = (Mongo) p.getField(appender, "mongo"); - List addresses = mongo.getAllAddress(); - assertTrue(addresses != null); - for (ServerAddress address : addresses) { - boolean found = false; - int i = 0; - for (String host : hosts) { - if (host.equals(address.getHost())) { - String p = String.valueOf(address.getPort()); - if (ports.get(i).equals(p)) { - found = true; - break; - } - } - i++; - } - assertTrue(found); - } - - appender.close(); - } - - private Properties getDefaultPortProperties(String hostname) { - Properties props = new Properties(); - props.put("log4j.rootLogger", "DEBUG, MongoDB"); - props.put("log4j.appender.MongoDB", "org.log4mongo.MongoDbAppender"); - props.put("log4j.appender.MongoDB.databaseName", TEST_DATABASE_NAME); - props.put("log4j.appender.MongoDB.hostname", hostname); - return props; - } - - private Properties getNonDefaultPortProperties(String hostname, String port) { - Properties props = new Properties(); - props.put("log4j.rootLogger", "DEBUG, MongoDB"); - props.put("log4j.appender.MongoDB", "org.log4mongo.MongoDbAppender"); - props.put("log4j.appender.MongoDB.databaseName", TEST_DATABASE_NAME); - props.put("log4j.appender.MongoDB.hostname", hostname); - props.put("log4j.appender.MongoDB.port", port); - return props; - } -} +/* + * Copyright (C) 2009 Robert Stewart (robert@wombatnation.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.log4mongo; + +import com.mongodb.Mongo; +import com.mongodb.ServerAddress; +import com.wombatnation.privateer.Privateer; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import static org.junit.Assert.assertTrue; + + +/** + * JUnit unit tests for MongoDbAppender to verify proper behavior when setting the hostname and + * property properties. + *

+ * Note: these tests require that a MongoDB server is running, and (by default) assumes that + * server is listening on the default port (27017) on localhost. + *

+ * Unless a replica set is configured with mongod instances listening on ports 27017 and 27018, + * errors will be logged. If a host is not listening on port 27018, testOneHostNonDefaultPort() will + * fail with an error. + *

+ * To test on localhost with a replica set, do the following (either starting mongod instances in + * separate terminals or start them with --fork): + *

    + *
  • $ mkdir -p /data/r0 + *
  • $ mkdir -p /data/r1 + *
  • $ mkdir -p /data/r2 + *
  • $ mongod --replSet foo --smallfiles --port 27017 --dbpath /data/r0 + *
  • $ mongod --replSet foo --smallfiles --port 27018 --dbpath /data/r1 + *
  • $ mongod --replSet foo --smallfiles --port 27019 --dbpath /data/r2 + *
  • $ mongo + *
  • >config = {"_id": "foo", members:[{_id: 0, host: 'localhost:27017'},{_id: 1, host: + * 'localhost:27018'},{_id: 2, host: 'localhost:27019', arbiterOnly: true}]} + *
  • >rs.initiate(config) + *
  • Then wait about a minute until replica set is established. You can run rs.status() and look + * for direct confirmation. + *
+ * Since the unit tests create and drop databases several times, they run about twice as fast if + * mongod is started with the --smallfiles argument. + * + * @author Robert Stewart (robert@wombatnation.com) + */ +public class TestMongoDbAppenderHosts { + + private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; + + private final static int TEST_MONGO_SERVER_PORT = 27017; + + private final static String TEST_DATABASE_NAME = "log4mongotest"; + + private final static String MONGODB_APPENDER_NAME = "MongoDB"; + + private final Privateer p = new Privateer(); + + @Test + public void testOneHost() throws Exception { + String hostname = TEST_MONGO_SERVER_HOSTNAME; + PropertyConfigurator.configure( getDefaultPortProperties( hostname ) ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + MONGODB_APPENDER_NAME ); + Mongo mongo = (Mongo) p.getField( appender, "mongo" ); + assertTrue( mongo.getAddress() != null ); + assertTrue( TEST_MONGO_SERVER_HOSTNAME.equals( mongo.getAddress().getHost() ) ); + assertTrue( TEST_MONGO_SERVER_PORT == mongo.getAddress().getPort() ); + + appender.close(); + } + + @Test + public void testOneHostNonDefaultPort() throws Exception { + String hostname = TEST_MONGO_SERVER_HOSTNAME; + String port = "27017"; + int portNum = Integer.parseInt( port ); + PropertyConfigurator.configure( getNonDefaultPortProperties( hostname, port ) ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + MONGODB_APPENDER_NAME ); + + Mongo mongo = (Mongo) p.getField( appender, "mongo" ); + assertTrue( mongo.getAddress() != null ); + assertTrue( TEST_MONGO_SERVER_HOSTNAME.equals( mongo.getAddress().getHost() ) ); + assertTrue( portNum == mongo.getAddress().getPort() ); + + appender.close(); + } + + /** + * If this test is run without a mongod running on localhost port 27018, an error will be logged + * to the console by the appender. + * + * @throws Exception + */ + @Test + public void testTwoHostsTwoPorts() throws Exception { + String hostname = "technecium technecium"; + List hosts = Arrays.asList( "localhost", "localhost" ); + String port = "27017 27017"; + List ports = Arrays.asList( "27017", "27017" ); + PropertyConfigurator.configure( getNonDefaultPortProperties( hostname, port ) ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + MONGODB_APPENDER_NAME ); + + Mongo mongo = (Mongo) p.getField( appender, "mongo" ); + List addresses = mongo.getAllAddress(); + assertTrue( addresses != null ); + for ( ServerAddress address : addresses ) { + boolean found = false; + int i = 0; + for ( String host : hosts ) { + if ( host.equals( address.getHost() ) ) { + String p = String.valueOf( address.getPort() ); + if ( ports.get( i ).equals( p ) ) { + found = true; + break; + } + } + i++; + } + assertTrue( found ); + } + + appender.close(); + } + + private Properties getDefaultPortProperties( String hostname ) { + Properties props = new Properties(); + props.put( "log4j.rootLogger", "DEBUG, MongoDB" ); + props.put( "log4j.appender.MongoDB", "org.log4mongo.MongoDbAppender" ); + props.put( "log4j.appender.MongoDB.databaseName", TEST_DATABASE_NAME ); + props.put( "log4j.appender.MongoDB.hostname", hostname ); + return props; + } + + private Properties getNonDefaultPortProperties( String hostname, String port ) { + Properties props = new Properties(); + props.put( "log4j.rootLogger", "DEBUG, MongoDB" ); + props.put( "log4j.appender.MongoDB", "org.log4mongo.MongoDbAppender" ); + props.put( "log4j.appender.MongoDB.databaseName", TEST_DATABASE_NAME ); + props.put( "log4j.appender.MongoDB.hostname", hostname ); + props.put( "log4j.appender.MongoDB.port", port ); + return props; + } +} diff --git a/src/test/java/org/log4mongo/TestMongoDbPatternLayout.java b/src/test/java/org/log4mongo/TestMongoDbPatternLayout.java index 287f3d8..c4a0b2d 100644 --- a/src/test/java/org/log4mongo/TestMongoDbPatternLayout.java +++ b/src/test/java/org/log4mongo/TestMongoDbPatternLayout.java @@ -1,345 +1,354 @@ -/* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.log4mongo; - -import static org.junit.Assert.*; - -import java.net.InetAddress; -import java.util.Properties; - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.BasicDBObjectBuilder; -import com.mongodb.DBCollection; -import com.mongodb.DBObject; -import com.mongodb.Mongo; - -/** - * JUnit unit tests for PatternLayout style logging. - * - * Since tests may depend on different Log4J property settings, each test reconfigures an appender - * using a Properties object. - * - * Note: these tests require that a MongoDB server is running, and (by default) assumes that server - * is listening on the default port (27017) on localhost. - * - * @author Robert Stewart (robert@wombatnation.com) - */ -public class TestMongoDbPatternLayout { - private static final Logger log = Logger.getLogger(TestMongoDbPatternLayout.class); - - public static final String TEST_MONGO_SERVER_HOSTNAME = "localhost"; - public static final int TEST_MONGO_SERVER_PORT = 27017; - private static final String TEST_DATABASE_NAME = "log4mongotest"; - private static final String TEST_COLLECTION_NAME = "logeventslayout"; - - private static final String APPENDER_NAME = "MongoDBPatternLayout"; - - private final Mongo mongo; - private DBCollection collection; - - public TestMongoDbPatternLayout() throws Exception { - mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @Before - public void setUp() throws Exception { - // Ensure both the appender and the JUnit test use the same collection - // object - provides consistency across reads (JUnit) & writes (Log4J) - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - collection.drop(); - - mongo.getDB(TEST_DATABASE_NAME).requestStart(); - } - - @After - public void tearDown() throws Exception { - mongo.getDB(TEST_DATABASE_NAME).requestDone(); - } - - @Test - public void testValidPatternLayout() { - PropertyConfigurator.configure(getValidPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - assertEquals(0L, countLogEntries()); - log.warn("Warn entry"); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Warn entry", entry.get("message")); - // This is the custom info. In the pattern, the field is named "extra". - assertEquals("useful info", entry.get("extra")); - } - - @Test - public void testQuotesInMessage() { - PropertyConfigurator.configure(getValidPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - assertEquals(0L, countLogEntries()); - String msg = "\"Quotes\" ' \"embedded\""; - log.warn(msg); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals(msg, entry.get("message")); - } - - @Test - public void testNestedDoc() { - PropertyConfigurator.configure(getNestedDocPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - assertEquals(0L, countLogEntries()); - String msg = "Nested warning"; - log.warn(msg); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - DBObject nestedDoc = (DBObject) entry.get("nested"); - assertEquals(msg, nestedDoc.get("message")); - } - - /** - * Tests that the document stored in MongoDB has an array as a value if the conversion pattern - * specifies an array as a value. - */ - @Test - public void testArrayValue() { - PropertyConfigurator.configure(getArrayPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - assertEquals(0L, countLogEntries()); - String msg = "Message in array"; - log.warn(msg); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - BasicDBList list = (BasicDBList) entry.get("array"); - assertEquals(2, list.size()); - assertEquals(this.getClass().getSimpleName(), list.get(0)); - assertEquals(msg, list.get(1)); - } - - @Test - public void testHostInfoPatternLayout() throws Exception { - PropertyConfigurator.configure(getHostInfoPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - assertEquals(0L, countLogEntries()); - String msg = "Message in array"; - log.warn(msg); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertNotNull(entry.get("host")); - DBObject hostinfo = (DBObject) entry.get("host"); - assertNotNull(hostinfo.get("name")); - assertNotNull(hostinfo.get("ip_address")); - assertNotNull(hostinfo.get("process")); - assertEquals(InetAddress.getLocalHost().getHostName(), hostinfo.get("name")); - assertEquals(InetAddress.getLocalHost().getHostAddress(), hostinfo.get("ip_address")); - } - - @Test - public void testBackslashInMessage() { - PropertyConfigurator.configure(getValidPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - assertEquals(0L, countLogEntries()); - String msg = "c:\\users\\some_file\\"; - log.warn(msg); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - String msgDoubleBackslash = "c:\\\\users\\\\some_file\\\\"; - log.info(msgDoubleBackslash); - assertEquals(2L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("INFO")); - - // verify log entry content - DBObject queryObj = new BasicDBObject(); - queryObj.put("level", "WARN"); - DBObject entry = collection.findOne(queryObj); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals(msg, entry.get("message")); - - queryObj = new BasicDBObject(); - queryObj.put("level", "INFO"); - entry = collection.findOne(queryObj); - assertNotNull(entry); - assertEquals("INFO", entry.get("level")); - assertEquals(msgDoubleBackslash, entry.get("message")); - } - - @Test - public void testPerformance() { - PropertyConfigurator.configure(getValidPatternLayoutProperties()); - - MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( - APPENDER_NAME); - - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - appender.setCollection(collection); - - int NUM_MESSAGES = 1000; - long now = System.currentTimeMillis(); - for (int i = 0; i < NUM_MESSAGES; i++) { - log.warn("Warn entry"); - } - long dur = System.currentTimeMillis() - now; - System.out.println("Milliseconds for MongoDbPatternLayoutAppender to log " + NUM_MESSAGES - + " messages:" + dur); - assertEquals(NUM_MESSAGES, countLogEntries()); - } - - private long countLogEntries() { - return (collection.getCount()); - } - - private long countLogEntriesAtLevel(final String level) { - return (countLogEntriesWhere(BasicDBObjectBuilder.start().add("level", level.toUpperCase()) - .get())); - } - - private long countLogEntriesWhere(final DBObject whereClause) { - return collection.getCount(whereClause); - } - - private Properties getValidPatternLayoutProperties() { - Properties props = new Properties(); - props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); - props.put("log4j.appender.MongoDBPatternLayout", - "org.log4mongo.MongoDbPatternLayoutAppender"); - props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); - props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); - props.put( - "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", - "{\"extra\":\"%e\",\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"class\":\"%c{1}\",\"message\":\"%m\"}"); - return props; - } - - private Properties getNestedDocPatternLayoutProperties() { - Properties props = new Properties(); - props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); - props.put("log4j.appender.MongoDBPatternLayout", - "org.log4mongo.MongoDbPatternLayoutAppender"); - props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); - props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); - props.put( - "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", - "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"nested\":{\"class\":\"%c{1}\",\"message\":\"%m\"}}"); - return props; - } - - private Properties getArrayPatternLayoutProperties() { - Properties props = new Properties(); - props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); - props.put("log4j.appender.MongoDBPatternLayout", - "org.log4mongo.MongoDbPatternLayoutAppender"); - props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); - props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); - props.put("log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", - "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"array\":[\"%c{1}\",\"%m\"]}"); - return props; - } - - private Properties getHostInfoPatternLayoutProperties() { - Properties props = new Properties(); - props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); - props.put("log4j.appender.MongoDBPatternLayout", - "org.log4mongo.MongoDbPatternLayoutAppender"); - props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); - props.put("log4j.appender.MongoDBPatternLayout.layout", - "org.log4mongo.contrib.HostInfoPatternLayout"); - props.put( - "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", - "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"array\":[\"%c{1}\",\"%m\"],\"host\":{\"name\":\"%H\", \"process\":\"%V\", \"ip_address\":\"%I\"}}"); - return props; - } -} +/* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.log4mongo; + +import com.mongodb.*; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.bson.Document; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.InetAddress; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * JUnit unit tests for PatternLayout style logging. + *

+ * Since tests may depend on different Log4J property settings, each test reconfigures an appender + * using a Properties object. + *

+ * Note: these tests require that a MongoDB server is running, and (by default) assumes that server + * is listening on the default port (27017) on localhost. + * + * @author Robert Stewart (robert@wombatnation.com) + */ +public class TestMongoDbPatternLayout { + + private static final Logger log = Logger.getLogger( TestMongoDbPatternLayout.class ); + + public static final String TEST_MONGO_SERVER_HOSTNAME = "localhost"; + + public static final int TEST_MONGO_SERVER_PORT = 27017; + + private static final String TEST_DATABASE_NAME = "log4mongotest"; + + private static final String TEST_COLLECTION_NAME = "logeventslayout"; + + private static final String APPENDER_NAME = "MongoDBPatternLayout"; + + private final MongoClient mongo; + + private MongoCollection collection; + + public TestMongoDbPatternLayout() throws Exception { + mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @Before + public void setUp() throws Exception { + // Ensure both the appender and the JUnit test use the same collection + // object - provides consistency across reads (JUnit) & writes (Log4J) + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + collection.drop(); + } + + @Test + public void testValidPatternLayout() { + PropertyConfigurator.configure( getValidPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + assertEquals( 0L, countLogEntries() ); + log.warn( "Warn entry" ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Warn entry", entry.get( "message" ) ); + // This is the custom info. In the pattern, the field is named "extra". + assertEquals( "useful info", entry.get( "extra" ) ); + } + } + + @Test + public void testQuotesInMessage() { + PropertyConfigurator.configure( getValidPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + assertEquals( 0L, countLogEntries() ); + String msg = "\"Quotes\" ' \"embedded\""; + log.warn( msg ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( msg, entry.get( "message" ) ); + } + } + + @Test + public void testNestedDoc() { + PropertyConfigurator.configure( getNestedDocPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + assertEquals( 0L, countLogEntries() ); + String msg = "Nested warning"; + log.warn( msg ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + DBObject nestedDoc = (DBObject) entry.get( "nested" ); + assertEquals( msg, nestedDoc.get( "message" ) ); + } + } + + /** + * Tests that the document stored in MongoDB has an array as a value if the conversion pattern + * specifies an array as a value. + */ + @Test + public void testArrayValue() { + PropertyConfigurator.configure( getArrayPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + assertEquals( 0L, countLogEntries() ); + String msg = "Message in array"; + log.warn( msg ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + BasicDBList list = (BasicDBList) entry.get( "array" ); + assertEquals( 2, list.size() ); + assertEquals( this.getClass().getSimpleName(), list.get( 0 ) ); + assertEquals( msg, list.get( 1 ) ); + } + } + + @Test + public void testHostInfoPatternLayout() throws Exception { + PropertyConfigurator.configure( getHostInfoPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + assertEquals( 0L, countLogEntries() ); + String msg = "Message in array"; + log.warn( msg ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertNotNull( entry.get( "host" ) ); + DBObject hostinfo = (DBObject) entry.get( "host" ); + assertNotNull( hostinfo.get( "name" ) ); + assertNotNull( hostinfo.get( "ip_address" ) ); + assertNotNull( hostinfo.get( "process" ) ); + assertEquals( InetAddress.getLocalHost().getHostName(), hostinfo.get( "name" ) ); + assertEquals( InetAddress.getLocalHost().getHostAddress(), hostinfo.get( "ip_address" ) ); + } + } + + @Test + public void testBackslashInMessage() { + PropertyConfigurator.configure( getValidPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + assertEquals( 0L, countLogEntries() ); + String msg = "c:\\users\\some_file\\"; + log.warn( msg ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + String msgDoubleBackslash = "c:\\\\users\\\\some_file\\\\"; + log.info( msgDoubleBackslash ); + assertEquals( 2L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "INFO" ) ); + + // verify log entry content + DBObject queryObj = new BasicDBObject(); + queryObj.put( "level", "WARN" ); + FindIterable entries = collection.find(DBObject.class).limit(1); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( msg, entry.get( "message" ) ); + } + + queryObj = new BasicDBObject(); + queryObj.put( "level", "INFO" ); + entries = collection.find(DBObject.class).skip( 1 ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "INFO", entry.get( "level" ) ); + assertEquals( msgDoubleBackslash, entry.get( "message" ) ); + } + } + + @Test + public void testPerformance() { + PropertyConfigurator.configure( getValidPatternLayoutProperties() ); + + MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( + APPENDER_NAME ); + + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + appender.setCollection( collection ); + + int NUM_MESSAGES = 1000; + long now = System.currentTimeMillis(); + for ( int i = 0; i < NUM_MESSAGES; i++ ) { + log.warn( "Warn entry" ); + } + long dur = System.currentTimeMillis() - now; + System.out.println( "Milliseconds for MongoDbPatternLayoutAppender to log " + NUM_MESSAGES + + " messages:" + dur ); + assertEquals( NUM_MESSAGES, countLogEntries() ); + } + + private long countLogEntries() { + return ( collection.count() ); + } + + private long countLogEntriesAtLevel( final String level ) { + return ( countLogEntriesWhere( Document.parse( "{ 'level' : '" + level.toUpperCase() + "' }" ) ) ); + } + + private long countLogEntriesWhere( final Document whereClause ) { + return collection.count( whereClause ); + } + + private Properties getValidPatternLayoutProperties() { + Properties props = new Properties(); + props.put( "log4j.rootLogger", "DEBUG, MongoDBPatternLayout" ); + props.put( "log4j.appender.MongoDBPatternLayout", + "org.log4mongo.MongoDbPatternLayoutAppender" ); + props.put( "log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest" ); + props.put( "log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout" ); + props.put( + "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", + "{\"extra\":\"%e\",\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"class\":\"%c{1}\",\"message\":\"%m\"}" ); + return props; + } + + private Properties getNestedDocPatternLayoutProperties() { + Properties props = new Properties(); + props.put( "log4j.rootLogger", "DEBUG, MongoDBPatternLayout" ); + props.put( "log4j.appender.MongoDBPatternLayout", + "org.log4mongo.MongoDbPatternLayoutAppender" ); + props.put( "log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest" ); + props.put( "log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout" ); + props.put( + "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", + "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"nested\":{\"class\":\"%c{1}\",\"message\":\"%m\"}}" ); + return props; + } + + private Properties getArrayPatternLayoutProperties() { + Properties props = new Properties(); + props.put( "log4j.rootLogger", "DEBUG, MongoDBPatternLayout" ); + props.put( "log4j.appender.MongoDBPatternLayout", + "org.log4mongo.MongoDbPatternLayoutAppender" ); + props.put( "log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest" ); + props.put( "log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout" ); + props.put( "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", + "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"array\":[\"%c{1}\",\"%m\"]}" ); + return props; + } + + private Properties getHostInfoPatternLayoutProperties() { + Properties props = new Properties(); + props.put( "log4j.rootLogger", "DEBUG, MongoDBPatternLayout" ); + props.put( "log4j.appender.MongoDBPatternLayout", + "org.log4mongo.MongoDbPatternLayoutAppender" ); + props.put( "log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest" ); + props.put( "log4j.appender.MongoDBPatternLayout.layout", + "org.log4mongo.contrib.HostInfoPatternLayout" ); + props.put( + "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", + "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"array\":[\"%c{1}\",\"%m\"],\"host\":{\"name\":\"%H\", \"process\":\"%V\", \"ip_address\":\"%I\"}}" ); + return props; + } +} diff --git a/src/test/java/org/log4mongo/TestWriteConcern.java b/src/test/java/org/log4mongo/TestWriteConcern.java index 2ba21d9..09d24c8 100644 --- a/src/test/java/org/log4mongo/TestWriteConcern.java +++ b/src/test/java/org/log4mongo/TestWriteConcern.java @@ -1,311 +1,317 @@ -/* - * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.log4mongo; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.lang.management.ManagementFactory; -import java.net.InetAddress; - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObjectBuilder; -import com.mongodb.DBCollection; -import com.mongodb.DBObject; -import com.mongodb.Mongo; - -/** - * JUnit unit tests for using write concerns with MongoDbAppender. - * - * When using FSYNCED, logging is well over a magnitude slower than UNACKNOWLEDGED. - * - * Note: these tests require that a MongoDB server is running, and (by default) assumes that server - * is listening on the default port (27017) on localhost. - */ -public class TestWriteConcern { - private final static Logger log = Logger.getLogger(TestWriteConcern.class); - - private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; - private final static int TEST_MONGO_SERVER_PORT = 27017; - private final static String TEST_DATABASE_NAME = "log4mongotest"; - private final static String TEST_COLLECTION_NAME = "logevents"; - - private final static String MONGODB_APPENDER_NAME = "MongoDB"; - - private final static String LOG4J_PROPS = "src/test/resources/log4j_write_concern.properties"; - - private final Mongo mongo; - private final MongoDbAppender appender; - private DBCollection collection; - - public TestWriteConcern() throws Exception { - PropertyConfigurator.configure(LOG4J_PROPS); - mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - appender = (MongoDbAppender) Logger.getRootLogger().getAppender(MONGODB_APPENDER_NAME); - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - Mongo mongo = new Mongo(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); - mongo.dropDatabase(TEST_DATABASE_NAME); - } - - @Before - public void setUp() throws Exception { - // Ensure both the appender and the JUnit test use the same collection - // object - provides consistency across reads (JUnit) & writes (Log4J) - collection = mongo.getDB(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); - collection.drop(); - appender.setCollection(collection); - - mongo.getDB(TEST_DATABASE_NAME).requestStart(); - } - - @After - public void tearDown() throws Exception { - mongo.getDB(TEST_DATABASE_NAME).requestDone(); - } - - @Test - public void testInitialized() throws Exception { - if (!appender.isInitialized()) - fail(); - } - - @Test - public void testWriteConcern() throws Exception { - testInitialized(); - assertEquals(appender.getWriteConcern(), "FSYNCED"); - } - - @Test - public void testSingleLogEntry() throws Exception { - log.trace("Trace entry"); - - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("trace")); - assertEquals(0L, countLogEntriesAtLevel("debug")); - assertEquals(0L, countLogEntriesAtLevel("info")); - assertEquals(0L, countLogEntriesAtLevel("warn")); - assertEquals(0L, countLogEntriesAtLevel("error")); - assertEquals(0L, countLogEntriesAtLevel("fatal")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("TRACE", entry.get("level")); - assertEquals("Trace entry", entry.get("message")); - } - - @Test - public void testTimestampStoredNatively() throws Exception { - log.debug("Debug entry"); - - assertEquals(1L, countLogEntries()); - - // verify timestamp - presence and data type - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertTrue("Timestamp is not present in logged entry", entry.containsField("timestamp")); - assertTrue("Timestamp of logged entry is not stored as native date", - (entry.get("timestamp") instanceof java.util.Date)); - } - - @Test - public void testAllLevels() throws Exception { - log.trace("Trace entry"); - log.debug("Debug entry"); - log.info("Info entry"); - log.warn("Warn entry"); - log.error("Error entry"); - log.fatal("Fatal entry"); - - assertEquals(6L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("trace")); - assertEquals(1L, countLogEntriesAtLevel("debug")); - assertEquals(1L, countLogEntriesAtLevel("info")); - assertEquals(1L, countLogEntriesAtLevel("warn")); - assertEquals(1L, countLogEntriesAtLevel("error")); - assertEquals(1L, countLogEntriesAtLevel("fatal")); - } - - @Test - public void testLogWithException() throws Exception { - log.error("Error entry", new RuntimeException("Here is an exception!")); - - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("error")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("ERROR", entry.get("level")); - assertEquals("Error entry", entry.get("message")); - - // verify throwable presence and content - assertTrue("Throwable is not present in logged entry", entry.containsField("throwables")); - BasicDBList throwables = (BasicDBList) entry.get("throwables"); - assertEquals(1, throwables.size()); - - DBObject throwableEntry = (DBObject) throwables.get("0"); - assertTrue("Throwable message is not present in logged entry", - throwableEntry.containsField("message")); - assertEquals("Here is an exception!", throwableEntry.get("message")); - } - - @Test - public void testLogWithChainedExceptions() throws Exception { - Exception rootCause = new RuntimeException("I'm the real culprit!"); - - log.error("Error entry", new RuntimeException("I'm an innocent bystander.", rootCause)); - - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("error")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("ERROR", entry.get("level")); - assertEquals("Error entry", entry.get("message")); - - // verify throwable presence and content - assertTrue("Throwable is not present in logged entry", entry.containsField("throwables")); - BasicDBList throwables = (BasicDBList) entry.get("throwables"); - assertEquals(2, throwables.size()); - - DBObject rootEntry = (DBObject) throwables.get("0"); - assertTrue("Throwable message is not present in logged entry", - rootEntry.containsField("message")); - assertEquals("I'm an innocent bystander.", rootEntry.get("message")); - - DBObject chainedEntry = (DBObject) throwables.get("1"); - assertTrue("Throwable message is not present in logged entry", - chainedEntry.containsField("message")); - assertEquals("I'm the real culprit!", chainedEntry.get("message")); - } - - @Test - public void testQuotesInMessage() { - assertEquals(0L, countLogEntries()); - log.warn("Quotes\" \"embedded"); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Quotes\" \"embedded", entry.get("message")); - } - - @Test - public void testPerformance() throws Exception { - // Log one event to minimize start up effects on performance - log.warn("Warn entry"); - - long NUM_MESSAGES = 100; - long now = System.currentTimeMillis(); - for (long i = 0; i < NUM_MESSAGES; i++) { - log.warn("Warn entry"); - } - long dur = System.currentTimeMillis() - now; - System.out.println("Milliseconds for MongoDbAppender with write concern " - + appender.getWriteConcern() + " to log " + NUM_MESSAGES + " messages:" + dur); - assertEquals(NUM_MESSAGES + 1, countLogEntries()); - } - - @Test - public void testRegularLoggerRecordsLoggerNameCorrectly() { - log.info("From an unwrapped logger"); - - assertEquals(1, countLogEntries()); - assertEquals(1, countLogEntriesAtLevel("info")); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("loggerName.className", "TestWriteConcern").get())); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("class.className", "TestWriteConcern").get())); - } - - @Test - public void testWrappedLoggerRecordsLoggerNameCorrectly() { - WrappedLogger wrapped = new WrappedLogger(log); - wrapped.info("From a wrapped logger"); - - assertEquals(1, countLogEntries()); - assertEquals(1, countLogEntriesAtLevel("info")); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("loggerName.className", "TestWriteConcern").get())); - assertEquals( - 1, - countLogEntriesWhere(BasicDBObjectBuilder.start() - .add("class.className", "WrappedLogger").get())); - } - - @Test - public void testHostInfoRecords() throws Exception { - assertEquals(0L, countLogEntries()); - log.warn("Testing hostinfo"); - assertEquals(1L, countLogEntries()); - assertEquals(1L, countLogEntriesAtLevel("WARN")); - - // verify log entry content - DBObject entry = collection.findOne(); - assertNotNull(entry); - assertEquals("WARN", entry.get("level")); - assertEquals("Testing hostinfo", entry.get("message")); - assertNotNull(entry.get("host")); - DBObject hostinfo = (DBObject) entry.get("host"); - assertNotNull(hostinfo.get("process")); - assertEquals(InetAddress.getLocalHost().getHostName(), hostinfo.get("name")); - assertEquals(ManagementFactory.getRuntimeMXBean().getName(), hostinfo.get("process")); - } - - private long countLogEntries() { - return (collection.getCount()); - } - - private long countLogEntriesAtLevel(final String level) { - return (countLogEntriesWhere(BasicDBObjectBuilder.start().add("level", level.toUpperCase()) - .get())); - } - - private long countLogEntriesWhere(final DBObject whereClause) { - return collection.getCount(whereClause); - } -} +/* + * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.log4mongo; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DBObject; +import com.mongodb.MongoClient; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.bson.Document; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; + +import static org.junit.Assert.*; + + +/** + * JUnit unit tests for using write concerns with MongoDbAppender. + *

+ * When using FSYNCED, logging is well over a magnitude slower than UNACKNOWLEDGED. + *

+ * Note: these tests require that a MongoDB server is running, and (by default) assumes that server + * is listening on the default port (27017) on localhost. + */ +public class TestWriteConcern { + + private final static Logger log = Logger.getLogger( TestWriteConcern.class ); + + private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; + + private final static int TEST_MONGO_SERVER_PORT = 27017; + + private final static String TEST_DATABASE_NAME = "log4mongotest"; + + private final static String TEST_COLLECTION_NAME = "logevents"; + + private final static String MONGODB_APPENDER_NAME = "MongoDB"; + + private final static String LOG4J_PROPS = "src/test/resources/log4j_write_concern.properties"; + + private final MongoClient mongo; + + private final MongoDbAppender appender; + + private MongoCollection collection; + + public TestWriteConcern() throws Exception { + PropertyConfigurator.configure( LOG4J_PROPS ); + mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + appender = (MongoDbAppender) Logger.getRootLogger().getAppender( MONGODB_APPENDER_NAME ); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + MongoClient mongo = new MongoClient( TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT ); + mongo.dropDatabase( TEST_DATABASE_NAME ); + } + + @Before + public void setUp() throws Exception { + // Ensure both the appender and the JUnit test use the same collection + // object - provides consistency across reads (JUnit) & writes (Log4J) + collection = mongo.getDatabase( TEST_DATABASE_NAME ).getCollection( TEST_COLLECTION_NAME ); + collection.drop(); + appender.setCollection( collection ); + } + + @Test + public void testInitialized() throws Exception { + if ( !appender.isInitialized() ) { + fail(); + } + } + + @Test + public void testWriteConcern() throws Exception { + testInitialized(); + assertEquals( appender.getWriteConcern(), "FSYNCED" ); + } + + @Test + public void testSingleLogEntry() throws Exception { + log.trace( "Trace entry" ); + + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "trace" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "debug" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "info" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "warn" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "error" ) ); + assertEquals( 0L, countLogEntriesAtLevel( "fatal" ) ); + + // verify log entry content + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "TRACE", entry.get( "level" ) ); + assertEquals( "Trace entry", entry.get( "message" ) ); + } + } + + @Test + public void testTimestampStoredNatively() throws Exception { + log.debug( "Debug entry" ); + + assertEquals( 1L, countLogEntries() ); + + // verify timestamp - presence and data type + FindIterable entries = collection.find( DBObject.class ); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertTrue( "Timestamp is not present in logged entry", entry.containsField( "timestamp" ) ); + assertTrue( "Timestamp of logged entry is not stored as native date", + ( entry.get( "timestamp" ) instanceof java.util.Date ) ); + } + } + + @Test + public void testAllLevels() throws Exception { + log.trace( "Trace entry" ); + log.debug( "Debug entry" ); + log.info( "Info entry" ); + log.warn( "Warn entry" ); + log.error( "Error entry" ); + log.fatal( "Fatal entry" ); + + assertEquals( 6L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "trace" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "debug" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "info" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "warn" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "error" ) ); + assertEquals( 1L, countLogEntriesAtLevel( "fatal" ) ); + } + + @Test + public void testLogWithException() throws Exception { + log.error( "Error entry", new RuntimeException( "Here is an exception!" ) ); + + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "error" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "ERROR", entry.get( "level" ) ); + assertEquals( "Error entry", entry.get( "message" ) ); + + // verify throwable presence and content + assertTrue( "Throwable is not present in logged entry", entry.containsField( "throwables" ) ); + BasicDBList throwables = (BasicDBList) entry.get( "throwables" ); + assertEquals( 1, throwables.size() ); + + DBObject throwableEntry = (DBObject) throwables.get( "0" ); + assertTrue( "Throwable message is not present in logged entry", + throwableEntry.containsField( "message" ) ); + assertEquals( "Here is an exception!", throwableEntry.get( "message" ) ); + } + } + + @Test + public void testLogWithChainedExceptions() throws Exception { + Exception rootCause = new RuntimeException( "I'm the real culprit!" ); + + log.error( "Error entry", new RuntimeException( "I'm an innocent bystander.", rootCause ) ); + + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "error" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "ERROR", entry.get( "level" ) ); + assertEquals( "Error entry", entry.get( "message" ) ); + + // verify throwable presence and content + assertTrue( "Throwable is not present in logged entry", entry.containsField( "throwables" ) ); + BasicDBList throwables = (BasicDBList) entry.get( "throwables" ); + assertEquals( 2, throwables.size() ); + + DBObject rootEntry = (DBObject) throwables.get( "0" ); + assertTrue( "Throwable message is not present in logged entry", + rootEntry.containsField( "message" ) ); + assertEquals( "I'm an innocent bystander.", rootEntry.get( "message" ) ); + + DBObject chainedEntry = (DBObject) throwables.get( "1" ); + assertTrue( "Throwable message is not present in logged entry", + chainedEntry.containsField( "message" ) ); + assertEquals( "I'm the real culprit!", chainedEntry.get( "message" ) ); + } + } + + @Test + public void testQuotesInMessage() { + assertEquals( 0L, countLogEntries() ); + log.warn( "Quotes\" \"embedded" ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Quotes\" \"embedded", entry.get( "message" ) ); + } + } + + @Test + public void testPerformance() throws Exception { + // Log one event to minimize start up effects on performance + log.warn( "Warn entry" ); + + long NUM_MESSAGES = 100; + long now = System.currentTimeMillis(); + for ( long i = 0; i < NUM_MESSAGES; i++ ) { + log.warn( "Warn entry" ); + } + long dur = System.currentTimeMillis() - now; + System.out.println( "Milliseconds for MongoDbAppender with write concern " + + appender.getWriteConcern() + " to log " + NUM_MESSAGES + " messages:" + dur ); + assertEquals( NUM_MESSAGES + 1, countLogEntries() ); + } + + @Test + public void testRegularLoggerRecordsLoggerNameCorrectly() { + log.info( "From an unwrapped logger" ); + + assertEquals( 1, countLogEntries() ); + assertEquals( 1, countLogEntriesAtLevel( "info" ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'loggerName.className' : 'TestWriteConcern' }" ) ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'class.className' : 'TestWriteConcern' }" ) ) ); + } + + @Test + public void testWrappedLoggerRecordsLoggerNameCorrectly() { + WrappedLogger wrapped = new WrappedLogger( log ); + wrapped.info( "From a wrapped logger" ); + + assertEquals( 1, countLogEntries() ); + assertEquals( 1, countLogEntriesAtLevel( "info" ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'loggerName.className' : 'TestWriteConcern' }" ) ) ); + assertEquals( + 1, + countLogEntriesWhere( Document.parse( "{ 'class.className' : 'WrappedLogger' }" ) ) ); + } + + @Test + public void testHostInfoRecords() throws Exception { + assertEquals( 0L, countLogEntries() ); + log.warn( "Testing hostinfo" ); + assertEquals( 1L, countLogEntries() ); + assertEquals( 1L, countLogEntriesAtLevel( "WARN" ) ); + + // verify log entry content + FindIterable entries = collection.find(DBObject.class); + for ( DBObject entry : entries ) { + assertNotNull( entry ); + assertEquals( "WARN", entry.get( "level" ) ); + assertEquals( "Testing hostinfo", entry.get( "message" ) ); + assertNotNull( entry.get( "host" ) ); + DBObject hostinfo = (DBObject) entry.get( "host" ); + assertNotNull( hostinfo.get( "process" ) ); + assertEquals( InetAddress.getLocalHost().getHostName(), hostinfo.get( "name" ) ); + assertEquals( ManagementFactory.getRuntimeMXBean().getName(), hostinfo.get( "process" ) ); + } + } + + private long countLogEntries() { + return ( collection.count() ); + } + + private long countLogEntriesAtLevel( final String level ) { + return ( countLogEntriesWhere( Document.parse( "{ 'level' : '" + level.toUpperCase() + "' }" ) ) ); + } + + private long countLogEntriesWhere( final Document whereClause ) { + return collection.count( whereClause ); + } + +} diff --git a/src/test/java/org/log4mongo/WrappedLogger.java b/src/test/java/org/log4mongo/WrappedLogger.java index a7bc61b..f8c8413 100644 --- a/src/test/java/org/log4mongo/WrappedLogger.java +++ b/src/test/java/org/log4mongo/WrappedLogger.java @@ -20,195 +20,196 @@ import java.text.MessageFormat; + /** * */ public class WrappedLogger { - private final Logger delegate; - - public WrappedLogger(Logger logger) { - this.delegate = logger; - } - - /** - * @see org.apache.log4j.Category#fatal(java.lang.Object, java.lang.Throwable) - */ - public void fatal(Object message, Throwable t) { - delegate.fatal(message, t); - } - - /** - * @see org.apache.log4j.Category#fatal(java.lang.Object) - */ - public void fatal(Object message) { - delegate.fatal(message); - } - - public void fatal(String format, Object... params) { - // fatal is always enabled - if (params[params.length - 1] instanceof Throwable) { - delegate.fatal(generateFormattedMessage(format, params), - (Throwable) params[params.length - 1]); - } else { - delegate.fatal(generateFormattedMessage(format, params)); - } - } - - public boolean isErrorEnabled() { - return delegate.isEnabledFor(Level.ERROR); - } - - /** - * @see org.apache.log4j.Category#error(java.lang.Object, java.lang.Throwable) - */ - public void error(Object message, Throwable t) { - delegate.error(message, t); - } - - /** - * @see org.apache.log4j.Category#error(java.lang.Object) - */ - public void error(Object message) { - delegate.error(message); - } - - public void error(String format, Object... params) { - // skip isErrorEnabled() check here since more than likely it is. - // Log4j checks anyway, so... - if (params[params.length - 1] instanceof Throwable) { - final String message = generateFormattedMessage(format, params); - delegate.error(message, (Throwable) params[params.length - 1]); - } else { - delegate.error(generateFormattedMessage(format, params)); - } - } - - public boolean isWarnEnabled() { - return delegate.isEnabledFor(Level.WARN); - } - - /** - * @see org.apache.log4j.Category#warn(java.lang.Object, java.lang.Throwable) - */ - public void warn(Object message, Throwable t) { - delegate.warn(message, t); - } - - /** - * @see org.apache.log4j.Category#warn(java.lang.Object) - */ - public void warn(Object message) { - delegate.warn(message); - } - - public void warn(String format, Object... params) { - if (params[params.length - 1] instanceof Throwable) { - delegate.warn(generateFormattedMessage(format, params), - (Throwable) params[params.length - 1]); - } else { - delegate.warn(generateFormattedMessage(format, params)); - } - } - - /** - * @see org.apache.log4j.Category#isInfoEnabled() - */ - public boolean isInfoEnabled() { - return delegate.isInfoEnabled(); - } - - /** - * @see org.apache.log4j.Category#info(java.lang.Object, java.lang.Throwable) - */ - public void info(Object message, Throwable t) { - delegate.info(message, t); - } - - /** - * @see org.apache.log4j.Category#info(java.lang.Object) - */ - public void info(Object message) { - delegate.info(message); - } - - public void info(String format, Object... params) { - if (isInfoEnabled()) { - if (params[params.length - 1] instanceof Throwable) { - delegate.info(generateFormattedMessage(format, params), - (Throwable) params[params.length - 1]); - } else { - delegate.info(generateFormattedMessage(format, params)); - } - } - } - - /** - * @see org.apache.log4j.Category#isDebugEnabled() - */ - public boolean isDebugEnabled() { - return delegate.isDebugEnabled(); - } - - /** - * @see org.apache.log4j.Category#debug(java.lang.Object, java.lang.Throwable) - */ - public void debug(Object message, Throwable t) { - delegate.debug(message, t); - } - - /** - * @see org.apache.log4j.Category#debug(java.lang.Object) - */ - public void debug(Object message) { - delegate.debug(message); - } - - public void debug(String format, Object... params) { - if (isDebugEnabled()) { - if (params[params.length - 1] instanceof Throwable) { - delegate.debug(generateFormattedMessage(format, params), - (Throwable) params[params.length - 1]); - } else { - delegate.debug(generateFormattedMessage(format, params)); - } - } - } - - private String generateFormattedMessage(String format, Object... params) { - return MessageFormat.format(format, params); - } - - /** - * @see org.apache.log4j.Logger#isTraceEnabled() - */ - public boolean isTraceEnabled() { - return delegate.isTraceEnabled(); - } - - /** - * @see org.apache.log4j.Logger#trace(java.lang.Object, java.lang.Throwable) - */ - public void trace(Object message, Throwable t) { - delegate.trace(message, t); - } - - /** - * @see org.apache.log4j.Logger#trace(java.lang.Object) - */ - public void trace(Object message) { - delegate.trace(message); - } - - public void trace(String format, Object... params) { - if (isTraceEnabled()) { - if (params[params.length - 1] instanceof Throwable) { - delegate.trace(generateFormattedMessage(format, params), - (Throwable) params[params.length - 1]); - } else { - delegate.trace(generateFormattedMessage(format, params)); - } - } - } + private final Logger delegate; + + public WrappedLogger( Logger logger ) { + this.delegate = logger; + } + + /** + * @see org.apache.log4j.Category#fatal(java.lang.Object, java.lang.Throwable) + */ + public void fatal( Object message, Throwable t ) { + delegate.fatal( message, t ); + } + + /** + * @see org.apache.log4j.Category#fatal(java.lang.Object) + */ + public void fatal( Object message ) { + delegate.fatal( message ); + } + + public void fatal( String format, Object... params ) { + // fatal is always enabled + if ( params[ params.length - 1 ] instanceof Throwable ) { + delegate.fatal( generateFormattedMessage( format, params ), + (Throwable) params[ params.length - 1 ] ); + } else { + delegate.fatal( generateFormattedMessage( format, params ) ); + } + } + + public boolean isErrorEnabled() { + return delegate.isEnabledFor( Level.ERROR ); + } + + /** + * @see org.apache.log4j.Category#error(java.lang.Object, java.lang.Throwable) + */ + public void error( Object message, Throwable t ) { + delegate.error( message, t ); + } + + /** + * @see org.apache.log4j.Category#error(java.lang.Object) + */ + public void error( Object message ) { + delegate.error( message ); + } + + public void error( String format, Object... params ) { + // skip isErrorEnabled() check here since more than likely it is. + // Log4j checks anyway, so... + if ( params[ params.length - 1 ] instanceof Throwable ) { + final String message = generateFormattedMessage( format, params ); + delegate.error( message, (Throwable) params[ params.length - 1 ] ); + } else { + delegate.error( generateFormattedMessage( format, params ) ); + } + } + + public boolean isWarnEnabled() { + return delegate.isEnabledFor( Level.WARN ); + } + + /** + * @see org.apache.log4j.Category#warn(java.lang.Object, java.lang.Throwable) + */ + public void warn( Object message, Throwable t ) { + delegate.warn( message, t ); + } + + /** + * @see org.apache.log4j.Category#warn(java.lang.Object) + */ + public void warn( Object message ) { + delegate.warn( message ); + } + + public void warn( String format, Object... params ) { + if ( params[ params.length - 1 ] instanceof Throwable ) { + delegate.warn( generateFormattedMessage( format, params ), + (Throwable) params[ params.length - 1 ] ); + } else { + delegate.warn( generateFormattedMessage( format, params ) ); + } + } + + /** + * @see org.apache.log4j.Category#isInfoEnabled() + */ + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + /** + * @see org.apache.log4j.Category#info(java.lang.Object, java.lang.Throwable) + */ + public void info( Object message, Throwable t ) { + delegate.info( message, t ); + } + + /** + * @see org.apache.log4j.Category#info(java.lang.Object) + */ + public void info( Object message ) { + delegate.info( message ); + } + + public void info( String format, Object... params ) { + if ( isInfoEnabled() ) { + if ( params[ params.length - 1 ] instanceof Throwable ) { + delegate.info( generateFormattedMessage( format, params ), + (Throwable) params[ params.length - 1 ] ); + } else { + delegate.info( generateFormattedMessage( format, params ) ); + } + } + } + + /** + * @see org.apache.log4j.Category#isDebugEnabled() + */ + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + /** + * @see org.apache.log4j.Category#debug(java.lang.Object, java.lang.Throwable) + */ + public void debug( Object message, Throwable t ) { + delegate.debug( message, t ); + } + + /** + * @see org.apache.log4j.Category#debug(java.lang.Object) + */ + public void debug( Object message ) { + delegate.debug( message ); + } + + public void debug( String format, Object... params ) { + if ( isDebugEnabled() ) { + if ( params[ params.length - 1 ] instanceof Throwable ) { + delegate.debug( generateFormattedMessage( format, params ), + (Throwable) params[ params.length - 1 ] ); + } else { + delegate.debug( generateFormattedMessage( format, params ) ); + } + } + } + + private String generateFormattedMessage( String format, Object... params ) { + return MessageFormat.format( format, params ); + } + + /** + * @see org.apache.log4j.Logger#isTraceEnabled() + */ + public boolean isTraceEnabled() { + return delegate.isTraceEnabled(); + } + + /** + * @see org.apache.log4j.Logger#trace(java.lang.Object, java.lang.Throwable) + */ + public void trace( Object message, Throwable t ) { + delegate.trace( message, t ); + } + + /** + * @see org.apache.log4j.Logger#trace(java.lang.Object) + */ + public void trace( Object message ) { + delegate.trace( message ); + } + + public void trace( String format, Object... params ) { + if ( isTraceEnabled() ) { + if ( params[ params.length - 1 ] instanceof Throwable ) { + delegate.trace( generateFormattedMessage( format, params ), + (Throwable) params[ params.length - 1 ] ); + } else { + delegate.trace( generateFormattedMessage( format, params ) ); + } + } + } }