diff --git a/spring-data-log4j-appender/README.md b/spring-data-log4j-appender/README.md new file mode 100644 index 000000000..3b94302f5 --- /dev/null +++ b/spring-data-log4j-appender/README.md @@ -0,0 +1,52 @@ +# MongoDB Log4J Appender + +This module sets up a Log4J appender that puts logging events in MongoDB. It is fully configurable +and connects directly to the MongoDB server using the driver. It has no dependency on any Spring package. + +To use it, configure a host, port, (optionally) applicationId, and database property in your Log4J configuration: + + log4j.appender.stdout=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + log4j.appender.stdout.host = localhost + log4j.appender.stdout.port = 27017 + log4j.appender.stdout.database = logs + log4j.appender.stdout.applicationId = my.application + log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE + +It will even support properties in your MDC (so long as they're Strings or support .toString()). + +The collection name is configurable as well. If you don't specify anything, it will use the Category name. +If you want to specify a collection name, you can give it a String.format() string which will be passed the +following parameters: + +1. Calendar.YEAR +2. Calendar.MONTH +3. Calendar.DAY_OF_MONTH +4. Calendar.HOUR_OF_DAY +5. event.getLevel().toString() +6. event.getLogger().getName() + +An example log entry might look like: + + { + "_id" : ObjectId("4d89341a8ef397e06940d5cd"), + "applicationId" : "my.application", + "name" : "org.springframework.data.document.mongodb.log4j.AppenderTest", + "level" : "DEBUG", + "timestamp" : "1300837402444", + "properties" : { + "property" : "one" + }, + "message" : "DEBUG message" + } + +To set WriteConcern levels for WARN or higher messages, set warnOrHigherWriteConcern to one of the following: + +* FSYNC_SAFE +* NONE +* NORMAL +* REPLICAS_SAFE +* SAFE + +(http://api.mongodb.org/java/2.5-pre-/com/mongodb/WriteConcern.html#field_detail)[http://api.mongodb.org/java/2.5-pre-/com/mongodb/WriteConcern.html#field_detail] \ No newline at end of file diff --git a/spring-data-log4j-appender/pom.xml b/spring-data-log4j-appender/pom.xml new file mode 100644 index 000000000..d73e5e185 --- /dev/null +++ b/spring-data-log4j-appender/pom.xml @@ -0,0 +1,163 @@ + + 4.0.0 + + org.springframework.data + spring-data-document-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-data-document-parent/pom.xml + + spring-data-log4j-appender + jar + Spring Data MongoDB Log4J Appender + + + 2.3 + + + + + + + org.springframework + spring-beans + + + org.springframework + spring-tx + + + org.springframework + spring-expression + + + org.springframework + spring-test + test + + + + + org.springframework.data + spring-data-document-core + ${project.version} + + + org.springframework.data + spring-data-commons-core + ${data.commons.version} + + + + + org.mongodb + mongo-java-driver + ${mongo.version} + + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + compile + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + compile + + + + + org.mockito + mockito-all + test + + + + org.hamcrest + hamcrest-all + 1.1 + test + + + + junit + junit + test + + + + joda-time + joda-time + 1.6 + test + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + com.mysema.maven + maven-apt-plugin + 1.0 + + + generate-test-sources + + test-process + + + target/generated-sources/test-annotations + org.springframework.data.document.mongodb.repository.MongoAnnotationProcessor + + + + + + + + + + querydsl + Mysema QueryDsl + http://source.mysema.com/maven2/releases + + false + + + + diff --git a/spring-data-log4j-appender/src/main/java/org/springframework/data/document/mongodb/log4j/MongoLog4jAppender.java b/spring-data-log4j-appender/src/main/java/org/springframework/data/document/mongodb/log4j/MongoLog4jAppender.java new file mode 100644 index 000000000..db850b73c --- /dev/null +++ b/spring-data-log4j-appender/src/main/java/org/springframework/data/document/mongodb/log4j/MongoLog4jAppender.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.springframework.data.document.mongodb.log4j; + +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Map; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.Mongo; +import com.mongodb.WriteConcern; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.spi.LoggingEvent; + +/** + * @author Jon Brisbin + */ +public class MongoLog4jAppender extends AppenderSkeleton { + + public static final String LEVEL = "level"; + public static final String NAME = "name"; + public static final String APP_ID = "applicationId"; + public static final String TIMESTAMP = "timestamp"; + public static final String PROPERTIES = "properties"; + public static final String TRACEBACK = "traceback"; + public static final String MESSAGE = "message"; + + protected String host = "localhost"; + protected int port = 27017; + protected String database = "logs"; + protected String collection = null; + protected String applicationId = System.getProperty("APPLICATION_ID", null); + protected WriteConcern warnOrHigherWriteConcern = WriteConcern.SAFE; + protected WriteConcern infoOrLowerWriteConcern = WriteConcern.NORMAL; + protected Mongo mongo; + protected DB db; + + public MongoLog4jAppender() { + } + + public MongoLog4jAppender(boolean isActive) { + super(isActive); + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public void setWarnOrHigherWriteConcern(String wc) { + this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc); + } + + public String getWarnOrHigherWriteConcern() { + return warnOrHigherWriteConcern.toString(); + } + + public String getInfoOrLowerWriteConcern() { + return infoOrLowerWriteConcern.toString(); + } + + public void setInfoOrLowerWriteConcern(String wc) { + this.infoOrLowerWriteConcern = WriteConcern.valueOf(wc); + } + + protected void connectToMongo() throws UnknownHostException { + this.mongo = new Mongo(host, port); + this.db = mongo.getDB(database); + } + + @SuppressWarnings({"unchecked"}) + @Override + protected void append(final LoggingEvent event) { + if (null == db) { + try { + connectToMongo(); + } catch (UnknownHostException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + BasicDBObject dbo = new BasicDBObject(); + dbo.put(APP_ID, applicationId); + dbo.put(NAME, event.getLogger().getName()); + dbo.put(LEVEL, event.getLevel().toString()); + dbo.put(TIMESTAMP, String.format("%s", event.getTimeStamp())); + + // Copy properties into document + Map props = event.getProperties(); + if (null != props && props.size() > 0) { + BasicDBObject propsDbo = new BasicDBObject(); + for (Map.Entry entry : props.entrySet()) { + propsDbo.put(entry.getKey().toString(), entry.getValue().toString()); + } + dbo.put(PROPERTIES, propsDbo); + } + + // Copy traceback info (if there is any) into the document + String[] traceback = event.getThrowableStrRep(); + if (null != traceback && traceback.length > 0) { + BasicDBList tbDbo = new BasicDBList(); + tbDbo.addAll(Arrays.asList(traceback)); + dbo.put(TRACEBACK, tbDbo); + } + + // Put the rendered message into the document + dbo.put(MESSAGE, event.getRenderedMessage()); + + // Insert the document + if (null == collection) { + // Use the category name + collection = event.getLogger().getName(); + } else { + Calendar now = Calendar.getInstance(); + collection = String.format(collection, + now.get(Calendar.YEAR), + now.get(Calendar.MONTH), + now.get(Calendar.DAY_OF_MONTH), + now.get(Calendar.HOUR_OF_DAY), + event.getLevel().toString(), + event.getLogger().getName()); + } + + WriteConcern wc; + if (event.getLevel().isGreaterOrEqual(Level.WARN)) { + wc = warnOrHigherWriteConcern; + } else { + wc = infoOrLowerWriteConcern; + } + db.getCollection(collection).insert(dbo, wc); + } + + public void close() { + mongo.close(); + } + + public boolean requiresLayout() { + return true; + } +} diff --git a/spring-data-log4j-appender/src/test/java/org/springframework/data/document/mongodb/log4j/AppenderTest.java b/spring-data-log4j-appender/src/test/java/org/springframework/data/document/mongodb/log4j/AppenderTest.java new file mode 100644 index 000000000..de0793e5c --- /dev/null +++ b/spring-data-log4j-appender/src/test/java/org/springframework/data/document/mongodb/log4j/AppenderTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.springframework.data.document.mongodb.log4j; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.net.UnknownHostException; + +import com.mongodb.DB; +import com.mongodb.DBCursor; +import com.mongodb.Mongo; +import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Jon Brisbin + */ +public class AppenderTest { + + private static final String NAME = AppenderTest.class.getName(); + private Logger log = Logger.getLogger(NAME); + private Mongo mongo; + private DB db; + + @Before + public void setup() { + try { + mongo = new Mongo("localhost", 27017); + db = mongo.getDB("logs"); + db.getCollection(NAME).drop(); + } catch (UnknownHostException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Test + public void testLogging() { + log.debug("DEBUG message"); + log.info("INFO message"); + log.warn("WARN message"); + log.error("ERROR message"); + + DBCursor msgs = db.getCollection(NAME).find(); + assertThat(msgs.count(), is(4)); + + } + + @Test + public void testProperties() { + MDC.put("property", "one"); + log.debug("DEBUG message"); + } + +} diff --git a/spring-data-log4j-appender/src/test/resources/log4j.properties b/spring-data-log4j-appender/src/test/resources/log4j.properties new file mode 100644 index 000000000..c82981c8e --- /dev/null +++ b/spring-data-log4j-appender/src/test/resources/log4j.properties @@ -0,0 +1,19 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.stdout.host = localhost +log4j.appender.stdout.port = 27017 +log4j.appender.stdout.database = logs +log4j.appender.stdout.applicationId = my.application +log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE + +log4j.category.org.apache.activemq=ERROR +log4j.category.org.springframework.batch=DEBUG +log4j.category.org.springframework.data.document.mongodb=DEBUG +log4j.category.org.springframework.transaction=INFO + +log4j.category.org.hibernate.SQL=DEBUG +# for debugging datasource initialization +# log4j.category.test.jdbc=DEBUG diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java index b421c5b34..2766b3a03 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java @@ -33,7 +33,6 @@ import org.springframework.data.util.TypeInformation; /** * @author Jon Brisbin - * @author Oliver Gierke */ public class MongoMappingContext extends BasicMappingContext { @@ -77,4 +76,5 @@ public class MongoMappingContext extends BasicMappingContext { throws MappingConfigurationException { return new MongoPersistentEntity(mappingContext, typeInformation); } + }