diff --git a/src/main/java/org/log4mongo/MongoDbPatternLayoutDateAppender.java b/src/main/java/org/log4mongo/MongoDbPatternLayoutDateAppender.java new file mode 100644 index 0000000..dc43985 --- /dev/null +++ b/src/main/java/org/log4mongo/MongoDbPatternLayoutDateAppender.java @@ -0,0 +1,101 @@ +package org.log4mongo; + +import com.mongodb.DBObject; +import com.mongodb.MongoException; +import com.mongodb.util.JSON; + +import java.util.Date; + +import org.apache.log4j.Layout; +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. + * This appender is same as the MongoDbPatternLayoutAppender, only difference is that the + * MongoDbPatternLayoutDateAppender will save pattern layout 'timestamp'(%d) in the mongodb as a + * Date type instead of String. + *
+ * The conversion pattern specifies the format of a JSON document. The document can contain + * 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 MongoDbPatternLayoutDateAppender. + *
+ * 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). + * + */ +public class MongoDbPatternLayoutDateAppender 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. Here timestamp is stored as a Date in mongodb. + *
+ * 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; + String dateKey = getDateKeyFromPatternLayout(layout); + if (dateKey != null) { + //saving time stamp as a date instead of string + bson.put(dateKey, new Date(loggingEvent.getTimeStamp())); + } + } + } + + 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); + } + } + } + } + + /** + * this method returns the key of the date which is mentioned in layout pattern + * @param layout + * @return key of Date (%d) + */ + private String getDateKeyFromPatternLayout(Layout layout) { + String dateKey = null; + String conversionPattern = ((MongoDbPatternLayout)layout).getConversionPattern(); + String[] splitPattern = conversionPattern.split(","); + for (String pattern : splitPattern) { + if (pattern.contains("%d")) { + dateKey = pattern.split(":")[0]; + } + } + // here we are removing double quotes '"' and opening curly braces '{' from the Key + if (dateKey != null) { + dateKey = dateKey.replaceAll("\"|\"$", ""); + dateKey = dateKey.replaceAll("\\{", ""); + } + return dateKey; + } + +} diff --git a/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java b/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java index 80eda75..1e56e22 100644 --- a/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java +++ b/src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java @@ -15,12 +15,17 @@ package org.log4mongo; -import com.mongodb.Mongo; +import com.mongodb.BasicDBObject; import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; import org.apache.log4j.PropertyConfigurator; +import org.bson.Document; import org.junit.*; +import java.util.ArrayList; +import java.util.Collections; + /** * Authentication-related JUnit unit tests for MongoDbAppender. *
@@ -90,7 +95,22 @@ public void testAppenderActivateNoAuth() { */ @Test public void testAppenderActivateWithAuth() { - mongo.getDB(TEST_DATABASE_NAME).addUser(username, password.toCharArray()); + Document result = null; + MongoDatabase db = mongo.getDatabase(TEST_DATABASE_NAME); + BasicDBObject getUsersInfoCommand = new BasicDBObject("usersInfo", + new BasicDBObject("user", username).append("db", TEST_DATABASE_NAME)); + BasicDBObject dropUserCommand = new BasicDBObject("dropUser", username); + BasicDBObject createUserCommand = new BasicDBObject("createUser", username).append("pwd", password).append("roles", + Collections.singletonList(new BasicDBObject("role", "dbOwner").append("db", TEST_DATABASE_NAME))); + + // If test user exists from a previous run, drop it + result = db.runCommand(getUsersInfoCommand); + ArrayList users = (ArrayList) result.get("users"); + if (!users.isEmpty()) { + db.runCommand(dropUserCommand); + } + + db.runCommand(createUserCommand); PropertyConfigurator.configure(LOG4J_AUTH_PROPS); } diff --git a/src/test/java/org/log4mongo/TestMongoDbPatternLayoutDate.java b/src/test/java/org/log4mongo/TestMongoDbPatternLayoutDate.java new file mode 100644 index 0000000..23d9978 --- /dev/null +++ b/src/test/java/org/log4mongo/TestMongoDbPatternLayoutDate.java @@ -0,0 +1,104 @@ +package org.log4mongo; + +import static org.junit.Assert.assertNotNull; + +import java.util.Date; +import java.util.Properties; + +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.mongodb.DBObject; +import com.mongodb.MongoClient; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; + +/** + * JUnit unit tests for PatternLayout style logging with Date as a BSON object. + *
+ * 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.
+ */
+public class TestMongoDbPatternLayoutDate {
+
+ 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 TestMongoDbPatternLayoutDate() 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();
+ }
+
+ /**
+ * Here the timestamp we get from DB is Date type. Hence it is valid here to type cast timestamp into java.util.Date
+ * and it does not throw any exception. If timestamp is stored as string in the db then it will not cast into the
+ * java.util.Date and it will throw typecast exception.
+ */
+ @Test(expected = Test.None.class)
+ public void testDateStoredInMongodbIsISOObject() {
+ PropertyConfigurator.configure(getValidPatternLayoutProperties());
+
+ MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender(
+ APPENDER_NAME);
+
+ collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME);
+ appender.setCollection(collection);
+
+ FindIterable