From 8d7886658a2847e6857193e001026809f337461c Mon Sep 17 00:00:00 2001 From: Alik Kurdyukov Date: Mon, 26 Sep 2011 18:25:09 +0400 Subject: [PATCH 1/2] MDC usage added, code formatted with tabs --- .../org/log4mongo/LoggingEventBsonifier.java | 5 +- .../log4mongo/LoggingEventBsonifierImpl.java | 402 ++++++----- .../java/org/log4mongo/MongoDbAppender.java | 671 +++++++++--------- .../org/log4mongo/MongoDbPatternLayout.java | 156 ++-- .../MongoDbPatternLayoutAppender.java | 74 +- .../org/log4mongo/TestMongoDbAppender.java | 639 +++++++++-------- src/test/resources/log4j.properties | 13 +- 7 files changed, 1004 insertions(+), 956 deletions(-) diff --git a/src/main/java/org/log4mongo/LoggingEventBsonifier.java b/src/main/java/org/log4mongo/LoggingEventBsonifier.java index 230b4b6..2f2bad4 100644 --- a/src/main/java/org/log4mongo/LoggingEventBsonifier.java +++ b/src/main/java/org/log4mongo/LoggingEventBsonifier.java @@ -26,5 +26,8 @@ * LoggingEvent. LoggingEventBsonifierImpl is the default implementation. */ public interface LoggingEventBsonifier { - DBObject bsonify(LoggingEvent loggingEvent); + DBObject bsonify(LoggingEvent loggingEvent); + + void setAppendMDCVariables(boolean appendMDCVariables); + boolean getAppendMDCVariables(); } diff --git a/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java b/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java index 29a6f94..9ebc6dc 100644 --- a/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java +++ b/src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java @@ -23,7 +23,9 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Map; +import org.apache.log4j.MDC; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; @@ -38,233 +40,241 @@ * LoggingEvent. */ public class LoggingEventBsonifierImpl implements LoggingEventBsonifier { + private boolean appendMDCVariables = false; + private DBObject hostInfo = new BasicDBObject(); - private DBObject hostInfo = new BasicDBObject(); + public LoggingEventBsonifierImpl() { + setupNetworkInfo(); + } - public LoggingEventBsonifierImpl() { - setupNetworkInfo(); - } + private void setupNetworkInfo() { + hostInfo.put("process", ManagementFactory.getRuntimeMXBean().getName()); + try { + hostInfo.put("name", InetAddress.getLocalHost().getHostName()); + hostInfo.put("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("timestamp", new Date(loggingEvent.getTimeStamp())); + nullSafePut(result, "level", loggingEvent.getLevel().toString()); + nullSafePut(result, "thread", loggingEvent.getThreadName()); + nullSafePut(result, "message", loggingEvent.getMessage()); + nullSafePut(result, "loggerName", bsonifyClassName(loggingEvent.getLoggerName())); + + addLocationInformation(result, loggingEvent.getLocationInformation()); + addThrowableInformation(result, loggingEvent.getThrowableInformation()); + addHostnameInformation(result); + addMDCVariables(result); + } - private void setupNetworkInfo() { - hostInfo.put("process", ManagementFactory.getRuntimeMXBean().getName()); - try { - hostInfo.put("name", InetAddress.getLocalHost().getHostName()); - hostInfo.put("ip", InetAddress.getLocalHost().getHostAddress()); - } catch (UnknownHostException e) { - LogLog.warn(e.getMessage()); + return (result); } - } - - /** - * 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("timestamp", new Date(loggingEvent.getTimeStamp())); - nullSafePut(result, "level", loggingEvent.getLevel().toString()); - nullSafePut(result, "thread", loggingEvent.getThreadName()); - nullSafePut(result, "message", loggingEvent.getMessage()); - nullSafePut(result, "loggerName", bsonifyClassName(loggingEvent.getLoggerName())); - - addLocationInformation(result, loggingEvent.getLocationInformation()); - addThrowableInformation(result, loggingEvent.getThrowableInformation()); - addHostnameInformation(result); + + public void setAppendMDCVariables(boolean appendMDCVariables) { + this.appendMDCVariables = appendMDCVariables; } - return (result); - } - - /** - * 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, "fileName", locationInfo.getFileName()); - nullSafePut(bson, "method", locationInfo.getMethodName()); - nullSafePut(bson, "lineNumber", locationInfo.getLineNumber()); - nullSafePut(bson, "class", bsonifyClassName(locationInfo.getClassName())); + public boolean getAppendMDCVariables() { + return appendMDCVariables; } - } - - /** - * 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(); - } + /** + * 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, "fileName", locationInfo.getFileName()); + nullSafePut(bson, "method", locationInfo.getMethodName()); + nullSafePut(bson, "lineNumber", locationInfo.getLineNumber()); + nullSafePut(bson, "class", bsonifyClassName(locationInfo.getClassName())); + } + } - if (throwables.size() > 0) { - bson.put("throwables", throwables); - } + /** + * 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("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, "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, "message", throwable.getMessage()); - nullSafePut(result, "stackTrace", bsonifyStackTrace(throwable.getStackTrace())); + + /** + * 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, "host", hostInfo); } - return (result); - } + /** + * 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; - /** - * 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 (throwable != null) { + result = new BasicDBObject(); - if (stackTrace != null && stackTrace.length > 0) { - result = new BasicDBList(); + nullSafePut(result, "message", throwable.getMessage()); + nullSafePut(result, "stackTrace", bsonifyStackTrace(throwable.getStackTrace())); + } - for (StackTraceElement element : stackTrace) { - DBObject bson = bsonifyStackTraceElement(element); + return (result); + } - if (bson != null) { - result.add(bson); + /** + * 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); } - 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, "fileName", element.getFileName()); - nullSafePut(result, "method", element.getMethodName()); - nullSafePut(result, "lineNumber", element.getLineNumber()); - nullSafePut(result, "class", bsonifyClassName(element.getClassName())); + /** + * 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, "fileName", element.getFileName()); + nullSafePut(result, "method", element.getMethodName()); + nullSafePut(result, "lineNumber", element.getLineNumber()); + nullSafePut(result, "class", bsonifyClassName(element.getClassName())); + } + + return (result); } - 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; - /** - * 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(); - if (className != null && className.trim().length() > 0) { - result = new BasicDBObject(); + result.put("fullyQualifiedClassName", className); - result.put("fullyQualifiedClassName", className); + List packageComponents = new BasicDBList(); + String[] packageAndClassName = className.split("\\."); - 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))); - packageComponents.addAll(Arrays.asList(packageAndClassName)); - // Requires Java 6 - // packageComponents.addAll(Arrays.asList(Arrays.copyOf(packageAndClassName, - // packageAndClassName.length - 1))); + if (packageComponents.size() > 0) { + result.put("package", packageComponents); + } - if (packageComponents.size() > 0) { - result.put("package", packageComponents); - } + result.put("className", + packageAndClassName[packageAndClassName.length - 1]); + } - result.put("className", - packageAndClassName[packageAndClassName.length - 1]); + return (result); + } + + @SuppressWarnings(value = "unchecked") + private void addMDCVariables(DBObject result) { + if (appendMDCVariables) { + final Map mdc = MDC.getContext(); + if (mdc != null) { + for (Map.Entry entry: mdc.entrySet()) { + nullSafePut(result, entry.getKey(), entry.getValue()); + } + } + } } - 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); + /** + * 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 { + bson.put(key, value); + } } - } else { - bson.put(key, value); - } } - } } diff --git a/src/main/java/org/log4mongo/MongoDbAppender.java b/src/main/java/org/log4mongo/MongoDbAppender.java index 2d4734a..4609b10 100644 --- a/src/main/java/org/log4mongo/MongoDbAppender.java +++ b/src/main/java/org/log4mongo/MongoDbAppender.java @@ -1,333 +1,338 @@ -/* - * 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 java.net.UnknownHostException; -import java.util.ArrayList; -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; - -/** - * 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 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 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 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); - if (addresses.size() < 2) { - mongo = new Mongo(addresses.get(0)); - } else { - // Replication set - mongo = new Mongo(addresses); - } - DB database = mongo.getDB(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); - } - } - - /** - * 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) { - // PRECONDITIONS - assert collection != null : "collection must not be null."; - - // Body - 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) { - // PRECONDITIONS - assert hostname != null : "hostname must not be null"; - assert hostname.trim().length() > 0 : "hostname must not be empty or blank"; - - // Body - 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) { - // PRECONDITIONS - assert port != null : "port must not be null"; - assert port.trim().length() > 0 : "port must not be empty or blank"; - - // Body - 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) { - // PRECONDITIONS - assert databaseName != null : "database must not be null"; - assert databaseName.trim().length() > 0 : "database must not be empty or blank"; - - // Body - 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) { - // PRECONDITIONS - assert collectionName != null : "collection must not be null"; - assert collectionName.trim().length() > 0 : "collection must not be empty or blank"; - - // Body - 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; - } - - /** - * @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); - } 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.: - *
    - *
  • 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 = 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; - } - -} +/* + * 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 java.net.UnknownHostException; +import java.util.ArrayList; +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; + +/** + * 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 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 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 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); + if (addresses.size() < 2) { + mongo = new Mongo(addresses.get(0)); + } else { + // Replication set + mongo = new Mongo(addresses); + } + DB database = mongo.getDB(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); + } + } + + /** + * 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) { + // PRECONDITIONS + assert collection != null : "collection must not be null."; + + // Body + 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) { + // PRECONDITIONS + assert hostname != null : "hostname must not be null"; + assert hostname.trim().length() > 0 : "hostname must not be empty or blank"; + + // Body + 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) { + // PRECONDITIONS + assert port != null : "port must not be null"; + assert port.trim().length() > 0 : "port must not be empty or blank"; + + // Body + 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) { + // PRECONDITIONS + assert databaseName != null : "database must not be null"; + assert databaseName.trim().length() > 0 : "database must not be empty or blank"; + + // Body + 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) { + // PRECONDITIONS + assert collectionName != null : "collection must not be null"; + assert collectionName.trim().length() > 0 : "collection must not be empty or blank"; + + // Body + 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; + } + + public void setAppendMDCVariables(boolean appendMDCVariables) { + LoggingEventBsonifier bsonifier = getBsonifier(); + if (bsonifier != null) { + bsonifier.setAppendMDCVariables(appendMDCVariables); + } + } + + /** + * @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); + } 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.: + *

    + *
  • 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 = 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; + } + +} diff --git a/src/main/java/org/log4mongo/MongoDbPatternLayout.java b/src/main/java/org/log4mongo/MongoDbPatternLayout.java index 29de908..6d954c1 100644 --- a/src/main/java/org/log4mongo/MongoDbPatternLayout.java +++ b/src/main/java/org/log4mongo/MongoDbPatternLayout.java @@ -24,102 +24,102 @@ /** * 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); - } + private StringBuffer buf = new StringBuffer(BUF_SIZE); + private StringBuilder builder = new StringBuilder(BUF_SIZE); + private String conversionPattern; + private PatternConverter headConverter; - public MongoDbPatternLayout(String pattern) { - this.conversionPattern = pattern; - headConverter = createPatternParser( - (pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern) - .parse(); - } + public MongoDbPatternLayout() { + this(DEFAULT_CONVERSION_PATTERN); + } - @Override - public void setConversionPattern(String conversionPattern) { - this.conversionPattern = conversionPattern; - headConverter = createPatternParser(conversionPattern).parse(); - } + public MongoDbPatternLayout(String pattern) { + this.conversionPattern = pattern; + headConverter = createPatternParser( + (pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern) + .parse(); + } - @Override - public String getConversionPattern() { - return conversionPattern; - } + @Override + public void setConversionPattern(String conversionPattern) { + this.conversionPattern = conversionPattern; + headConverter = createPatternParser(conversionPattern).parse(); + } - @Override - public PatternParser createPatternParser(String pattern) { - PatternParser parser; - if (pattern == null) - parser = new PatternParser(DEFAULT_CONVERSION_PATTERN); - else - parser = new PatternParser(pattern); + @Override + public String getConversionPattern() { + return conversionPattern; + } - return parser; - } + @Override + public PatternParser createPatternParser(String pattern) { + PatternParser parser; + if (pattern == null) + parser = new PatternParser(DEFAULT_CONVERSION_PATTERN); + else + parser = new PatternParser(pattern); - /** - * 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); + return parser; } - PatternConverter c = headConverter; + /** + * 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); + } - while (c != null) { - if (buf.capacity() > MAX_CAPACITY) { - buf = new StringBuffer(BUF_SIZE); - } else { - buf.setLength(0); - } - c.format(buf, event); + PatternConverter c = headConverter; - // 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); - } + while (c != null) { + if (buf.capacity() > MAX_CAPACITY) { + buf = new StringBuffer(BUF_SIZE); + } else { + buf.setLength(0); + } + c.format(buf, event); - c = c.next; + // 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(); } - return builder.toString(); - } } diff --git a/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java b/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java index 1518c9a..eb71d2f 100644 --- a/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java +++ b/src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java @@ -26,61 +26,61 @@ /** * A Log4J Appender that uses a PatternLayout to write log events into a MongoDB database. - *

+ *

* The conversion pattern specifies the format of a JSON document. The document can contain - * sub-documents and the elements can be strings or arrays. - *

+ * sub-documents and the elements can be strings or arrays. + *

* For some Log4J appenders (especially file appenders) blank space padding is often used to * get fields in adjacent rows to line up. For example, %-5p is often used to make all log levels * the same width in characters. Since each value is stored in a separate property in the document, * it usually doesn't make sense to use blank space padding with MongoDbPatternLayoutAppender. - *

+ *

* 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 MongoDB */ public class MongoDbPatternLayoutAppender extends MongoDbAppender { - @Override - public boolean requiresLayout() { - return (true); - } + @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); + /** + * 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().insert(bson); + } catch (MongoException e) { + errorHandler.error("Failed to insert document to MongoDB", + e, ErrorCode.WRITE_FAILURE); + } + } } - } } - } } diff --git a/src/test/java/org/log4mongo/TestMongoDbAppender.java b/src/test/java/org/log4mongo/TestMongoDbAppender.java index d5a2c2b..ee91462 100644 --- a/src/test/java/org/log4mongo/TestMongoDbAppender.java +++ b/src/test/java/org/log4mongo/TestMongoDbAppender.java @@ -1,305 +1,334 @@ -/* - * 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")); - } - - 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 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 java.util.Date; +import java.util.GregorianCalendar; + +import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +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 + public void testMDCVariables() throws Exception { + assertEquals(0L, countLogEntries()); + + Date now = GregorianCalendar.getInstance().getTime(); + MDC.put("testString", "value"); + MDC.put("testDate", now); + log.warn("Testing MDC"); + assertEquals(1L, countLogEntries()); + assertEquals(1L, countLogEntriesAtLevel("WARN")); + + // verify log content + DBObject entry = collection.findOne(); + assertNotNull(entry); + assertEquals("WARN", entry.get("level")); + assertEquals("Testing MDC", entry.get("message")); + + // check string + assertNotNull(entry.get("testString")); + assertEquals("value", entry.get("testString")); + + // check date + assertNotNull(entry.get("testDate")); + assertEquals(now, entry.get("testDate")); + } + + 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); + } +} diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index 1b61c3c..5e6d6fc 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -1,6 +1,7 @@ -# Set the root logger to -log4j.rootLogger=all, MongoDB - -# MongoDB appender classname -log4j.appender.MongoDB=org.log4mongo.MongoDbAppender -log4j.appender.MongoDB.databaseName=log4mongotest \ No newline at end of file +# Set the root logger to +log4j.rootLogger=all, MongoDB + +# MongoDB appender classname +log4j.appender.MongoDB=org.log4mongo.MongoDbAppender +log4j.appender.MongoDB.databaseName=log4mongotest +log4j.appender.MongoDB.appendMDCVariables=true \ No newline at end of file From 89582bd65c8c1abb31d0fdc0747be6a8dae1e678 Mon Sep 17 00:00:00 2001 From: Alik Kurdyukov Date: Mon, 3 Oct 2011 00:04:17 +0400 Subject: [PATCH 2/2] version updated --- pom.xml | 296 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/pom.xml b/pom.xml index 705b111..1ffb2a6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,148 +1,148 @@ - - - 4.0.0 - org.log4mongo - log4mongo-java - jar - log4mongo-java - Log4J Appender for MongoDB - 0.7.0 - - - - UTF-8 - - - UTF-8 - - - - - - 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 - - - - - Google Code - http://code.google.com/p/log4mongo/issues/list - - - - scm:git:git://github.com/log4mongo/log4mongo-java.git - scm:git:git://github.com/log4mongo/log4mongo-java.git - http://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.6.5 - - - com.wombatnation - privateer - 0.1.1 - test - - - - - - - org.apache.maven.wagon - wagon-webdav - 1.0-beta-2 - - - - - maven-compiler-plugin - - 1.5 - 1.5 - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - release - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - - - - - + + + 4.0.0 + org.log4mongo + log4mongo-java + jar + log4mongo-java + Log4J Appender for MongoDB + 0.7.1-SNAPSHOT + + + + UTF-8 + + + UTF-8 + + + + + + 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 + + + + + Google Code + http://code.google.com/p/log4mongo/issues/list + + + + scm:git:git://github.com/log4mongo/log4mongo-java.git + scm:git:git://github.com/log4mongo/log4mongo-java.git + http://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.6.5 + + + com.wombatnation + privateer + 0.1.1 + test + + + + + + + org.apache.maven.wagon + wagon-webdav + 1.0-beta-2 + + + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + + + +