From 880436673016805e3931dca2292df4a35855a43d Mon Sep 17 00:00:00 2001 From: Pramod R Date: Mon, 13 Aug 2018 14:49:08 +0530 Subject: [PATCH 1/2] Creating new class to store timestamp as a Date. adding new appender to save pattern layout timestamp in the mongodb as a Date type. Before it was getting saved as a string. --- .../MongoDbPatternLayoutDateAppender.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/main/java/org/log4mongo/MongoDbPatternLayoutDateAppender.java 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; + } + +} From 496c606755e24e948b6efe8cb24cb2851c94922a Mon Sep 17 00:00:00 2001 From: Pramod R Date: Fri, 17 Aug 2018 15:51:13 +0530 Subject: [PATCH 2/2] Added JUnits for MongoDbPatternLayoutDateAppender --- .../TestMongoDbPatternLayoutDate.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/test/java/org/log4mongo/TestMongoDbPatternLayoutDate.java 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 entries = collection.find(DBObject.class); + for (DBObject entry : entries) { + assertNotNull(entry); + //here date is type cast into Date. It will not throw the exception as the date saved in DB is ISODate + Date date = (Date)entry.get("timestamp"); + } + } + + private Properties getValidPatternLayoutProperties() { + Properties props = new Properties(); + props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); + props.put("log4j.appender.MongoDBPatternLayout", + "org.log4mongo.MongoDbPatternLayoutDateAppender"); + 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{DATE}\",\"level\":\"%p\",\"class\":\"%c{1}\",\"message\":\"%m\"}"); + return props; + } +}