Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/main/java/org/log4mongo/MongoDbPatternLayoutDateAppender.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* The conversion pattern specifies the format of a JSON document. The document can contain
* sub-documents and the elements can be strings or arrays.
* <p>
* 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.
* <p>
* The appender does <u>not</u> 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.
* <p>
* 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;
}

}
24 changes: 22 additions & 2 deletions src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
Expand Down Expand Up @@ -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);
}

Expand Down
104 changes: 104 additions & 0 deletions src/test/java/org/log4mongo/TestMongoDbPatternLayoutDate.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* Since tests may depend on different Log4J property settings, each test reconfigures an appender
* using a Properties object.
* <p>
* 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<DBObject> 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;
}
}