├── .gitignore ├── LICENSE.txt ├── MAINTAINERS ├── README.md ├── buildfile ├── log4j.properties.sample ├── pom.xml └── src ├── main └── java │ └── org │ └── log4mongo │ ├── BsonAppender.java │ ├── ExtendedMongoDbAppender.java │ ├── LoggingEventBsonifier.java │ ├── LoggingEventBsonifierImpl.java │ ├── MongoDbAppender.java │ ├── MongoDbPatternLayout.java │ ├── MongoDbPatternLayoutAppender.java │ ├── MongoDbPatternLayoutDateAppender.java │ └── contrib │ ├── HostInfoPatternLayout.java │ └── HostInfoPatternParser.java └── test ├── java └── org │ └── log4mongo │ ├── CustomPatternLayout.java │ ├── CustomPatternParser.java │ ├── TestExtendedMongoDbAppender.java │ ├── TestLoggingEventBsonifierImpl.java │ ├── TestMongoDbAppender.java │ ├── TestMongoDbAppenderAuth.java │ ├── TestMongoDbAppenderHosts.java │ ├── TestMongoDbPatternLayout.java │ ├── TestMongoDbPatternLayoutDate.java │ ├── TestWriteConcern.java │ └── WrappedLogger.java └── resources ├── log4j.properties ├── log4j_auth.properties ├── log4j_extended.properties ├── log4j_extended.xml └── log4j_write_concern.properties /.gitignore: -------------------------------------------------------------------------------- 1 | reports 2 | target 3 | *.iml 4 | *.ipr 5 | *.iws 6 | .classpath 7 | .project 8 | .settings 9 | *.swp 10 | *.swo 11 | bin 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Robert Stewart 2 | E-mail: robert@wombatnation.com 3 | Userid: RobertStewart 4 | 5 | Peter Monks 6 | E-mail: pmonks@gmail.com 7 | Userid: pmonks 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Log4mongo-java 2 | ================ 3 | 4 | [Project site](https://log4mongo.atlassian.net/wiki/display/LOG4MONGO/Log4mongo+for+Java) 5 | 6 | [Source code on GitHub](http://github.com/log4mongo/log4mongo-java) 7 | 8 | # Security Warning 9 | Continued use of log4mongo-java is no longer recommended due to a security issue with log4j 1.x that will not be fixed. An upgrade to log4j 2.x is possible but is not planned by the current maintainers of log4mongo-java. Instead you should consider using the [Log4J MongoDB4 Appender](https://logging.apache.org/log4j/2.x/log4j-mongodb4/index.html). 10 | 11 | # Description 12 | This library provides Log4J Appenders [1] that write log events to the 13 | MongoDB document oriented database [2]. 14 | 15 | * MongoDbAppender - Stores a BSONified version of the Log4J LoggingEvent 16 | * ExtendedMongoDbAppender - Extends MongoDbAppender by allowing you to add top level elements 17 | * MongoDbPatternLayoutAppender - Uses standard Log4J pattern layout, parser and converter classes to store a log message as a custom-formatted document 18 | * MongoDbPatternLayoutDateAppender - Similar to MongoDbPatternLayoutAppender, but stores dates as ISODate objects rather than strings. 19 | 20 | More details are at the [Project site](https://log4mongo.atlassian.net/wiki/display/LOG4MONGO/Log4mongo+for+Java) 21 | 22 | # Authors 23 | * Peter Monks (pmonks@gmail.com) 24 | * Robert Stewart (robert@wombatnation.com) 25 | 26 | # Contributors 27 | * Jozef Sevcik (sevcik@codescale.net) 28 | * Zach Bailey (znbailey@gmail.com) 29 | * Gabriel Eisbruch (gabrieleisbruch@gmail.com) 30 | * cskinfill 31 | * Mick Knutson 32 | * Jay Patel 33 | * Šimon Schierreich 34 | * Pramod R 35 | 36 | # Pre-requisites 37 | * JDK 1.8+ 38 | * MongoDB Server v3.0+ (tested with 4.0.1) 39 | * MongoDB Java Driver v3.0+ (tested with 3.4.2) 40 | * Log4J 1.2+ (tested with 1.2.17 - note: tests won't work on earlier versions due to Log4J API changes) 41 | * Privateer (used only in unit tests - a copy is in the lib dir, in case you can't get it 42 | from the central Maven repo) 43 | 44 | ## Additional dependency notes: 45 | * Version 0.3.2 of log4mongo-java will work with MongoDB Java driver versions only up 46 | to 2.0. The 2.1 driver includes a source compatible, but binary incompatible, change to 47 | a DBCollection.insert() method used by log4mongo-java. 48 | 49 | 50 | # Installation / Build / Configuration 51 | If you downloaded a pre-built jar file, skip step 4. 52 | 53 | 1. Start local MongoDB servers running as a replica set. This is required for the replica set 54 | part of the unit tests. The --smallfiles arg makes the unit tests run about twice as fast, 55 | since databases are created and dropped several times, though it generally should not 56 | be used in production. The --noprealloc option speeds up tests and should also not generally 57 | be used in production. 58 | 59 | $ sudo mkdir -p /var/data/r{0,1,2} 60 | $ sudo chown -R `whoami` /var/data 61 | $ mongod --replSet foo --smallfiles --noprealloc --port 27017 --dbpath /var/data/r0 62 | $ mongod --replSet foo --smallfiles --noprealloc --port 27018 --dbpath /var/data/r1 63 | $ mongod --replSet foo --smallfiles --noprealloc --port 27019 --dbpath /var/data/r2 64 | 65 | 2. If this is the first time you have set up this replica set, you'll need to initiate it from the mongo shell: 66 | 67 | $ mongo 68 | > config = {"_id": "foo", members:[{_id: 0, host: '127.0.0.1:27017'},{_id: 1, host: '127.0.0.1:27018'},{_id: 2, host: '127.0.0.1:27019', arbiterOnly: true}]} 69 | > rs.initiate(config) 70 | 71 | 3. Wait about a minute until the replica set is established. You can run rs.status() in the mongo shell to look for direct confirmation it is ready. 72 | 73 | 4. Build the JAR file using Maven2. The following command will run all the unit tests. 74 | 75 | $ mvn clean package 76 | 77 | 5. Deploy the target/log4mongo-java-x.y.jar file, along with the Log4J and MongoDB 78 | Java driver jars, into the classpath of your Java application 79 | 80 | 6. Configure log4j as usual, referring to the log4j.properties.sample file for 81 | the specific configuration properties the appender supports. The Java package for 82 | the classes changed to org.log4mongo in the 0.7 release, so make sure you specify 83 | the fully qualified class name of the appender class correctly. 84 | 85 | The TestMongoDbAppenderHosts test case tests logging to replica sets. See notes in that test case 86 | for starting multiple mongod instances as a replica set. 87 | 88 | 89 | # ToDos 90 | * More unit tests 91 | * connection failures 92 | 93 | * Clean up BSONification code - currently it's functional but skanky. 94 | Consider using daybreak for this [4]. 95 | 96 | 97 | # Notes on Date Handling 98 | MongoDB (actually BSON) supports datetimes as a native data type [5] 99 | and all drivers are supposed to handle conversion from client-native 100 | date type (java.util.Date in Java) to BSON representation of date in miliseconds 101 | since the Unix epoch. 102 | 103 | However, MongoDB built-in console (bin/mongo) does represent dates formatted, 104 | even the dates were saved in native data type, which may be confusing [6]. 105 | See testTimestampStoredNatively in tests (TestMongoDbAppender.java) if you want to get an idea. 106 | 107 | # References 108 | * [1] http://logging.apache.org/log4j/1.2/index.html 109 | * [2] http://www.mongodb.org/ 110 | * [3] http://github.com/mongodb/mongo-java-driver/downloads 111 | * [4] http://github.com/maxaf/daybreak 112 | * [5] http://bsonspec.org/#/specification 113 | * [6] http://groups.google.com/group/mongodb-user/browse_thread/thread/e59cbc8c9ba30411/af061b4bdbce5287 114 | -------------------------------------------------------------------------------- /buildfile: -------------------------------------------------------------------------------- 1 | # Generated by Buildr 1.4.0, change to your liking 2 | # Standard maven2 repository 3 | repositories.remote << 'http://www.ibiblio.org/maven2' 4 | # Version number for this release 5 | VERSION_NUMBER = `git describe --tags`.strip 6 | 7 | desc 'Log4J Appender for MongoDB' 8 | define 'log4mongo-java' do 9 | project.group = 'org.log4mongo' 10 | project.version = VERSION_NUMBER 11 | compile.with 'junit:junit:jar:4.8.2', 'log4j:log4j:jar:1.2.16', 'org.mongodb:mongo-java-driver:jar:2.12.4' 12 | package :jar, :id => 'log4mongo-java' 13 | end 14 | -------------------------------------------------------------------------------- /log4j.properties.sample: -------------------------------------------------------------------------------- 1 | # Set the root logger to 2 | log4j.rootLogger=error, MongoDB 3 | 4 | # MongoDB appender classname 5 | # To log with a PatternLayout, use org.log4mongo.MongoDbPatternLayoutAppender 6 | log4j.appender.MongoDB=org.log4mongo.MongoDbAppender 7 | 8 | # MongoDB appender properties 9 | # All are optional - defaults shown below (except for userName and password, which default to undefined) 10 | # If using a replica set, set hostname to blank space-delimited list of host seeds. Don't include arbiters. 11 | # Also, set port to either one port that all hosts will use or space-delimited list of one port per hostname 12 | log4j.appender.MongoDB.hostname=localhost 13 | log4j.appender.MongoDB.port=27017 14 | log4j.appender.MongoDB.databaseName=log4mongo 15 | log4j.appender.MongoDB.collectionName=log 16 | log4j.appender.MongoDB.userName=open 17 | log4j.appender.MongoDB.password=sesame 18 | 19 | # The layout property is required only if the MongoDbPatternLayoutAppender appender is used. 20 | # If a custom PatternParser and custom PatternConverters are required to log additional data, 21 | # the specified layout class must extend MongoDbPatternLayout. A ConversionPattern property 22 | # should also be specified. 23 | #log4j.appender.MongoDB.layout=org.log4mongo.MongoDbPatternLayout 24 | 25 | # The ConversionPattern property is required only if MongoDbPatternLayoutAppender is used. 26 | # The pattern must be a valid JSON document. The value will typically contain one or more 27 | # converter characters that are replaced when a message is logged. A key cannot begin with $, 28 | # contain a . or be equal to _id due to BSON naming restrictions. The JSON document can have 29 | # sub-documents. Values can be strings or arrays. 30 | #log4j.appender.MongoDB.layout.ConversionPattern={"timestamp":"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}","level":"%p","class":"%c{1}","message":"%m"} 31 | 32 | # Add optional root level elements to each log event 33 | #log4j.appender.MongoDB.rootLevelProperties=applicationName=MyProject&eventType=Development -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | org.log4mongo 7 | log4mongo-java 8 | jar 9 | log4mongo-java 10 | Log4J Appender for MongoDB 11 | 0.9.1 12 | 13 | 14 | org.sonatype.oss 15 | oss-parent 16 | 7 17 | 18 | 19 | 20 | 21 | UTF-8 22 | 23 | 24 | UTF-8 25 | 26 | 27 | 28 | 29 | 30 | Apache 2 31 | http://www.apache.org/licenses/LICENSE-2.0.txt 32 | repo 33 | 34 | 35 | 36 | 37 | 38 | pmonks 39 | Peter Monks 40 | pmonks@gmail.com 41 | 42 | 43 | jsk 44 | Jozef Sevcik 45 | sevcik@styxys.com 46 | 47 | 48 | wombatnation 49 | Robert Stewart 50 | robert@wombatnation.com 51 | 52 | 53 | ScheRas 54 | Šimon Schierreich 55 | simon.schierreich@messenger.cz 56 | 57 | 58 | 59 | 60 | Github 61 | https://github.com/log4mongo/log4mongo-java/issues 62 | 63 | 64 | 65 | scm:git:git@github.com:log4mongo/log4mongo-java.git 66 | scm:git:git@github.com:log4mongo/log4mongo-java.git 67 | scm:git:git@github.com:log4mongo/log4mongo-java 68 | 69 | 70 | 71 | 72 | log4j 73 | log4j 74 | 1.2.17 75 | provided 76 | 77 | 78 | org.mongodb 79 | mongo-java-driver 80 | 3.4.2 81 | 82 | 83 | javax.xml.bind 84 | jaxb-api 85 | 2.3.0 86 | 87 | 88 | 89 | junit 90 | junit 91 | [4.13.1,) 92 | test 93 | 94 | 95 | com.wombatnation 96 | privateer 97 | 0.1.1 98 | test 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.wagon 106 | wagon-webdav 107 | 1.0-beta-2 108 | 109 | 110 | 111 | 112 | maven-compiler-plugin 113 | 3.6.0 114 | 115 | 1.8 116 | 1.8 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-release-plugin 122 | 2.5.3 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-gpg-plugin 127 | 1.6 128 | 129 | 130 | sign-artifacts 131 | verify 132 | 133 | sign 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-source-plugin 141 | 3.0.1 142 | 143 | 144 | attach-sources 145 | 146 | jar 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | release 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-javadoc-plugin 162 | 163 | 164 | attach-javadocs 165 | 166 | jar 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/BsonAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo; 19 | 20 | import org.apache.log4j.AppenderSkeleton; 21 | import org.apache.log4j.spi.LoggingEvent; 22 | import org.bson.BSONObject; 23 | 24 | /** 25 | * Abstract Log4J Appender class that stores log events in the BSON format. Concrete implementation 26 | * classes must implement append(DBObject) to store the BSON representation of a LoggingEvent. 27 | *

28 | * An example BSON structure for a single log entry is as follows: 29 | *

30 | * 31 | *
 32 |  * {
 33 |  *   "_id"        : ObjectId("f1c0895fd5eee04a445deb00"),
 34 |  *   "timestamp"  : "Thu Oct 22 2009 16:46:29 GMT-0700 (Pacific Daylight Time)",
 35 |  *   "level"      : "ERROR",
 36 |  *   "thread"     : "main",
 37 |  *   "message"    : "Error entry",
 38 |  *   "fileName"   : "TestMongoDbAppender.java",
 39 |  *   "method"     : "testLogWithChainedExceptions",
 40 |  *   "lineNumber" : "147",
 41 |  *   "loggerName" : {
 42 |  *                    "fullyQualifiedClassName" : "org.log4mongo.TestMongoDbAppender",
 43 |  *                    "package"                 : [ "org", "log4mongo" ],
 44 |  *                    "className"               : "TestMongoDbAppender"
 45 |  *                  },
 46 |  *   "class"      : {
 47 |  *                    "fullyQualifiedClassName" : "org.log4mongo.TestMongoDbAppender",
 48 |  *                    "package"                 : [ "org", "log4mongo" ],
 49 |  *                    "className"               : "TestMongoDbAppender"
 50 |  *                  },
 51 |  *   "throwables" : [
 52 |  *                    {
 53 |  *                      "message"    : "I'm an innocent bystander.",
 54 |  *                      "stackTrace" : [
 55 |  *                                       {
 56 |  *                                         "fileName"   : "TestMongoDbAppender.java",
 57 |  *                                         "method"     : "testLogWithChainedExceptions",
 58 |  *                                         "lineNumber" : 147,
 59 |  *                                         "class"      : {
 60 |  *                                                          "fullyQualifiedClassName" :
 61 |  * "org.log4mongo.TestMongoDbAppender",
 62 |  *                                                          "package"                 : [ "org", "log4mongo" ],
 63 |  *                                                          "className"               : "TestMongoDbAppender"
 64 |  *                                                        }
 65 |  *                                       },
 66 |  *                                       {
 67 |  *                                         "method"     : "invoke0",
 68 |  *                                         "lineNumber" : -2,
 69 |  *                                         "class"      : {
 70 |  *                                                          "fullyQualifiedClassName" :
 71 |  * "sun.reflect.NativeMethodAccessorImpl",
 72 |  *                                                          "package"                 : [ "sun", "reflect" ],
 73 |  *                                                          "className"               : "NativeMethodAccessorImpl"
 74 |  *                                                        }
 75 |  *                                       },
 76 |  *                                       ...
 77 |  *                                     ]
 78 |  *                    },
 79 |  *                    {
 80 |  *                      "message" : "I'm the real culprit!",
 81 |  *                      "stackTrace" : [
 82 |  *                                       {
 83 |  *                                         "fileName" : "TestMongoDbAppender.java",
 84 |  *                                         "method" : "testLogWithChainedExceptions",
 85 |  *                                         "lineNumber" : 145,
 86 |  *                                         "class" : {
 87 |  *                                                     "fullyQualifiedClassName" : "org.log4mongo.TestMongoDbAppender",
 88 |  *                                                     "package"                 : [ "org", "log4mongo" ],
 89 |  *                                                     "className"               : "TestMongoDbAppender"
 90 |  *                                                   }
 91 |  *                                       },
 92 |  *                                       ...
 93 |  *                                     ]
 94 |  *                    }
 95 |  *                  ]
 96 |  * }
 97 |  * 
98 | * 99 | * @see Log4J 100 | * Appender Interface 101 | * @see MongoDB 102 | */ 103 | public abstract class BsonAppender extends AppenderSkeleton { 104 | 105 | private LoggingEventBsonifier bsonifier = new LoggingEventBsonifierImpl(); 106 | 107 | /** 108 | * @see org.apache.log4j.Appender#requiresLayout() 109 | */ 110 | public boolean requiresLayout() { 111 | return (false); 112 | } 113 | 114 | /** 115 | * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent) 116 | */ 117 | @Override 118 | protected void append(final LoggingEvent loggingEvent) { 119 | BSONObject bson = bsonifier.bsonify(loggingEvent); 120 | append(bson); 121 | } 122 | 123 | /** 124 | * Method implemented by a concrete class to store the BSON object. 125 | * 126 | * @param bson 127 | * The BSON representation of a Logging Event that will be stored 128 | */ 129 | protected abstract void append(BSONObject bson); 130 | 131 | /** 132 | * @return Object used to Bsonify LoggingEvent objects 133 | */ 134 | public LoggingEventBsonifier getBsonifier() { 135 | return bsonifier; 136 | } 137 | 138 | /** 139 | * @param bsonifier 140 | * Object used to Bsonify LoggingEvent objects 141 | */ 142 | public void setBsonifier(LoggingEventBsonifier bsonifier) { 143 | this.bsonifier = bsonifier; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/ExtendedMongoDbAppender.java: -------------------------------------------------------------------------------- 1 | package org.log4mongo; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.DBObject; 5 | import org.bson.BSONObject; 6 | 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * This appender is designed so you can add top level elements to each logging entry. Users can also 12 | * extend MongoDbAppender themselves in order to add the top level elements. 13 | *

14 | * Use case: A desire to use a common appender for unified logs across different code bases, such 15 | * that commonly logged elements be consistent, such as application, eventType, etc. This is enabled 16 | * by adding a property called rootLevelProperties with a key=value list of elements to be added to 17 | * the root level log. See log4j.properties.sample for an example. 18 | * 19 | * @author Mick Knutson (http://www.baselogic.com) 20 | */ 21 | public class ExtendedMongoDbAppender extends MongoDbAppender { 22 | 23 | private DBObject constants; 24 | 25 | private Map rootProperties = new LinkedHashMap(); 26 | 27 | /** 28 | * @see org.apache.log4j.AppenderSkeleton#activateOptions() 29 | */ 30 | @Override 31 | public void activateOptions() { 32 | super.activateOptions(); 33 | initTopLevelProperties(); 34 | } 35 | 36 | /** 37 | * Initialize custom top level elements to appear in a log event 38 | *

39 | * Allows users to create custom properties to be added to the top level log event. 40 | */ 41 | public void initTopLevelProperties() { 42 | constants = new BasicDBObject(); 43 | if (!rootProperties.isEmpty()) { 44 | constants.putAll(rootProperties); 45 | } 46 | } 47 | 48 | /** 49 | * This will handle spaces and empty values A = minus- @amp; C=equals= @amp; E==F 50 | * For XML, must escape the ampersand. 51 | * 52 | * @param rootLevelProperties 53 | * key=value list of elements to be added to the root level log 54 | */ 55 | public void setRootLevelProperties(String rootLevelProperties) { 56 | for (String keyValue : rootLevelProperties.split(" *& *")) { 57 | String[] pairs = keyValue.split(" *= *", 2); 58 | rootProperties.put(pairs[0], pairs.length == 1 ? "" : pairs[1]); 59 | } 60 | } 61 | 62 | /** 63 | * @param bson 64 | * The BSON object to insert into a MongoDB database collection. 65 | */ 66 | @Override 67 | public void append(BSONObject bson) { 68 | if (this.isInitialized() && bson != null) { 69 | if (constants != null) { 70 | bson.putAll(constants); 71 | } 72 | super.append(bson); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/LoggingEventBsonifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo; 19 | 20 | import org.apache.log4j.spi.LoggingEvent; 21 | import org.bson.BSONObject; 22 | 23 | /** 24 | * Interface implemented by classes that create a BSON representation of a Log4J LoggingEvent. 25 | * LoggingEventBsonifierImpl is the default implementation. 26 | */ 27 | public interface LoggingEventBsonifier { 28 | 29 | BSONObject bsonify(LoggingEvent loggingEvent); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/LoggingEventBsonifierImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo; 19 | 20 | import com.mongodb.BasicDBList; 21 | import com.mongodb.BasicDBObject; 22 | import com.mongodb.DBObject; 23 | import org.apache.log4j.helpers.LogLog; 24 | import org.apache.log4j.spi.LocationInfo; 25 | import org.apache.log4j.spi.LoggingEvent; 26 | import org.apache.log4j.spi.ThrowableInformation; 27 | import org.bson.BSONObject; 28 | 29 | import java.lang.management.ManagementFactory; 30 | import java.net.InetAddress; 31 | import java.net.UnknownHostException; 32 | import java.util.Arrays; 33 | import java.util.Date; 34 | import java.util.List; 35 | import java.util.Map; 36 | 37 | /** 38 | * Default implementation class for creating a BSON representation of a Log4J LoggingEvent. 39 | */ 40 | public class LoggingEventBsonifierImpl implements LoggingEventBsonifier { 41 | 42 | // Main log event elements 43 | private static final String KEY_TIMESTAMP = "timestamp"; 44 | 45 | private static final String KEY_LEVEL = "level"; 46 | 47 | private static final String KEY_THREAD = "thread"; 48 | 49 | private static final String KEY_MESSAGE = "message"; 50 | 51 | private static final String KEY_LOGGER_NAME = "loggerName"; 52 | 53 | // Source code location 54 | private static final String KEY_FILE_NAME = "fileName"; 55 | 56 | private static final String KEY_METHOD = "method"; 57 | 58 | private static final String KEY_LINE_NUMBER = "lineNumber"; 59 | 60 | private static final String KEY_CLASS = "class"; 61 | 62 | // Class info 63 | private static final String KEY_FQCN = "fullyQualifiedClassName"; 64 | 65 | private static final String KEY_PACKAGE = "package"; 66 | 67 | private static final String KEY_CLASS_NAME = "className"; 68 | 69 | // Exceptions 70 | private static final String KEY_THROWABLES = "throwables"; 71 | 72 | private static final String KEY_EXCEPTION_MESSAGE = "message"; 73 | 74 | private static final String KEY_STACK_TRACE = "stackTrace"; 75 | 76 | // Host and Process Info 77 | private static final String KEY_HOST = "host"; 78 | 79 | private static final String KEY_PROCESS = "process"; 80 | 81 | private static final String KEY_HOSTNAME = "name"; 82 | 83 | private static final String KEY_IP = "ip"; 84 | 85 | // MDC Properties 86 | private static final String KEY_MDC_PROPERTIES = "properties"; 87 | 88 | private final DBObject hostInfo = new BasicDBObject(); 89 | 90 | public LoggingEventBsonifierImpl() { 91 | setupNetworkInfo(); 92 | } 93 | 94 | private void setupNetworkInfo() { 95 | hostInfo.put(KEY_PROCESS, ManagementFactory.getRuntimeMXBean().getName()); 96 | try { 97 | hostInfo.put(KEY_HOSTNAME, InetAddress.getLocalHost().getHostName()); 98 | hostInfo.put(KEY_IP, InetAddress.getLocalHost().getHostAddress()); 99 | } catch (UnknownHostException e) { 100 | LogLog.warn(e.getMessage()); 101 | } 102 | } 103 | 104 | /** 105 | * BSONifies a single Log4J LoggingEvent object. 106 | * 107 | * @param loggingEvent 108 | * The LoggingEvent object to BSONify (may be null). 109 | * 110 | * @return The BSONified equivalent of the LoggingEvent object (may be null). 111 | */ 112 | public BSONObject bsonify(final LoggingEvent loggingEvent) { 113 | DBObject result = null; 114 | 115 | if (loggingEvent != null) { 116 | result = new BasicDBObject(); 117 | 118 | result.put(KEY_TIMESTAMP, new Date(loggingEvent.getTimeStamp())); 119 | nullSafePut(result, KEY_LEVEL, loggingEvent.getLevel().toString()); 120 | nullSafePut(result, KEY_THREAD, loggingEvent.getThreadName()); 121 | nullSafePut(result, KEY_MESSAGE, loggingEvent.getRenderedMessage()); 122 | nullSafePut(result, KEY_LOGGER_NAME, bsonifyClassName(loggingEvent.getLoggerName())); 123 | 124 | addMDCInformation(result, loggingEvent.getProperties()); 125 | addLocationInformation(result, loggingEvent.getLocationInformation()); 126 | addThrowableInformation(result, loggingEvent.getThrowableInformation()); 127 | addHostnameInformation(result); 128 | } 129 | 130 | return (result); 131 | } 132 | 133 | /** 134 | * Adds MDC Properties to the DBObject. 135 | * 136 | * @param bson 137 | * The root DBObject 138 | * @param props 139 | * MDC Properties to be logged 140 | */ 141 | protected void addMDCInformation(DBObject bson, final Map props) { 142 | if (props != null && props.size() > 0) { 143 | 144 | BasicDBObject mdcProperties = new BasicDBObject(); 145 | String key; 146 | // Copy MDC properties into document 147 | for (Map.Entry entry : props.entrySet()) { 148 | key = (entry.getKey().toString().contains(".")) ? entry.getKey().toString() 149 | .replaceAll("\\.", "_") : entry.getKey().toString(); 150 | nullSafePut(mdcProperties, key, entry.getValue().toString()); 151 | } 152 | bson.put(KEY_MDC_PROPERTIES, mdcProperties); 153 | } 154 | } 155 | 156 | /** 157 | * Adds the LocationInfo object to an existing BSON object. 158 | * 159 | * @param bson 160 | * The BSON object to add the location info to (must not be null). 161 | * @param locationInfo 162 | * The LocationInfo object to add to the BSON object (may be null). 163 | */ 164 | protected void addLocationInformation(DBObject bson, final LocationInfo locationInfo) { 165 | if (locationInfo != null) { 166 | nullSafePut(bson, KEY_FILE_NAME, locationInfo.getFileName()); 167 | nullSafePut(bson, KEY_METHOD, locationInfo.getMethodName()); 168 | nullSafePut(bson, KEY_LINE_NUMBER, locationInfo.getLineNumber()); 169 | nullSafePut(bson, KEY_CLASS, bsonifyClassName(locationInfo.getClassName())); 170 | } 171 | } 172 | 173 | /** 174 | * Adds the ThrowableInformation object to an existing BSON object. 175 | * 176 | * @param bson 177 | * The BSON object to add the throwable info to (must not be null). 178 | * @param throwableInfo 179 | * The ThrowableInformation object to add to the BSON object (may be null). 180 | */ 181 | @SuppressWarnings(value = "unchecked") 182 | protected void addThrowableInformation(DBObject bson, final ThrowableInformation throwableInfo) { 183 | if (throwableInfo != null) { 184 | Throwable currentThrowable = throwableInfo.getThrowable(); 185 | List throwables = new BasicDBList(); 186 | 187 | while (currentThrowable != null) { 188 | DBObject throwableBson = bsonifyThrowable(currentThrowable); 189 | 190 | if (throwableBson != null) { 191 | throwables.add(throwableBson); 192 | } 193 | 194 | currentThrowable = currentThrowable.getCause(); 195 | } 196 | 197 | if (throwables.size() > 0) { 198 | bson.put(KEY_THROWABLES, throwables); 199 | } 200 | } 201 | } 202 | 203 | /** 204 | * Adds the current process's host name, VM name and IP address 205 | * 206 | * @param bson 207 | * A BSON object containing host name, VM name and IP address 208 | */ 209 | protected void addHostnameInformation(DBObject bson) { 210 | nullSafePut(bson, KEY_HOST, hostInfo); 211 | } 212 | 213 | /** 214 | * BSONifies the given Throwable. 215 | * 216 | * @param throwable 217 | * The throwable object to BSONify (may be null). 218 | * 219 | * @return The BSONified equivalent of the Throwable object (may be null). 220 | */ 221 | protected DBObject bsonifyThrowable(final Throwable throwable) { 222 | DBObject result = null; 223 | 224 | if (throwable != null) { 225 | result = new BasicDBObject(); 226 | 227 | nullSafePut(result, KEY_EXCEPTION_MESSAGE, throwable.getMessage()); 228 | nullSafePut(result, KEY_STACK_TRACE, bsonifyStackTrace(throwable.getStackTrace())); 229 | } 230 | 231 | return (result); 232 | } 233 | 234 | /** 235 | * BSONifies the given stack trace. 236 | * 237 | * @param stackTrace 238 | * The stack trace object to BSONify (may be null). 239 | * 240 | * @return The BSONified equivalent of the stack trace object (may be null). 241 | */ 242 | protected DBObject bsonifyStackTrace(final StackTraceElement[] stackTrace) { 243 | BasicDBList result = null; 244 | 245 | if (stackTrace != null && stackTrace.length > 0) { 246 | result = new BasicDBList(); 247 | 248 | for (StackTraceElement element : stackTrace) { 249 | DBObject bson = bsonifyStackTraceElement(element); 250 | 251 | if (bson != null) { 252 | result.add(bson); 253 | } 254 | } 255 | } 256 | 257 | return (result); 258 | } 259 | 260 | /** 261 | * BSONifies the given stack trace element. 262 | * 263 | * @param element 264 | * The stack trace element object to BSONify (may be null). 265 | * 266 | * @return The BSONified equivalent of the stack trace element object (may be null). 267 | */ 268 | protected DBObject bsonifyStackTraceElement(final StackTraceElement element) { 269 | DBObject result = null; 270 | 271 | if (element != null) { 272 | result = new BasicDBObject(); 273 | 274 | nullSafePut(result, KEY_FILE_NAME, element.getFileName()); 275 | nullSafePut(result, KEY_METHOD, element.getMethodName()); 276 | nullSafePut(result, KEY_LINE_NUMBER, element.getLineNumber()); 277 | nullSafePut(result, KEY_CLASS, bsonifyClassName(element.getClassName())); 278 | } 279 | 280 | return (result); 281 | } 282 | 283 | /** 284 | * BSONifies the given class name. 285 | * 286 | * @param className 287 | * The class name to BSONify (may be null). 288 | * 289 | * @return The BSONified equivalent of the class name (may be null). 290 | */ 291 | @SuppressWarnings(value = "unchecked") 292 | protected DBObject bsonifyClassName(final String className) { 293 | DBObject result = null; 294 | 295 | if (className != null && className.trim().length() > 0) { 296 | result = new BasicDBObject(); 297 | 298 | result.put(KEY_FQCN, className); 299 | 300 | List packageComponents = new BasicDBList(); 301 | String[] packageAndClassName = className.split("\\."); 302 | 303 | packageComponents.addAll(Arrays.asList(packageAndClassName)); 304 | // Requires Java 6 305 | // packageComponents.addAll(Arrays.asList(Arrays.copyOf(packageAndClassName, 306 | // packageAndClassName.length - 1))); 307 | 308 | if (packageComponents.size() > 0) { 309 | result.put(KEY_PACKAGE, packageComponents); 310 | } 311 | 312 | result.put(KEY_CLASS_NAME, packageAndClassName[packageAndClassName.length - 1]); 313 | } 314 | 315 | return (result); 316 | } 317 | 318 | /** 319 | * Adds the given value to the given key, except if it's null (in which case this method does 320 | * nothing). 321 | * 322 | * @param bson 323 | * The BSON object to add the key/value to (must not be null). 324 | * @param key 325 | * The key of the object (must not be null). 326 | * @param value 327 | * The value of the object (may be null). 328 | */ 329 | protected void nullSafePut(DBObject bson, final String key, final Object value) { 330 | if (value != null) { 331 | if (value instanceof String) { 332 | String stringValue = (String) value; 333 | if (stringValue.trim().length() > 0) { 334 | bson.put(key, stringValue); 335 | } 336 | } else if (value instanceof StringBuffer) { 337 | String stringValue = ((StringBuffer) value).toString(); 338 | if (stringValue.trim().length() > 0) { 339 | bson.put(key, stringValue); 340 | } 341 | } else { 342 | bson.put(key, value); 343 | } 344 | } 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/MongoDbAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo; 19 | 20 | import com.mongodb.*; 21 | import com.mongodb.client.MongoCollection; 22 | import com.mongodb.client.MongoDatabase; 23 | import org.apache.log4j.spi.ErrorCode; 24 | import org.bson.BSONObject; 25 | import org.bson.Document; 26 | 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.List; 30 | 31 | /** 32 | * Log4J Appender that writes log events into a MongoDB document oriented database. Log events are 33 | * fully parsed and stored as structured records in MongoDB (this appender does not require, nor use 34 | * a Log4J layout). 35 | *

36 | * The appender does not create any indexes on the data that's stored - it is assumed that if 37 | * query performance is required, those would be created externally (e.g., in the MongoDB shell or 38 | * other external application). 39 | * 40 | * @author Peter Monks (pmonks@gmail.com) 41 | * @see Log4J 42 | * Appender Interface 43 | * @see MongoDB 44 | */ 45 | public class MongoDbAppender extends BsonAppender { 46 | 47 | private final static String DEFAULT_MONGO_DB_HOSTNAME = "localhost"; 48 | 49 | private final static String DEFAULT_MONGO_DB_PORT = "27017"; 50 | 51 | private final static String DEFAULT_MONGO_DB_DATABASE_NAME = "log4mongo"; 52 | 53 | private final static String DEFAULT_MONGO_DB_COLLECTION_NAME = "logevents"; 54 | 55 | private WriteConcern concern; 56 | 57 | private String hostname = DEFAULT_MONGO_DB_HOSTNAME; 58 | 59 | private String port = DEFAULT_MONGO_DB_PORT; 60 | 61 | private String databaseName = DEFAULT_MONGO_DB_DATABASE_NAME; 62 | 63 | private String collectionName = DEFAULT_MONGO_DB_COLLECTION_NAME; 64 | 65 | private String userName = null; 66 | 67 | private String password = null; 68 | 69 | private String writeConcern = null; 70 | 71 | private MongoClient mongo = null; 72 | 73 | private MongoCollection collection = null; 74 | 75 | private boolean initialized = false; 76 | 77 | /** 78 | * @see org.apache.log4j.Appender#requiresLayout() 79 | */ 80 | public boolean requiresLayout() { 81 | return (false); 82 | } 83 | 84 | /** 85 | * @see org.apache.log4j.AppenderSkeleton#activateOptions() 86 | */ 87 | @Override 88 | public void activateOptions() { 89 | try { 90 | // Close previous connections if reactivating 91 | if (mongo != null) { 92 | close(); 93 | } 94 | 95 | MongoCredential credentials = null; 96 | if (userName != null && userName.trim().length() > 0) { 97 | credentials = MongoCredential.createCredential(userName, databaseName, 98 | password.toCharArray()); 99 | password = null; 100 | } 101 | 102 | mongo = getMongo(getServerAddresses(hostname, port), 103 | (credentials != null) ? Arrays.asList(credentials) : null); 104 | 105 | MongoDatabase database = getDatabase(mongo, databaseName); 106 | 107 | setCollection(database.getCollection(collectionName)); 108 | 109 | initialized = true; 110 | } catch (Exception e) { 111 | errorHandler.error("Unexpected exception while initialising MongoDbAppender.", e, 112 | ErrorCode.GENERIC_FAILURE); 113 | } 114 | } 115 | 116 | /* 117 | * This method could be overridden to provide the DB instance from an existing connection. 118 | */ 119 | protected MongoDatabase getDatabase(MongoClient mongo, String databaseName) { 120 | return mongo.getDatabase(databaseName); 121 | } 122 | 123 | /* 124 | * This method could be overridden to provide the Mongo instance from an existing connection. 125 | */ 126 | protected MongoClient getMongo(List addresses) { 127 | if (addresses.size() < 2) { 128 | return new MongoClient(addresses.get(0)); 129 | } else { 130 | // Replica set 131 | return new MongoClient(addresses); 132 | } 133 | } 134 | 135 | private MongoClient getMongo(List addresses, List credentials) { 136 | if (credentials == null) { 137 | return this.getMongo(addresses); 138 | } 139 | 140 | if (addresses.size() < 2) { 141 | return new MongoClient(addresses.get(0), credentials); 142 | } else { 143 | // Replica set 144 | return new MongoClient(addresses, credentials); 145 | } 146 | } 147 | 148 | /** 149 | * Note: this method is primarily intended for use by the unit tests. 150 | * 151 | * @param collection 152 | * The MongoDB collection to use when logging events. 153 | */ 154 | public void setCollection(final MongoCollection collection) { 155 | assert collection != null : "collection must not be null"; 156 | 157 | this.collection = collection; 158 | } 159 | 160 | /** 161 | * @see org.apache.log4j.Appender#close() 162 | */ 163 | public void close() { 164 | if (mongo != null) { 165 | collection = null; 166 | mongo.close(); 167 | } 168 | } 169 | 170 | /** 171 | * @return The hostname of the MongoDB server (will not be null, empty or blank). 172 | */ 173 | public String getHostname() { 174 | return hostname; 175 | } 176 | 177 | /** 178 | * @param hostname 179 | * The MongoDB hostname to set (must not be null, empty or blank). 180 | */ 181 | public void setHostname(final String hostname) { 182 | assert hostname != null : "hostname must not be null"; 183 | assert hostname.trim().length() > 0 : "hostname must not be empty or blank"; 184 | 185 | this.hostname = hostname; 186 | } 187 | 188 | /** 189 | * @return The port of the MongoDB server (will be greater than 0). 190 | */ 191 | public String getPort() { 192 | return port; 193 | } 194 | 195 | /** 196 | * @param port 197 | * The port to set (must not be null, empty or blank). 198 | */ 199 | public void setPort(final String port) { 200 | assert port != null : "port must not be null"; 201 | assert port.trim().length() > 0 : "port must not be empty or blank"; 202 | 203 | this.port = port; 204 | } 205 | 206 | /** 207 | * @return The database used in the MongoDB server (will not be null, empty or blank). 208 | */ 209 | public String getDatabaseName() { 210 | return databaseName; 211 | } 212 | 213 | /** 214 | * @param databaseName 215 | * The database to use in the MongoDB server (must not be null, empty or 216 | * blank). 217 | */ 218 | public void setDatabaseName(final String databaseName) { 219 | assert databaseName != null : "database must not be null"; 220 | assert databaseName.trim().length() > 0 : "database must not be empty or blank"; 221 | 222 | this.databaseName = databaseName; 223 | } 224 | 225 | /** 226 | * @return The collection used within the database in the MongoDB server (will not be null, 227 | * empty or blank). 228 | */ 229 | public String getCollectionName() { 230 | return collectionName; 231 | } 232 | 233 | /** 234 | * @param collectionName 235 | * The collection used within the database in the MongoDB server (must not be 236 | * null, empty or blank). 237 | */ 238 | public void setCollectionName(final String collectionName) { 239 | assert collectionName != null : "collection must not be null"; 240 | assert collectionName.trim().length() > 0 : "collection must not be empty or blank"; 241 | 242 | this.collectionName = collectionName; 243 | } 244 | 245 | /** 246 | * @return The userName used to authenticate with MongoDB (may be null). 247 | */ 248 | public String getUserName() { 249 | return userName; 250 | } 251 | 252 | /** 253 | * @param userName 254 | * The userName to use when authenticating with MongoDB (may be null). 255 | */ 256 | public void setUserName(final String userName) { 257 | this.userName = userName; 258 | } 259 | 260 | /** 261 | * @param password 262 | * The password to use when authenticating with MongoDB (may be null). 263 | */ 264 | public void setPassword(final String password) { 265 | this.password = password; 266 | } 267 | 268 | /** 269 | * @return the writeConcern setting for Mongo. 270 | */ 271 | public String getWriteConcern() { 272 | return writeConcern; 273 | } 274 | 275 | /** 276 | * @param writeConcern 277 | * The WriteConcern setting for Mongo.(may be null). If null, set to default of 278 | * dbCollection's writeConcern. 279 | */ 280 | public void setWriteConcern(final String writeConcern) { 281 | this.writeConcern = writeConcern; 282 | concern = WriteConcern.valueOf(writeConcern); 283 | } 284 | 285 | public WriteConcern getConcern() { 286 | if (concern == null) { 287 | concern = getCollection().getWriteConcern(); 288 | } 289 | return concern; 290 | } 291 | 292 | /** 293 | * @param bson 294 | * The BSON object to insert into a MongoDB database collection. 295 | */ 296 | @Override 297 | public void append(BSONObject bson) { 298 | if (initialized && bson != null) { 299 | try { 300 | getCollection().insertOne(new Document(bson.toMap())); 301 | } catch (MongoException e) { 302 | errorHandler.error("Failed to insert document to MongoDB", e, 303 | ErrorCode.WRITE_FAILURE); 304 | } 305 | } 306 | } 307 | 308 | /** 309 | * Returns true if appender was successfully initialized. If this method returns false, the 310 | * appender should not attempt to log events. 311 | * 312 | * @return true if appender was successfully initialized 313 | */ 314 | public boolean isInitialized() { 315 | return initialized; 316 | } 317 | 318 | /** 319 | * @return The MongoDB collection to which events are logged. 320 | */ 321 | protected MongoCollection getCollection() { 322 | if (concern == null) { 323 | return collection; 324 | } 325 | 326 | return collection.withWriteConcern(concern); 327 | } 328 | 329 | /** 330 | * Returns a List of ServerAddress objects for each host specified in the hostname property. 331 | * Returns an empty list if configuration is detected to be invalid, e.g.: 332 | *

337 | * 338 | * @param hostname 339 | * Blank space delimited hostnames 340 | * @param port 341 | * Blank space delimited ports. Must specify one port for all hosts or a port per 342 | * host. 343 | * 344 | * @return List of ServerAddresses to connect to 345 | */ 346 | private List getServerAddresses(String hostname, String port) { 347 | List addresses = new ArrayList(); 348 | 349 | String[] hosts = hostname.split(" "); 350 | String[] ports = port.split(" "); 351 | 352 | if (ports.length != 1 && ports.length != hosts.length) { 353 | errorHandler.error( 354 | "MongoDB appender port property must contain one port or a port per host", 355 | null, ErrorCode.ADDRESS_PARSE_FAILURE); 356 | } else { 357 | List portNums = getPortNumbers(ports); 358 | // Validate number of ports again after parsing 359 | if (portNums.size() != 1 && portNums.size() != hosts.length) { 360 | errorHandler 361 | .error("MongoDB appender port property must contain one port or a valid port per host", 362 | null, ErrorCode.ADDRESS_PARSE_FAILURE); 363 | } else { 364 | boolean onePort = (portNums.size() == 1); 365 | 366 | int i = 0; 367 | for (String host : hosts) { 368 | int portNum = (onePort) ? portNums.get(0) : portNums.get(i); 369 | addresses.add(new ServerAddress(host.trim(), portNum)); 370 | i++; 371 | } 372 | } 373 | } 374 | return addresses; 375 | } 376 | 377 | private List getPortNumbers(String[] ports) { 378 | List portNumbers = new ArrayList<>(); 379 | 380 | for (String port : ports) { 381 | try { 382 | Integer portNum = Integer.valueOf(port.trim()); 383 | if (portNum < 0) { 384 | errorHandler.error( 385 | "MongoDB appender port property can't contain a negative integer", 386 | null, ErrorCode.ADDRESS_PARSE_FAILURE); 387 | } else { 388 | portNumbers.add(portNum); 389 | } 390 | } catch (NumberFormatException e) { 391 | errorHandler.error( 392 | "MongoDB appender can't parse a port property value into an integer", e, 393 | ErrorCode.ADDRESS_PARSE_FAILURE); 394 | } 395 | 396 | } 397 | 398 | return portNumbers; 399 | } 400 | 401 | } 402 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/MongoDbPatternLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo; 19 | 20 | import org.apache.log4j.PatternLayout; 21 | import org.apache.log4j.helpers.PatternConverter; 22 | import org.apache.log4j.helpers.PatternParser; 23 | import org.apache.log4j.spi.LoggingEvent; 24 | 25 | /** 26 | * PatternLayout that must be used or extended when logging with MongoDbPatternLayoutAppender. 27 | *

28 | * Much of the PatternLayout functionality needed to be re-implemented, because double quotes and \ 29 | * need to be escaped in the formatted String. The formatted String will later be parsed as a JSON 30 | * document, so quotes in the values must be escaped. 31 | * 32 | * @author Robert Stewart (robert@wombatnation.com) 33 | */ 34 | public class MongoDbPatternLayout extends PatternLayout { 35 | 36 | private StringBuffer buf = new StringBuffer(BUF_SIZE); 37 | 38 | private StringBuilder builder = new StringBuilder(BUF_SIZE); 39 | 40 | private String conversionPattern; 41 | 42 | private PatternConverter headConverter; 43 | 44 | public MongoDbPatternLayout() { 45 | this(DEFAULT_CONVERSION_PATTERN); 46 | } 47 | 48 | public MongoDbPatternLayout(String pattern) { 49 | this.conversionPattern = pattern; 50 | headConverter = createPatternParser( 51 | (pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse(); 52 | } 53 | 54 | @Override 55 | public void setConversionPattern(String conversionPattern) { 56 | this.conversionPattern = conversionPattern; 57 | headConverter = createPatternParser(conversionPattern).parse(); 58 | } 59 | 60 | @Override 61 | public String getConversionPattern() { 62 | return conversionPattern; 63 | } 64 | 65 | @Override 66 | public PatternParser createPatternParser(String pattern) { 67 | PatternParser parser; 68 | if (pattern == null) { 69 | parser = new PatternParser(DEFAULT_CONVERSION_PATTERN); 70 | } else { 71 | parser = new PatternParser(pattern); 72 | } 73 | 74 | return parser; 75 | } 76 | 77 | /** 78 | * Produces a formatted string as specified by the conversion pattern. 79 | *

80 | * The PatternConverter expects to append to a StringBuffer. However, for converters other than 81 | * a LiteralPatternConverter, double quotes need to be escaped in the characters appended to the 82 | * StringBuffer. 83 | */ 84 | @Override 85 | public String format(LoggingEvent event) { 86 | // Reset working StringBuilder 87 | if (builder.capacity() > MAX_CAPACITY) { 88 | builder = new StringBuilder(BUF_SIZE); 89 | } else { 90 | builder.setLength(0); 91 | } 92 | 93 | PatternConverter c = headConverter; 94 | 95 | while (c != null) { 96 | if (buf.capacity() > MAX_CAPACITY) { 97 | buf = new StringBuffer(BUF_SIZE); 98 | } else { 99 | buf.setLength(0); 100 | } 101 | c.format(buf, event); 102 | 103 | // Escape double quotes and \ in String generated by converters 104 | // other than a LiteralPatternConverter. Can't use "instance of" 105 | // because class is private. 106 | if (c.getClass().getSimpleName().equals("LiteralPatternConverter")) { 107 | builder.append(buf); 108 | } else { 109 | char[] chars = buf.toString().toCharArray(); 110 | int pos = 0; 111 | for (int i = 0; i < chars.length; i++) { 112 | if (chars[i] == '\"') { 113 | builder.append(chars, pos, i - pos).append("\\\""); 114 | pos = i + 1; 115 | } else if (chars[i] == '\\') { 116 | builder.append(chars, pos, i - pos).append("\\\\"); 117 | pos = i + 1; 118 | } 119 | } 120 | builder.append(chars, pos, chars.length - pos); 121 | } 122 | 123 | c = c.next; 124 | } 125 | return builder.toString(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/MongoDbPatternLayoutAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo; 19 | 20 | import com.mongodb.DBObject; 21 | import com.mongodb.MongoException; 22 | import com.mongodb.util.JSON; 23 | import org.apache.log4j.spi.ErrorCode; 24 | import org.apache.log4j.spi.LoggingEvent; 25 | import org.bson.Document; 26 | 27 | /** 28 | * A Log4J Appender that uses a PatternLayout to write log events into a MongoDB database. 29 | *

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

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

38 | * The appender does not create any indexes on the data that's stored. If query performance 39 | * is required, indexes must be created externally (e.g., in the mongo shell or an external 40 | * reporting application). 41 | * 42 | * @author Robert Stewart (robert@wombatnation.com) 43 | * @see Log4J 44 | * Appender Interface 45 | * @see MongoDB 46 | */ 47 | public class MongoDbPatternLayoutAppender extends MongoDbAppender { 48 | 49 | @Override 50 | public boolean requiresLayout() { 51 | return (true); 52 | } 53 | 54 | /** 55 | * Inserts a BSON representation of a LoggingEvent into a MongoDB collection. A PatternLayout is 56 | * used to format a JSON document containing data available in the LoggingEvent and, optionally, 57 | * additional data returned by custom PatternConverters. 58 | *

59 | * The format of the JSON document is specified in the .layout.ConversionPattern property. 60 | * 61 | * @param loggingEvent 62 | * The LoggingEvent that will be formatted and stored in MongoDB 63 | */ 64 | @Override 65 | protected void append(final LoggingEvent loggingEvent) { 66 | if (isInitialized()) { 67 | DBObject bson = null; 68 | String json = layout.format(loggingEvent); 69 | 70 | if (json.length() > 0) { 71 | Object obj = JSON.parse(json); 72 | if (obj instanceof DBObject) { 73 | bson = (DBObject) obj; 74 | } 75 | } 76 | 77 | if (bson != null) { 78 | try { 79 | getCollection().insertOne(new Document(bson.toMap())); 80 | } catch (MongoException e) { 81 | errorHandler.error("Failed to insert document to MongoDB", e, 82 | ErrorCode.WRITE_FAILURE); 83 | } 84 | } 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/MongoDbPatternLayoutDateAppender.java: -------------------------------------------------------------------------------- 1 | package org.log4mongo; 2 | 3 | import com.mongodb.DBObject; 4 | import com.mongodb.MongoException; 5 | import com.mongodb.util.JSON; 6 | 7 | import java.util.Date; 8 | 9 | import org.apache.log4j.Layout; 10 | import org.apache.log4j.spi.ErrorCode; 11 | import org.apache.log4j.spi.LoggingEvent; 12 | import org.bson.Document; 13 | 14 | /** 15 | * A Log4J Appender that uses a PatternLayout to write log events into a MongoDB database. 16 | * This appender is same as the MongoDbPatternLayoutAppender, only difference is that the 17 | * MongoDbPatternLayoutDateAppender will save pattern layout 'timestamp'(%d) in the mongodb as a 18 | * Date type instead of String. 19 | *

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

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

28 | * The appender does not create any indexes on the data that's stored. If query performance 29 | * is required, indexes must be created externally (e.g., in the mongo shell or an external 30 | * reporting application). 31 | * 32 | */ 33 | public class MongoDbPatternLayoutDateAppender extends MongoDbAppender { 34 | 35 | @Override 36 | public boolean requiresLayout() { 37 | return (true); 38 | } 39 | 40 | /** 41 | * Inserts a BSON representation of a LoggingEvent into a MongoDB collection. A PatternLayout is 42 | * used to format a JSON document containing data available in the LoggingEvent and, optionally, 43 | * additional data returned by custom PatternConverters. Here timestamp is stored as a Date in mongodb. 44 | *

45 | * The format of the JSON document is specified in the .layout.ConversionPattern property. 46 | * 47 | * @param loggingEvent 48 | * The LoggingEvent that will be formatted and stored in MongoDB 49 | */ 50 | @Override 51 | protected void append(final LoggingEvent loggingEvent) { 52 | if (isInitialized()) { 53 | DBObject bson = null; 54 | String json = layout.format(loggingEvent); 55 | 56 | if (json.length() > 0) { 57 | Object obj = JSON.parse(json); 58 | if (obj instanceof DBObject) { 59 | bson = (DBObject) obj; 60 | String dateKey = getDateKeyFromPatternLayout(layout); 61 | if (dateKey != null) { 62 | //saving time stamp as a date instead of string 63 | bson.put(dateKey, new Date(loggingEvent.getTimeStamp())); 64 | } 65 | } 66 | } 67 | 68 | if (bson != null) { 69 | try { 70 | getCollection().insertOne(new Document(bson.toMap())); 71 | } catch (MongoException e) { 72 | errorHandler.error("Failed to insert document to MongoDB", e, 73 | ErrorCode.WRITE_FAILURE); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * this method returns the key of the date which is mentioned in layout pattern 81 | * @param layout 82 | * @return key of Date (%d) 83 | */ 84 | private String getDateKeyFromPatternLayout(Layout layout) { 85 | String dateKey = null; 86 | String conversionPattern = ((MongoDbPatternLayout)layout).getConversionPattern(); 87 | String[] splitPattern = conversionPattern.split(","); 88 | for (String pattern : splitPattern) { 89 | if (pattern.contains("%d")) { 90 | dateKey = pattern.split(":")[0]; 91 | } 92 | } 93 | // here we are removing double quotes '"' and opening curly braces '{' from the Key 94 | if (dateKey != null) { 95 | dateKey = dateKey.replaceAll("\"|\"$", ""); 96 | dateKey = dateKey.replaceAll("\\{", ""); 97 | } 98 | return dateKey; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/contrib/HostInfoPatternLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.log4mongo.contrib; 19 | 20 | import org.apache.log4j.helpers.PatternParser; 21 | import org.log4mongo.MongoDbPatternLayout; 22 | 23 | public class HostInfoPatternLayout extends MongoDbPatternLayout { 24 | 25 | public HostInfoPatternLayout() { 26 | } 27 | 28 | public HostInfoPatternLayout(String pattern) { 29 | super(pattern); 30 | } 31 | 32 | public PatternParser createPatternParser(String pattern) { 33 | PatternParser parser; 34 | if (pattern == null) { 35 | parser = new HostInfoPatternParser(DEFAULT_CONVERSION_PATTERN); 36 | } else { 37 | parser = new HostInfoPatternParser(pattern); 38 | } 39 | 40 | return parser; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/log4mongo/contrib/HostInfoPatternParser.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package org.log4mongo.contrib; 17 | 18 | import org.apache.log4j.helpers.LogLog; 19 | import org.apache.log4j.helpers.PatternConverter; 20 | import org.apache.log4j.helpers.PatternParser; 21 | import org.apache.log4j.spi.LoggingEvent; 22 | 23 | import java.lang.management.ManagementFactory; 24 | import java.net.InetAddress; 25 | import java.net.UnknownHostException; 26 | import java.util.Collections; 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | 30 | /** 31 | * PatternParser that adds pattern converters for logging useful host-related info, specifically: 32 | *

    33 | *
  • hostname
  • 34 | *
  • VM name (which often includes the pid) of the JVM on this host
  • 35 | *
  • IP address
  • 36 | *
37 | */ 38 | public class HostInfoPatternParser extends PatternParser { 39 | 40 | static final char HOST_NAME = 'H'; 41 | 42 | static final char VM_NAME = 'V'; 43 | 44 | static final char IP_ADDRESS = 'I'; 45 | 46 | static final Map converters; 47 | 48 | static { 49 | Map tmp = new HashMap(); 50 | tmp.put(String.valueOf(HOST_NAME), new HostPatternConverter()); 51 | tmp.put(String.valueOf(VM_NAME), new VMNamePatternConverter()); 52 | tmp.put(String.valueOf(IP_ADDRESS), new IPAddressPatternConverter()); 53 | 54 | converters = Collections.unmodifiableMap(tmp); 55 | } 56 | 57 | public HostInfoPatternParser(String pattern) { 58 | super(pattern); 59 | } 60 | 61 | /** 62 | * This method is called on each pattern converter character while the PatternParser superclass 63 | * is parsing the pattern. If the character is for a custom converter handled by this 64 | * PatternParser subclass, this class adds the appropriate converter to a LinkedList of 65 | * converters. If not, it allows the superclass to handle the converter character. 66 | * 67 | * @see org.apache.log4j.helpers.PatternParser#finalizeConverter(char) 68 | */ 69 | public void finalizeConverter(char formatChar) { 70 | PatternConverter pc = null; 71 | switch (formatChar) { 72 | case HOST_NAME: 73 | pc = HostInfoPatternParser.converters.get(String.valueOf(HOST_NAME)); 74 | currentLiteral.setLength(0); 75 | addConverter(pc); 76 | break; 77 | case VM_NAME: 78 | pc = HostInfoPatternParser.converters.get(String.valueOf(VM_NAME)); 79 | currentLiteral.setLength(0); 80 | addConverter(pc); 81 | break; 82 | case IP_ADDRESS: 83 | pc = HostInfoPatternParser.converters.get(String.valueOf(IP_ADDRESS)); 84 | currentLiteral.setLength(0); 85 | addConverter(pc); 86 | break; 87 | 88 | default: 89 | super.finalizeConverter(formatChar); 90 | } 91 | } 92 | 93 | /** 94 | * Custom PatternConverter for replacing converter character 'H' with the host name. 95 | */ 96 | private static class HostPatternConverter extends PatternConverter { 97 | 98 | private String hostname = ""; 99 | 100 | HostPatternConverter() { 101 | super(); 102 | 103 | try { 104 | hostname = InetAddress.getLocalHost().getHostName(); 105 | } catch (UnknownHostException e) { 106 | LogLog.warn(e.getMessage()); 107 | } 108 | } 109 | 110 | public String convert(LoggingEvent event) { 111 | return hostname; 112 | } 113 | } 114 | 115 | /** 116 | * Custom PatternConverter for replacing converter character 'V' with the VM name of the JVM, 117 | * usually formatted as pid@host. 118 | */ 119 | private static class VMNamePatternConverter extends PatternConverter { 120 | 121 | private String process = ""; 122 | 123 | VMNamePatternConverter() { 124 | super(); 125 | 126 | process = ManagementFactory.getRuntimeMXBean().getName(); 127 | } 128 | 129 | public String convert(LoggingEvent event) { 130 | return process; 131 | } 132 | } 133 | 134 | /** 135 | * Custom PatternConverter for replacing converter character 'I' with the IP Address. 136 | */ 137 | private static class IPAddressPatternConverter extends PatternConverter { 138 | 139 | private String ipaddress = ""; 140 | 141 | IPAddressPatternConverter() { 142 | super(); 143 | 144 | try { 145 | ipaddress = InetAddress.getLocalHost().getHostAddress(); 146 | } catch (UnknownHostException e) { 147 | LogLog.warn(e.getMessage()); 148 | } 149 | } 150 | 151 | public String convert(LoggingEvent event) { 152 | return ipaddress; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/CustomPatternLayout.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package org.log4mongo; 17 | 18 | import org.apache.log4j.helpers.PatternParser; 19 | 20 | /** 21 | * Example PatternLayout that specifies a custom PatternParser. 22 | */ 23 | public class CustomPatternLayout extends MongoDbPatternLayout { 24 | 25 | public CustomPatternLayout() { 26 | } 27 | 28 | public CustomPatternLayout(String pattern) { 29 | super(pattern); 30 | } 31 | 32 | public PatternParser createPatternParser(String pattern) { 33 | PatternParser parser; 34 | if (pattern == null) { 35 | parser = new CustomPatternParser(DEFAULT_CONVERSION_PATTERN); 36 | } else { 37 | parser = new CustomPatternParser(pattern); 38 | } 39 | 40 | return parser; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/CustomPatternParser.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package org.log4mongo; 17 | 18 | import org.apache.log4j.helpers.FormattingInfo; 19 | import org.apache.log4j.helpers.PatternConverter; 20 | import org.apache.log4j.helpers.PatternParser; 21 | import org.apache.log4j.spi.LoggingEvent; 22 | 23 | /** 24 | * Simple PatternParser that adds a single PatternConverter for logging some extra info. The extra 25 | * info returned from convert(LoggingEvent) will replace %e in the pattern when an event is logged 26 | * if the logging style is set to PatternLayout. 27 | */ 28 | public class CustomPatternParser extends PatternParser { 29 | 30 | static final char EXTRA_CHAR = 'e'; 31 | 32 | public CustomPatternParser(String pattern) { 33 | super(pattern); 34 | } 35 | 36 | /** 37 | * This method is called on each pattern converter character while the PatternParser superclass 38 | * is parsing the pattern. If the character is for a custom converter handled by this 39 | * PatternParser subclass, this class adds the appropriate converter to a LinkedList of 40 | * converters. If not, it allows the superclass to handle the converter character. 41 | * 42 | * @see org.apache.log4j.helpers.PatternParser#finalizeConverter(char) 43 | */ 44 | public void finalizeConverter(char formatChar) { 45 | PatternConverter pc = null; 46 | switch (formatChar) { 47 | case EXTRA_CHAR: 48 | pc = new ExtraInfoPatternConverter(formattingInfo); 49 | currentLiteral.setLength(0); 50 | addConverter(pc); 51 | break; 52 | 53 | default: 54 | super.finalizeConverter(formatChar); 55 | } 56 | } 57 | 58 | /** 59 | * Custom PatternConverter for replacing a converter character (in this case, the character 60 | * happens to be 'e') with some additional info. For a real application, this might be a session 61 | * ID or some other piece of meaningful info. 62 | */ 63 | private class ExtraInfoPatternConverter extends PatternConverter { 64 | 65 | ExtraInfoPatternConverter(FormattingInfo formatInfo) { 66 | super(formatInfo); 67 | } 68 | 69 | /** 70 | * Returns the string that will replace %e in the pattern string. 71 | */ 72 | public String convert(LoggingEvent event) { 73 | return "useful info"; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestExtendedMongoDbAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.log4mongo; 18 | 19 | import com.mongodb.DBObject; 20 | import com.mongodb.Mongo; 21 | import com.mongodb.MongoClient; 22 | import com.mongodb.client.FindIterable; 23 | import com.mongodb.client.MongoCollection; 24 | import org.apache.log4j.Logger; 25 | import org.apache.log4j.MDC; 26 | import org.apache.log4j.PropertyConfigurator; 27 | import org.bson.Document; 28 | import org.junit.AfterClass; 29 | import org.junit.Before; 30 | import org.junit.BeforeClass; 31 | import org.junit.Test; 32 | 33 | import java.util.HashMap; 34 | import java.util.Map; 35 | 36 | import static org.junit.Assert.*; 37 | 38 | /** 39 | * JUnit unit tests for ExtendedMongoDbAppender. 40 | *

41 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that server 42 | * is listening on the default port (27017) on localhost. 43 | * 44 | * @author Mick Knutson (http://www.baselogic.com) 45 | */ 46 | public class TestExtendedMongoDbAppender { 47 | 48 | private final static Logger log = Logger.getLogger(TestExtendedMongoDbAppender.class); 49 | 50 | private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 51 | 52 | private final static int TEST_MONGO_SERVER_PORT = 27017; 53 | 54 | private final static String TEST_DATABASE_NAME = "log4mongotest"; 55 | 56 | private final static String TEST_COLLECTION_NAME = "logevents"; 57 | 58 | private final static String MONGODB_APPENDER_NAME = "MongoDB"; 59 | 60 | private final static String LOG4J_PROPS = "src/test/resources/log4j_extended.properties"; 61 | // private final static String LOG4J_PROPS = "src/test/resources/log4j_extended.xml"; 62 | 63 | private final MongoClient mongo; 64 | 65 | private final ExtendedMongoDbAppender appender; 66 | 67 | private MongoCollection collection; 68 | 69 | public TestExtendedMongoDbAppender() throws Exception { 70 | PropertyConfigurator.configure(LOG4J_PROPS); 71 | mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 72 | appender = (ExtendedMongoDbAppender) Logger.getRootLogger().getAppender( 73 | MONGODB_APPENDER_NAME); 74 | } 75 | 76 | @BeforeClass 77 | public static void setUpBeforeClass() throws Exception { 78 | Mongo mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 79 | mongo.dropDatabase(TEST_DATABASE_NAME); 80 | } 81 | 82 | @AfterClass 83 | public static void tearDownAfterClass() throws Exception { 84 | Mongo mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 85 | mongo.dropDatabase(TEST_DATABASE_NAME); 86 | } 87 | 88 | @Before 89 | public void setUp() throws Exception { 90 | // Ensure both the appender and the JUnit test use the same collection 91 | // object - provides consistency across reads (JUnit) & writes (Log4J) 92 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 93 | collection.drop(); 94 | appender.setCollection(collection); 95 | } 96 | 97 | @Test 98 | public void testInitialized() throws Exception { 99 | if (!appender.isInitialized()) { 100 | fail(); 101 | } 102 | } 103 | 104 | @Test 105 | public void testRootLevelProperties() throws Exception { 106 | assertEquals(0L, countLogEntries()); 107 | 108 | Map obj = new HashMap() { 109 | 110 | { 111 | put("key1", "value1"); 112 | put("key2", "value2"); 113 | } 114 | }; 115 | 116 | // slf4j style: log.warn("Testing Object in Message: {}", obj); 117 | log.warn("Testing Object in Message: " + obj); 118 | assertEquals(1L, countLogEntries()); 119 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 120 | 121 | // verify log entry content 122 | FindIterable entries = collection.find(DBObject.class); 123 | for (DBObject entry : entries) { 124 | assertNotNull(entry); 125 | assertEquals("WARN", entry.get("level")); 126 | assertEquals("Testing Object in Message: {key1=value1, key2=value2}", 127 | entry.get("message")); 128 | } 129 | } 130 | 131 | @Test 132 | public void testObjectAsMessage() throws Exception { 133 | assertEquals(0L, countLogEntries()); 134 | 135 | log.warn("Testing Object in Message"); 136 | 137 | long appNameCount = countLogEntriesWhere(Document 138 | .parse("{ 'applicationName' : 'MyProject' }")); 139 | 140 | assertEquals(1L, appNameCount); 141 | 142 | // verify log entry content 143 | FindIterable entries = collection.find(DBObject.class); 144 | for (DBObject entry : entries) { 145 | assertNotNull(entry); 146 | assertEquals("Development", entry.get("eventType")); 147 | } 148 | } 149 | 150 | @Test 151 | public void testMdcProperties() throws Exception { 152 | assertEquals(0L, countLogEntries()); 153 | 154 | MDC.put("uuid", "1000"); 155 | MDC.put("recordAssociation", "xyz"); 156 | 157 | log.warn("Testing MDC Properties"); 158 | assertEquals(1L, countLogEntries()); 159 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 160 | 161 | // verify log entry content 162 | FindIterable entries = collection.find(DBObject.class); 163 | for (DBObject entry : entries) { 164 | assertNotNull(entry); 165 | assertEquals("WARN", entry.get("level")); 166 | assertEquals("Testing MDC Properties", entry.get("message")); 167 | assertNotNull(entry.get("properties")); 168 | DBObject mdcProperties = (DBObject) entry.get("properties"); 169 | 170 | assertNotNull(mdcProperties.get("uuid")); 171 | assertNotNull(mdcProperties.get("recordAssociation")); 172 | } 173 | } 174 | 175 | // -----------------------------------------------------------------------// 176 | // Private methods 177 | // -----------------------------------------------------------------------// 178 | private long countLogEntries() { 179 | return (collection.count()); 180 | } 181 | 182 | private long countLogEntriesAtLevel(final String level) { 183 | return (countLogEntriesWhere(Document.parse("{ 'level' : '" + level.toUpperCase() + "' }"))); 184 | } 185 | 186 | private long countLogEntriesWhere(final Document whereClause) { 187 | return collection.count(whereClause); 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestLoggingEventBsonifierImpl.java: -------------------------------------------------------------------------------- 1 | package org.log4mongo; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.DBObject; 5 | import org.junit.Test; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class TestLoggingEventBsonifierImpl { 12 | 13 | @Test 14 | public void testStringBuffer() throws NoSuchMethodException, IllegalAccessException, 15 | InvocationTargetException { 16 | LoggingEventBsonifierImplSubclass bsonifier = new LoggingEventBsonifierImplSubclass(); 17 | DBObject bson = new BasicDBObject(); 18 | String key = "thekey"; 19 | StringBuffer sb = new StringBuffer("thevalue"); 20 | bsonifier.publicNullSafePut(bson, key, sb); 21 | String retrievedValue = (String) bson.get(key); 22 | assertEquals(sb.toString(), retrievedValue); 23 | } 24 | 25 | // Create a subclass so I can test a protected method 26 | // Replace this after extending Privateer to support superclasses in method signature 27 | public class LoggingEventBsonifierImplSubclass extends LoggingEventBsonifierImpl { 28 | 29 | public void publicNullSafePut(DBObject bson, final String key, final Object value) { 30 | nullSafePut(bson, key, value); 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestMongoDbAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.log4mongo; 18 | 19 | import com.mongodb.*; 20 | import com.mongodb.client.FindIterable; 21 | import com.mongodb.client.MongoCollection; 22 | import org.apache.log4j.Logger; 23 | import org.apache.log4j.PropertyConfigurator; 24 | import org.bson.Document; 25 | import org.junit.AfterClass; 26 | import org.junit.Before; 27 | import org.junit.BeforeClass; 28 | import org.junit.Test; 29 | 30 | import java.lang.management.ManagementFactory; 31 | import java.net.InetAddress; 32 | 33 | import static org.junit.Assert.*; 34 | 35 | /** 36 | * JUnit unit tests for MongoDbAppender. 37 | *

38 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that server 39 | * is listening on the default port (27017) on localhost. 40 | * 41 | * @author Peter Monks (pmonks@gmail.com) 42 | */ 43 | public class TestMongoDbAppender { 44 | 45 | private final static Logger log = Logger.getLogger(TestMongoDbAppender.class); 46 | 47 | private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 48 | 49 | private final static int TEST_MONGO_SERVER_PORT = 27017; 50 | 51 | private final static String TEST_DATABASE_NAME = "log4mongotest"; 52 | 53 | private final static String TEST_COLLECTION_NAME = "logevents"; 54 | 55 | private final static String MONGODB_APPENDER_NAME = "MongoDB"; 56 | 57 | private final static String LOG4J_PROPS = "src/test/resources/log4j.properties"; 58 | 59 | private final MongoClient mongo; 60 | 61 | private final MongoDbAppender appender; 62 | 63 | private MongoCollection collection; 64 | 65 | public TestMongoDbAppender() throws Exception { 66 | PropertyConfigurator.configure(LOG4J_PROPS); 67 | mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 68 | appender = (MongoDbAppender) Logger.getRootLogger().getAppender(MONGODB_APPENDER_NAME); 69 | } 70 | 71 | @BeforeClass 72 | public static void setUpBeforeClass() throws Exception { 73 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 74 | mongo.dropDatabase(TEST_DATABASE_NAME); 75 | } 76 | 77 | @AfterClass 78 | public static void tearDownAfterClass() throws Exception { 79 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 80 | mongo.dropDatabase(TEST_DATABASE_NAME); 81 | } 82 | 83 | @Before 84 | public void setUp() throws Exception { 85 | // Ensure both the appender and the JUnit test use the same collection 86 | // object - provides consistency across reads (JUnit) & writes (Log4J) 87 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 88 | collection.drop(); 89 | appender.setCollection(collection); 90 | 91 | } 92 | 93 | @Test 94 | public void testInitialized() throws Exception { 95 | if (!appender.isInitialized()) { 96 | fail(); 97 | } 98 | } 99 | 100 | @Test 101 | public void testSingleLogEntry() throws Exception { 102 | log.trace("Trace entry"); 103 | 104 | assertEquals(1L, countLogEntries()); 105 | assertEquals(1L, countLogEntriesAtLevel("trace")); 106 | assertEquals(0L, countLogEntriesAtLevel("debug")); 107 | assertEquals(0L, countLogEntriesAtLevel("info")); 108 | assertEquals(0L, countLogEntriesAtLevel("warn")); 109 | assertEquals(0L, countLogEntriesAtLevel("error")); 110 | assertEquals(0L, countLogEntriesAtLevel("fatal")); 111 | 112 | // verify log entry content 113 | FindIterable entries = collection.find(DBObject.class); 114 | for (DBObject entry : entries) { 115 | assertNotNull(entry); 116 | assertEquals("TRACE", entry.get("level")); 117 | assertEquals("Trace entry", entry.get("message")); 118 | } 119 | } 120 | 121 | @Test 122 | public void testTimestampStoredNatively() throws Exception { 123 | log.debug("Debug entry"); 124 | 125 | assertEquals(1L, countLogEntries()); 126 | 127 | // verify timestamp - presence and data type 128 | FindIterable entries = collection.find(DBObject.class); 129 | for (DBObject entry : entries) { 130 | assertNotNull(entry); 131 | assertTrue("Timestamp is not present in logged entry", entry.containsField("timestamp")); 132 | assertTrue("Timestamp of logged entry is not stored as native date", 133 | (entry.get("timestamp") instanceof java.util.Date)); 134 | } 135 | } 136 | 137 | @Test 138 | public void testAllLevels() throws Exception { 139 | log.trace("Trace entry"); 140 | log.debug("Debug entry"); 141 | log.info("Info entry"); 142 | log.warn("Warn entry"); 143 | log.error("Error entry"); 144 | log.fatal("Fatal entry"); 145 | 146 | assertEquals(6L, countLogEntries()); 147 | assertEquals(1L, countLogEntriesAtLevel("trace")); 148 | assertEquals(1L, countLogEntriesAtLevel("debug")); 149 | assertEquals(1L, countLogEntriesAtLevel("info")); 150 | assertEquals(1L, countLogEntriesAtLevel("warn")); 151 | assertEquals(1L, countLogEntriesAtLevel("error")); 152 | assertEquals(1L, countLogEntriesAtLevel("fatal")); 153 | } 154 | 155 | @Test 156 | public void testLogWithException() throws Exception { 157 | log.error("Error entry", new RuntimeException("Here is an exception!")); 158 | 159 | assertEquals(1L, countLogEntries()); 160 | assertEquals(1L, countLogEntriesAtLevel("error")); 161 | 162 | // verify log entry content 163 | FindIterable entries = collection.find(DBObject.class); 164 | for (DBObject entry : entries) { 165 | assertNotNull(entry); 166 | assertEquals("ERROR", entry.get("level")); 167 | assertEquals("Error entry", entry.get("message")); 168 | 169 | // verify throwable presence and content 170 | assertTrue("Throwable is not present in logged entry", 171 | entry.containsField("throwables")); 172 | BasicDBList throwables = (BasicDBList) entry.get("throwables"); 173 | assertEquals(1, throwables.size()); 174 | 175 | DBObject throwableEntry = (DBObject) throwables.get("0"); 176 | assertTrue("Throwable message is not present in logged entry", 177 | throwableEntry.containsField("message")); 178 | assertEquals("Here is an exception!", throwableEntry.get("message")); 179 | } 180 | } 181 | 182 | @Test 183 | public void testLogWithChainedExceptions() throws Exception { 184 | Exception rootCause = new RuntimeException("I'm the real culprit!"); 185 | 186 | log.error("Error entry", new RuntimeException("I'm an innocent bystander.", rootCause)); 187 | 188 | assertEquals(1L, countLogEntries()); 189 | assertEquals(1L, countLogEntriesAtLevel("error")); 190 | 191 | // verify log entry content 192 | FindIterable entries = collection.find(DBObject.class); 193 | for (DBObject entry : entries) { 194 | assertNotNull(entry); 195 | assertEquals("ERROR", entry.get("level")); 196 | assertEquals("Error entry", entry.get("message")); 197 | 198 | // verify throwable presence and content 199 | assertTrue("Throwable is not present in logged entry", 200 | entry.containsField("throwables")); 201 | BasicDBList throwables = (BasicDBList) entry.get("throwables"); 202 | assertEquals(2, throwables.size()); 203 | 204 | DBObject rootEntry = (DBObject) throwables.get("0"); 205 | assertTrue("Throwable message is not present in logged entry", 206 | rootEntry.containsField("message")); 207 | assertEquals("I'm an innocent bystander.", rootEntry.get("message")); 208 | 209 | DBObject chainedEntry = (DBObject) throwables.get("1"); 210 | assertTrue("Throwable message is not present in logged entry", 211 | chainedEntry.containsField("message")); 212 | assertEquals("I'm the real culprit!", chainedEntry.get("message")); 213 | } 214 | } 215 | 216 | @Test 217 | public void testQuotesInMessage() { 218 | assertEquals(0L, countLogEntries()); 219 | log.warn("Quotes\" \"embedded"); 220 | assertEquals(1L, countLogEntries()); 221 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 222 | 223 | // verify log entry content 224 | FindIterable entries = collection.find(DBObject.class); 225 | for (DBObject entry : entries) { 226 | assertNotNull(entry); 227 | assertEquals("WARN", entry.get("level")); 228 | assertEquals("Quotes\" \"embedded", entry.get("message")); 229 | } 230 | } 231 | 232 | @Test 233 | public void testPerformance() throws Exception { 234 | // Log one event to minimize start up effects on performance 235 | log.warn("Warn entry"); 236 | 237 | long NUM_MESSAGES = 1000; 238 | long now = System.currentTimeMillis(); 239 | for (long i = 0; i < NUM_MESSAGES; i++) { 240 | log.warn("Warn entry"); 241 | } 242 | long dur = System.currentTimeMillis() - now; 243 | System.out.println("Milliseconds for MongoDbAppender to log " + NUM_MESSAGES + " messages:" 244 | + dur); 245 | assertEquals(NUM_MESSAGES + 1, countLogEntries()); 246 | } 247 | 248 | @Test 249 | public void testRegularLoggerRecordsLoggerNameCorrectly() { 250 | log.info("From an unwrapped logger"); 251 | 252 | assertEquals(1, countLogEntries()); 253 | assertEquals(1, countLogEntriesAtLevel("info")); 254 | assertEquals(1, 255 | countLogEntriesWhere(Document 256 | .parse("{ 'loggerName.className' : 'TestMongoDbAppender' }"))); 257 | assertEquals(1, 258 | countLogEntriesWhere(Document 259 | .parse("{ 'class.className' : 'TestMongoDbAppender' }"))); 260 | } 261 | 262 | @Test 263 | public void testWrappedLoggerRecordsLoggerNameCorrectly() { 264 | WrappedLogger wrapped = new WrappedLogger(log); 265 | wrapped.info("From a wrapped logger"); 266 | 267 | assertEquals(1, countLogEntries()); 268 | assertEquals(1, countLogEntriesAtLevel("info")); 269 | assertEquals(1, 270 | countLogEntriesWhere(Document 271 | .parse("{ 'loggerName.className' : 'TestMongoDbAppender' }"))); 272 | assertEquals(1, 273 | countLogEntriesWhere(Document.parse("{ 'class.className' : 'WrappedLogger' }"))); 274 | } 275 | 276 | @Test 277 | public void testHostInfoRecords() throws Exception { 278 | assertEquals(0L, countLogEntries()); 279 | log.warn("Testing hostinfo"); 280 | assertEquals(1L, countLogEntries()); 281 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 282 | 283 | // verify log entry content 284 | FindIterable entries = collection.find(DBObject.class); 285 | for (DBObject entry : entries) { 286 | assertNotNull(entry); 287 | assertEquals("WARN", entry.get("level")); 288 | assertEquals("Testing hostinfo", entry.get("message")); 289 | assertNotNull(entry.get("host")); 290 | DBObject hostinfo = (DBObject) entry.get("host"); 291 | assertNotNull(hostinfo.get("process")); 292 | assertEquals(InetAddress.getLocalHost().getHostName(), hostinfo.get("name")); 293 | assertEquals(ManagementFactory.getRuntimeMXBean().getName(), hostinfo.get("process")); 294 | } 295 | } 296 | 297 | @Test 298 | /** 299 | * Added to verify fix to GitHub issue #24. 300 | */ 301 | public void testLogObject() throws Exception { 302 | Object object = new Object(); 303 | 304 | assertEquals(0L, countLogEntries()); 305 | log.error(object); 306 | assertEquals(1L, countLogEntries()); 307 | } 308 | 309 | private long countLogEntries() { 310 | return (collection.count()); 311 | } 312 | 313 | private long countLogEntriesAtLevel(final String level) { 314 | return (countLogEntriesWhere(Document.parse("{ 'level' : '" + level.toUpperCase() + "' }"))); 315 | } 316 | 317 | private long countLogEntriesWhere(final Document whereClause) { 318 | return collection.count(whereClause); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestMongoDbAppenderAuth.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package org.log4mongo; 17 | 18 | import com.mongodb.BasicDBObject; 19 | import com.mongodb.MongoClient; 20 | import com.mongodb.client.MongoCollection; 21 | import com.mongodb.client.MongoDatabase; 22 | import org.apache.log4j.PropertyConfigurator; 23 | import org.bson.Document; 24 | import org.junit.*; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | 29 | /** 30 | * Authentication-related JUnit unit tests for MongoDbAppender. 31 | *

32 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that server 33 | * is listening on the default port (27017) on localhost. 34 | * 35 | * @author Robert Stewart (robert@wombatnation.com) 36 | */ 37 | public class TestMongoDbAppenderAuth { 38 | 39 | private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 40 | 41 | private final static int TEST_MONGO_SERVER_PORT = 27017; 42 | 43 | private final static String TEST_DATABASE_NAME = "log4mongotestauth"; 44 | 45 | private final static String TEST_COLLECTION_NAME = "logevents"; 46 | 47 | private final static String LOG4J_AUTH_PROPS = "src/test/resources/log4j_auth.properties"; 48 | 49 | private final static String username = "open"; 50 | 51 | private final static String password = "sesame"; 52 | 53 | private final MongoClient mongo; 54 | 55 | private MongoCollection collection; 56 | 57 | public TestMongoDbAppenderAuth() throws Exception { 58 | mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 59 | } 60 | 61 | @BeforeClass 62 | public static void setUpBeforeClass() throws Exception { 63 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 64 | mongo.dropDatabase(TEST_DATABASE_NAME); 65 | } 66 | 67 | @AfterClass 68 | public static void tearDownAfterClass() throws Exception { 69 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 70 | mongo.dropDatabase(TEST_DATABASE_NAME); 71 | } 72 | 73 | @Before 74 | public void setUp() throws Exception { 75 | // Ensure both the appender and the JUnit test use the same 76 | // collection object - provides consistency across reads (JUnit) & 77 | // writes (Log4J) 78 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 79 | collection.drop(); 80 | 81 | } 82 | 83 | /** 84 | * Catching the RuntimeException thrown when Log4J calls the MongoDbAppender activeOptions() 85 | * method isn't easy, since it is thrown in another thread. 86 | */ 87 | @Test(expected = RuntimeException.class) 88 | @Ignore 89 | public void testAppenderActivateNoAuth() { 90 | PropertyConfigurator.configure(LOG4J_AUTH_PROPS); 91 | } 92 | 93 | /** 94 | * Adds the user to the test database before activating the appender. 95 | */ 96 | @Test 97 | public void testAppenderActivateWithAuth() { 98 | Document result = null; 99 | MongoDatabase db = mongo.getDatabase(TEST_DATABASE_NAME); 100 | BasicDBObject getUsersInfoCommand = new BasicDBObject("usersInfo", 101 | new BasicDBObject("user", username).append("db", TEST_DATABASE_NAME)); 102 | BasicDBObject dropUserCommand = new BasicDBObject("dropUser", username); 103 | BasicDBObject createUserCommand = new BasicDBObject("createUser", username).append("pwd", password).append("roles", 104 | Collections.singletonList(new BasicDBObject("role", "dbOwner").append("db", TEST_DATABASE_NAME))); 105 | 106 | // If test user exists from a previous run, drop it 107 | result = db.runCommand(getUsersInfoCommand); 108 | ArrayList users = (ArrayList) result.get("users"); 109 | if (!users.isEmpty()) { 110 | db.runCommand(dropUserCommand); 111 | } 112 | 113 | db.runCommand(createUserCommand); 114 | PropertyConfigurator.configure(LOG4J_AUTH_PROPS); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestMongoDbAppenderHosts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Robert Stewart (robert@wombatnation.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.log4mongo; 18 | 19 | import com.mongodb.Mongo; 20 | import com.mongodb.ServerAddress; 21 | import com.wombatnation.privateer.Privateer; 22 | import org.apache.log4j.Logger; 23 | import org.apache.log4j.PropertyConfigurator; 24 | import org.junit.Test; 25 | 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import java.util.Properties; 29 | 30 | import static org.junit.Assert.assertTrue; 31 | 32 | /** 33 | * JUnit unit tests for MongoDbAppender to verify proper behavior when setting the hostname and 34 | * property properties. 35 | *

36 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that 37 | * server is listening on the default port (27017) on localhost. 38 | *

39 | * Unless a replica set is configured with mongod instances listening on ports 27017 and 27018, 40 | * errors will be logged. If a host is not listening on port 27018, testOneHostNonDefaultPort() will 41 | * fail with an error. 42 | *

43 | * To test on localhost with a replica set, do the following (either starting mongod instances in 44 | * separate terminals or start them with --fork): 45 | *

    46 | *
  • $ mkdir -p /data/r0 47 | *
  • $ mkdir -p /data/r1 48 | *
  • $ mkdir -p /data/r2 49 | *
  • $ mongod --replSet foo --smallfiles --port 27017 --dbpath /data/r0 50 | *
  • $ mongod --replSet foo --smallfiles --port 27018 --dbpath /data/r1 51 | *
  • $ mongod --replSet foo --smallfiles --port 27019 --dbpath /data/r2 52 | *
  • $ mongo 53 | *
  • >config = {"_id": "foo", members:[{_id: 0, host: 'localhost:27017'},{_id: 1, host: 54 | * 'localhost:27018'},{_id: 2, host: 'localhost:27019', arbiterOnly: true}]} 55 | *
  • >rs.initiate(config) 56 | *
  • Then wait about a minute until replica set is established. You can run rs.status() and look 57 | * for direct confirmation. 58 | *
59 | * Since the unit tests create and drop databases several times, they run about twice as fast if 60 | * mongod is started with the --smallfiles argument. 61 | * 62 | * @author Robert Stewart (robert@wombatnation.com) 63 | */ 64 | public class TestMongoDbAppenderHosts { 65 | 66 | private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 67 | 68 | private final static int TEST_MONGO_SERVER_PORT = 27017; 69 | 70 | private final static String TEST_DATABASE_NAME = "log4mongotest"; 71 | 72 | private final static String MONGODB_APPENDER_NAME = "MongoDB"; 73 | 74 | private final Privateer p = new Privateer(); 75 | 76 | @Test 77 | public void testOneHost() throws Exception { 78 | String hostname = TEST_MONGO_SERVER_HOSTNAME; 79 | PropertyConfigurator.configure(getDefaultPortProperties(hostname)); 80 | 81 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 82 | MONGODB_APPENDER_NAME); 83 | Mongo mongo = (Mongo) p.getField(appender, "mongo"); 84 | assertTrue(mongo.getAddress() != null); 85 | assertTrue(TEST_MONGO_SERVER_HOSTNAME.equals(mongo.getAddress().getHost())); 86 | assertTrue(TEST_MONGO_SERVER_PORT == mongo.getAddress().getPort()); 87 | 88 | appender.close(); 89 | } 90 | 91 | @Test 92 | public void testOneHostNonDefaultPort() throws Exception { 93 | String hostname = TEST_MONGO_SERVER_HOSTNAME; 94 | String port = "27017"; 95 | int portNum = Integer.parseInt(port); 96 | PropertyConfigurator.configure(getNonDefaultPortProperties(hostname, port)); 97 | 98 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 99 | MONGODB_APPENDER_NAME); 100 | 101 | Mongo mongo = (Mongo) p.getField(appender, "mongo"); 102 | assertTrue(mongo.getAddress() != null); 103 | assertTrue(TEST_MONGO_SERVER_HOSTNAME.equals(mongo.getAddress().getHost())); 104 | assertTrue(portNum == mongo.getAddress().getPort()); 105 | 106 | appender.close(); 107 | } 108 | 109 | /** 110 | * If this test is run without a mongod running on localhost port 27018, an error will be logged 111 | * to the console by the appender. 112 | * 113 | * @throws Exception 114 | */ 115 | @Test 116 | public void testTwoHostsTwoPorts() throws Exception { 117 | String hostname = "localhost localhost"; 118 | List hosts = Arrays.asList("localhost", "localhost"); 119 | String port = "27017 27018"; 120 | List ports = Arrays.asList("27017", "27018"); 121 | PropertyConfigurator.configure(getNonDefaultPortProperties(hostname, port)); 122 | 123 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 124 | MONGODB_APPENDER_NAME); 125 | 126 | Mongo mongo = (Mongo) p.getField(appender, "mongo"); 127 | List addresses = mongo.getAllAddress(); 128 | assertTrue(addresses != null); 129 | for (ServerAddress address : addresses) { 130 | boolean found = false; 131 | int i = 0; 132 | for (String host : hosts) { 133 | if (host.equals(address.getHost())) { 134 | String p = String.valueOf(address.getPort()); 135 | if (ports.get(i).equals(p)) { 136 | found = true; 137 | break; 138 | } 139 | } 140 | i++; 141 | } 142 | assertTrue(found); 143 | } 144 | 145 | appender.close(); 146 | } 147 | 148 | private Properties getDefaultPortProperties(String hostname) { 149 | Properties props = new Properties(); 150 | props.put("log4j.rootLogger", "DEBUG, MongoDB"); 151 | props.put("log4j.appender.MongoDB", "org.log4mongo.MongoDbAppender"); 152 | props.put("log4j.appender.MongoDB.databaseName", TEST_DATABASE_NAME); 153 | props.put("log4j.appender.MongoDB.hostname", hostname); 154 | return props; 155 | } 156 | 157 | private Properties getNonDefaultPortProperties(String hostname, String port) { 158 | Properties props = new Properties(); 159 | props.put("log4j.rootLogger", "DEBUG, MongoDB"); 160 | props.put("log4j.appender.MongoDB", "org.log4mongo.MongoDbAppender"); 161 | props.put("log4j.appender.MongoDB.databaseName", TEST_DATABASE_NAME); 162 | props.put("log4j.appender.MongoDB.hostname", hostname); 163 | props.put("log4j.appender.MongoDB.port", port); 164 | return props; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestMongoDbPatternLayout.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package org.log4mongo; 17 | 18 | import com.mongodb.*; 19 | import com.mongodb.client.FindIterable; 20 | import com.mongodb.client.MongoCollection; 21 | import org.apache.log4j.Logger; 22 | import org.apache.log4j.PropertyConfigurator; 23 | import org.bson.Document; 24 | import org.junit.AfterClass; 25 | import org.junit.Before; 26 | import org.junit.BeforeClass; 27 | import org.junit.Test; 28 | 29 | import java.net.InetAddress; 30 | import java.util.Properties; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertNotNull; 34 | 35 | /** 36 | * JUnit unit tests for PatternLayout style logging. 37 | *

38 | * Since tests may depend on different Log4J property settings, each test reconfigures an appender 39 | * using a Properties object. 40 | *

41 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that server 42 | * is listening on the default port (27017) on localhost. 43 | * 44 | * @author Robert Stewart (robert@wombatnation.com) 45 | */ 46 | public class TestMongoDbPatternLayout { 47 | 48 | private static final Logger log = Logger.getLogger(TestMongoDbPatternLayout.class); 49 | 50 | public static final String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 51 | 52 | public static final int TEST_MONGO_SERVER_PORT = 27017; 53 | 54 | private static final String TEST_DATABASE_NAME = "log4mongotest"; 55 | 56 | private static final String TEST_COLLECTION_NAME = "logeventslayout"; 57 | 58 | private static final String APPENDER_NAME = "MongoDBPatternLayout"; 59 | 60 | private final MongoClient mongo; 61 | 62 | private MongoCollection collection; 63 | 64 | public TestMongoDbPatternLayout() throws Exception { 65 | mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 66 | } 67 | 68 | @BeforeClass 69 | public static void setUpBeforeClass() throws Exception { 70 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 71 | mongo.dropDatabase(TEST_DATABASE_NAME); 72 | } 73 | 74 | @AfterClass 75 | public static void tearDownAfterClass() throws Exception { 76 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 77 | mongo.dropDatabase(TEST_DATABASE_NAME); 78 | } 79 | 80 | @Before 81 | public void setUp() throws Exception { 82 | // Ensure both the appender and the JUnit test use the same collection 83 | // object - provides consistency across reads (JUnit) & writes (Log4J) 84 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 85 | collection.drop(); 86 | } 87 | 88 | @Test 89 | public void testValidPatternLayout() { 90 | PropertyConfigurator.configure(getValidPatternLayoutProperties()); 91 | 92 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 93 | APPENDER_NAME); 94 | 95 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 96 | appender.setCollection(collection); 97 | 98 | assertEquals(0L, countLogEntries()); 99 | log.warn("Warn entry"); 100 | assertEquals(1L, countLogEntries()); 101 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 102 | 103 | // verify log entry content 104 | FindIterable entries = collection.find(DBObject.class); 105 | for (DBObject entry : entries) { 106 | assertNotNull(entry); 107 | assertEquals("WARN", entry.get("level")); 108 | assertEquals("Warn entry", entry.get("message")); 109 | // This is the custom info. In the pattern, the field is named "extra". 110 | assertEquals("useful info", entry.get("extra")); 111 | } 112 | } 113 | 114 | @Test 115 | public void testQuotesInMessage() { 116 | PropertyConfigurator.configure(getValidPatternLayoutProperties()); 117 | 118 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 119 | APPENDER_NAME); 120 | 121 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 122 | appender.setCollection(collection); 123 | 124 | assertEquals(0L, countLogEntries()); 125 | String msg = "\"Quotes\" ' \"embedded\""; 126 | log.warn(msg); 127 | assertEquals(1L, countLogEntries()); 128 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 129 | 130 | // verify log entry content 131 | FindIterable entries = collection.find(DBObject.class); 132 | for (DBObject entry : entries) { 133 | assertNotNull(entry); 134 | assertEquals("WARN", entry.get("level")); 135 | assertEquals(msg, entry.get("message")); 136 | } 137 | } 138 | 139 | @Test 140 | public void testNestedDoc() { 141 | PropertyConfigurator.configure(getNestedDocPatternLayoutProperties()); 142 | 143 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 144 | APPENDER_NAME); 145 | 146 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 147 | appender.setCollection(collection); 148 | 149 | assertEquals(0L, countLogEntries()); 150 | String msg = "Nested warning"; 151 | log.warn(msg); 152 | assertEquals(1L, countLogEntries()); 153 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 154 | 155 | // verify log entry content 156 | FindIterable entries = collection.find(DBObject.class); 157 | for (DBObject entry : entries) { 158 | assertNotNull(entry); 159 | assertEquals("WARN", entry.get("level")); 160 | DBObject nestedDoc = (DBObject) entry.get("nested"); 161 | assertEquals(msg, nestedDoc.get("message")); 162 | } 163 | } 164 | 165 | /** 166 | * Tests that the document stored in MongoDB has an array as a value if the conversion pattern 167 | * specifies an array as a value. 168 | */ 169 | @Test 170 | public void testArrayValue() { 171 | PropertyConfigurator.configure(getArrayPatternLayoutProperties()); 172 | 173 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 174 | APPENDER_NAME); 175 | 176 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 177 | appender.setCollection(collection); 178 | 179 | assertEquals(0L, countLogEntries()); 180 | String msg = "Message in array"; 181 | log.warn(msg); 182 | assertEquals(1L, countLogEntries()); 183 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 184 | 185 | // verify log entry content 186 | FindIterable entries = collection.find(DBObject.class); 187 | for (DBObject entry : entries) { 188 | assertNotNull(entry); 189 | assertEquals("WARN", entry.get("level")); 190 | BasicDBList list = (BasicDBList) entry.get("array"); 191 | assertEquals(2, list.size()); 192 | assertEquals(this.getClass().getSimpleName(), list.get(0)); 193 | assertEquals(msg, list.get(1)); 194 | } 195 | } 196 | 197 | @Test 198 | public void testHostInfoPatternLayout() throws Exception { 199 | PropertyConfigurator.configure(getHostInfoPatternLayoutProperties()); 200 | 201 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 202 | APPENDER_NAME); 203 | 204 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 205 | appender.setCollection(collection); 206 | 207 | assertEquals(0L, countLogEntries()); 208 | String msg = "Message in array"; 209 | log.warn(msg); 210 | assertEquals(1L, countLogEntries()); 211 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 212 | 213 | // verify log entry content 214 | FindIterable entries = collection.find(DBObject.class); 215 | for (DBObject entry : entries) { 216 | assertNotNull(entry); 217 | assertEquals("WARN", entry.get("level")); 218 | assertNotNull(entry.get("host")); 219 | DBObject hostinfo = (DBObject) entry.get("host"); 220 | assertNotNull(hostinfo.get("name")); 221 | assertNotNull(hostinfo.get("ip_address")); 222 | assertNotNull(hostinfo.get("process")); 223 | assertEquals(InetAddress.getLocalHost().getHostName(), hostinfo.get("name")); 224 | assertEquals(InetAddress.getLocalHost().getHostAddress(), hostinfo.get("ip_address")); 225 | } 226 | } 227 | 228 | @Test 229 | public void testBackslashInMessage() { 230 | PropertyConfigurator.configure(getValidPatternLayoutProperties()); 231 | 232 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 233 | APPENDER_NAME); 234 | 235 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 236 | appender.setCollection(collection); 237 | 238 | assertEquals(0L, countLogEntries()); 239 | String msg = "c:\\users\\some_file\\"; 240 | log.warn(msg); 241 | assertEquals(1L, countLogEntries()); 242 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 243 | 244 | String msgDoubleBackslash = "c:\\\\users\\\\some_file\\\\"; 245 | log.info(msgDoubleBackslash); 246 | assertEquals(2L, countLogEntries()); 247 | assertEquals(1L, countLogEntriesAtLevel("INFO")); 248 | 249 | // verify log entry content 250 | DBObject queryObj = new BasicDBObject(); 251 | queryObj.put("level", "WARN"); 252 | FindIterable entries = collection.find(DBObject.class).limit(1); 253 | for (DBObject entry : entries) { 254 | assertNotNull(entry); 255 | assertEquals("WARN", entry.get("level")); 256 | assertEquals(msg, entry.get("message")); 257 | } 258 | 259 | queryObj = new BasicDBObject(); 260 | queryObj.put("level", "INFO"); 261 | entries = collection.find(DBObject.class).skip(1); 262 | for (DBObject entry : entries) { 263 | assertNotNull(entry); 264 | assertEquals("INFO", entry.get("level")); 265 | assertEquals(msgDoubleBackslash, entry.get("message")); 266 | } 267 | } 268 | 269 | @Test 270 | public void testPerformance() { 271 | PropertyConfigurator.configure(getValidPatternLayoutProperties()); 272 | 273 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 274 | APPENDER_NAME); 275 | 276 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 277 | appender.setCollection(collection); 278 | 279 | int NUM_MESSAGES = 1000; 280 | long now = System.currentTimeMillis(); 281 | for (int i = 0; i < NUM_MESSAGES; i++) { 282 | log.warn("Warn entry"); 283 | } 284 | long dur = System.currentTimeMillis() - now; 285 | System.out.println("Milliseconds for MongoDbPatternLayoutAppender to log " + NUM_MESSAGES 286 | + " messages:" + dur); 287 | assertEquals(NUM_MESSAGES, countLogEntries()); 288 | } 289 | 290 | private long countLogEntries() { 291 | return (collection.count()); 292 | } 293 | 294 | private long countLogEntriesAtLevel(final String level) { 295 | return (countLogEntriesWhere(Document.parse("{ 'level' : '" + level.toUpperCase() + "' }"))); 296 | } 297 | 298 | private long countLogEntriesWhere(final Document whereClause) { 299 | return collection.count(whereClause); 300 | } 301 | 302 | private Properties getValidPatternLayoutProperties() { 303 | Properties props = new Properties(); 304 | props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); 305 | props.put("log4j.appender.MongoDBPatternLayout", 306 | "org.log4mongo.MongoDbPatternLayoutAppender"); 307 | props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); 308 | props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); 309 | props.put( 310 | "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", 311 | "{\"extra\":\"%e\",\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"class\":\"%c{1}\",\"message\":\"%m\"}"); 312 | return props; 313 | } 314 | 315 | private Properties getNestedDocPatternLayoutProperties() { 316 | Properties props = new Properties(); 317 | props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); 318 | props.put("log4j.appender.MongoDBPatternLayout", 319 | "org.log4mongo.MongoDbPatternLayoutAppender"); 320 | props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); 321 | props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); 322 | props.put( 323 | "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", 324 | "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"nested\":{\"class\":\"%c{1}\",\"message\":\"%m\"}}"); 325 | return props; 326 | } 327 | 328 | private Properties getArrayPatternLayoutProperties() { 329 | Properties props = new Properties(); 330 | props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); 331 | props.put("log4j.appender.MongoDBPatternLayout", 332 | "org.log4mongo.MongoDbPatternLayoutAppender"); 333 | props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); 334 | props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); 335 | props.put("log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", 336 | "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"array\":[\"%c{1}\",\"%m\"]}"); 337 | return props; 338 | } 339 | 340 | private Properties getHostInfoPatternLayoutProperties() { 341 | Properties props = new Properties(); 342 | props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); 343 | props.put("log4j.appender.MongoDBPatternLayout", 344 | "org.log4mongo.MongoDbPatternLayoutAppender"); 345 | props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); 346 | props.put("log4j.appender.MongoDBPatternLayout.layout", 347 | "org.log4mongo.contrib.HostInfoPatternLayout"); 348 | props.put( 349 | "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", 350 | "{\"timestamp\":\"%d{yyyy-MM-dd'T'HH:mm:ss'Z'}\",\"level\":\"%p\",\"array\":[\"%c{1}\",\"%m\"],\"host\":{\"name\":\"%H\", \"process\":\"%V\", \"ip_address\":\"%I\"}}"); 351 | return props; 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestMongoDbPatternLayoutDate.java: -------------------------------------------------------------------------------- 1 | package org.log4mongo; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | 5 | import java.util.Date; 6 | import java.util.Properties; 7 | 8 | import org.apache.log4j.Logger; 9 | import org.apache.log4j.PropertyConfigurator; 10 | import org.junit.AfterClass; 11 | import org.junit.Before; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | 15 | import com.mongodb.DBObject; 16 | import com.mongodb.MongoClient; 17 | import com.mongodb.client.FindIterable; 18 | import com.mongodb.client.MongoCollection; 19 | 20 | /** 21 | * JUnit unit tests for PatternLayout style logging with Date as a BSON object. 22 | *

23 | * Since tests may depend on different Log4J property settings, each test reconfigures an appender 24 | * using a Properties object. 25 | *

26 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that server 27 | * is listening on the default port (27017) on localhost. 28 | */ 29 | public class TestMongoDbPatternLayoutDate { 30 | 31 | public static final String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 32 | 33 | public static final int TEST_MONGO_SERVER_PORT = 27017; 34 | 35 | private static final String TEST_DATABASE_NAME = "log4mongotest"; 36 | 37 | private static final String TEST_COLLECTION_NAME = "logeventslayout"; 38 | 39 | private static final String APPENDER_NAME = "MongoDBPatternLayout"; 40 | 41 | private final MongoClient mongo; 42 | 43 | private MongoCollection collection; 44 | 45 | public TestMongoDbPatternLayoutDate() throws Exception { 46 | mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 47 | } 48 | 49 | @BeforeClass 50 | public static void setUpBeforeClass() throws Exception { 51 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 52 | mongo.dropDatabase(TEST_DATABASE_NAME); 53 | } 54 | 55 | @AfterClass 56 | public static void tearDownAfterClass() throws Exception { 57 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 58 | mongo.dropDatabase(TEST_DATABASE_NAME); 59 | } 60 | 61 | @Before 62 | public void setUp() throws Exception { 63 | // Ensure both the appender and the JUnit test use the same collection 64 | // object - provides consistency across reads (JUnit) & writes (Log4J) 65 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 66 | collection.drop(); 67 | } 68 | 69 | /** 70 | * Here the timestamp we get from DB is Date type. Hence it is valid here to type cast timestamp into java.util.Date 71 | * and it does not throw any exception. If timestamp is stored as string in the db then it will not cast into the 72 | * java.util.Date and it will throw typecast exception. 73 | */ 74 | @Test(expected = Test.None.class) 75 | public void testDateStoredInMongodbIsISOObject() { 76 | PropertyConfigurator.configure(getValidPatternLayoutProperties()); 77 | 78 | MongoDbAppender appender = (MongoDbAppender) Logger.getRootLogger().getAppender( 79 | APPENDER_NAME); 80 | 81 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 82 | appender.setCollection(collection); 83 | 84 | FindIterable entries = collection.find(DBObject.class); 85 | for (DBObject entry : entries) { 86 | assertNotNull(entry); 87 | //here date is type cast into Date. It will not throw the exception as the date saved in DB is ISODate 88 | Date date = (Date)entry.get("timestamp"); 89 | } 90 | } 91 | 92 | private Properties getValidPatternLayoutProperties() { 93 | Properties props = new Properties(); 94 | props.put("log4j.rootLogger", "DEBUG, MongoDBPatternLayout"); 95 | props.put("log4j.appender.MongoDBPatternLayout", 96 | "org.log4mongo.MongoDbPatternLayoutDateAppender"); 97 | props.put("log4j.appender.MongoDBPatternLayout.databaseName", "log4mongotest"); 98 | props.put("log4j.appender.MongoDBPatternLayout.layout", "org.log4mongo.CustomPatternLayout"); 99 | props.put( 100 | "log4j.appender.MongoDBPatternLayout.layout.ConversionPattern", 101 | "{\"extra\":\"%e\",\"timestamp\":\"%d{DATE}\",\"level\":\"%p\",\"class\":\"%c{1}\",\"message\":\"%m\"}"); 102 | return props; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/TestWriteConcern.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.log4mongo; 18 | 19 | import com.mongodb.BasicDBList; 20 | import com.mongodb.BasicDBObjectBuilder; 21 | import com.mongodb.DBObject; 22 | import com.mongodb.MongoClient; 23 | import com.mongodb.client.FindIterable; 24 | import com.mongodb.client.MongoCollection; 25 | import org.apache.log4j.Logger; 26 | import org.apache.log4j.PropertyConfigurator; 27 | import org.bson.Document; 28 | import org.junit.AfterClass; 29 | import org.junit.Before; 30 | import org.junit.BeforeClass; 31 | import org.junit.Test; 32 | 33 | import java.lang.management.ManagementFactory; 34 | import java.net.InetAddress; 35 | 36 | import static org.junit.Assert.*; 37 | 38 | /** 39 | * JUnit unit tests for using write concerns with MongoDbAppender. 40 | *

41 | * When using FSYNCED, logging is well over a magnitude slower than UNACKNOWLEDGED. 42 | *

43 | * Note: these tests require that a MongoDB server is running, and (by default) assumes that server 44 | * is listening on the default port (27017) on localhost. 45 | */ 46 | public class TestWriteConcern { 47 | 48 | private final static Logger log = Logger.getLogger(TestWriteConcern.class); 49 | 50 | private final static String TEST_MONGO_SERVER_HOSTNAME = "localhost"; 51 | 52 | private final static int TEST_MONGO_SERVER_PORT = 27017; 53 | 54 | private final static String TEST_DATABASE_NAME = "log4mongotest"; 55 | 56 | private final static String TEST_COLLECTION_NAME = "logevents"; 57 | 58 | private final static String MONGODB_APPENDER_NAME = "MongoDB"; 59 | 60 | private final static String LOG4J_PROPS = "src/test/resources/log4j_write_concern.properties"; 61 | 62 | private final MongoClient mongo; 63 | 64 | private final MongoDbAppender appender; 65 | 66 | private MongoCollection collection; 67 | 68 | public TestWriteConcern() throws Exception { 69 | PropertyConfigurator.configure(LOG4J_PROPS); 70 | mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 71 | appender = (MongoDbAppender) Logger.getRootLogger().getAppender(MONGODB_APPENDER_NAME); 72 | } 73 | 74 | @BeforeClass 75 | public static void setUpBeforeClass() throws Exception { 76 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 77 | mongo.dropDatabase(TEST_DATABASE_NAME); 78 | } 79 | 80 | @AfterClass 81 | public static void tearDownAfterClass() throws Exception { 82 | MongoClient mongo = new MongoClient(TEST_MONGO_SERVER_HOSTNAME, TEST_MONGO_SERVER_PORT); 83 | mongo.dropDatabase(TEST_DATABASE_NAME); 84 | } 85 | 86 | @Before 87 | public void setUp() throws Exception { 88 | // Ensure both the appender and the JUnit test use the same collection 89 | // object - provides consistency across reads (JUnit) & writes (Log4J) 90 | collection = mongo.getDatabase(TEST_DATABASE_NAME).getCollection(TEST_COLLECTION_NAME); 91 | collection.drop(); 92 | appender.setCollection(collection); 93 | } 94 | 95 | @Test 96 | public void testInitialized() throws Exception { 97 | if (!appender.isInitialized()) { 98 | fail(); 99 | } 100 | } 101 | 102 | @Test 103 | public void testWriteConcern() throws Exception { 104 | testInitialized(); 105 | assertEquals(appender.getWriteConcern(), "FSYNCED"); 106 | } 107 | 108 | @Test 109 | public void testSingleLogEntry() throws Exception { 110 | log.trace("Trace entry"); 111 | 112 | assertEquals(1L, countLogEntries()); 113 | assertEquals(1L, countLogEntriesAtLevel("trace")); 114 | assertEquals(0L, countLogEntriesAtLevel("debug")); 115 | assertEquals(0L, countLogEntriesAtLevel("info")); 116 | assertEquals(0L, countLogEntriesAtLevel("warn")); 117 | assertEquals(0L, countLogEntriesAtLevel("error")); 118 | assertEquals(0L, countLogEntriesAtLevel("fatal")); 119 | 120 | // verify log entry content 121 | FindIterable entries = collection.find(DBObject.class); 122 | for (DBObject entry : entries) { 123 | assertNotNull(entry); 124 | assertEquals("TRACE", entry.get("level")); 125 | assertEquals("Trace entry", entry.get("message")); 126 | } 127 | } 128 | 129 | @Test 130 | public void testTimestampStoredNatively() throws Exception { 131 | log.debug("Debug entry"); 132 | 133 | assertEquals(1L, countLogEntries()); 134 | 135 | // verify timestamp - presence and data type 136 | FindIterable entries = collection.find(DBObject.class); 137 | for (DBObject entry : entries) { 138 | assertNotNull(entry); 139 | assertTrue("Timestamp is not present in logged entry", entry.containsField("timestamp")); 140 | assertTrue("Timestamp of logged entry is not stored as native date", 141 | (entry.get("timestamp") instanceof java.util.Date)); 142 | } 143 | } 144 | 145 | @Test 146 | public void testAllLevels() throws Exception { 147 | log.trace("Trace entry"); 148 | log.debug("Debug entry"); 149 | log.info("Info entry"); 150 | log.warn("Warn entry"); 151 | log.error("Error entry"); 152 | log.fatal("Fatal entry"); 153 | 154 | assertEquals(6L, countLogEntries()); 155 | assertEquals(1L, countLogEntriesAtLevel("trace")); 156 | assertEquals(1L, countLogEntriesAtLevel("debug")); 157 | assertEquals(1L, countLogEntriesAtLevel("info")); 158 | assertEquals(1L, countLogEntriesAtLevel("warn")); 159 | assertEquals(1L, countLogEntriesAtLevel("error")); 160 | assertEquals(1L, countLogEntriesAtLevel("fatal")); 161 | } 162 | 163 | @Test 164 | public void testLogWithException() throws Exception { 165 | log.error("Error entry", new RuntimeException("Here is an exception!")); 166 | 167 | assertEquals(1L, countLogEntries()); 168 | assertEquals(1L, countLogEntriesAtLevel("error")); 169 | 170 | // verify log entry content 171 | FindIterable entries = collection.find(DBObject.class); 172 | for (DBObject entry : entries) { 173 | assertNotNull(entry); 174 | assertEquals("ERROR", entry.get("level")); 175 | assertEquals("Error entry", entry.get("message")); 176 | 177 | // verify throwable presence and content 178 | assertTrue("Throwable is not present in logged entry", 179 | entry.containsField("throwables")); 180 | BasicDBList throwables = (BasicDBList) entry.get("throwables"); 181 | assertEquals(1, throwables.size()); 182 | 183 | DBObject throwableEntry = (DBObject) throwables.get("0"); 184 | assertTrue("Throwable message is not present in logged entry", 185 | throwableEntry.containsField("message")); 186 | assertEquals("Here is an exception!", throwableEntry.get("message")); 187 | } 188 | } 189 | 190 | @Test 191 | public void testLogWithChainedExceptions() throws Exception { 192 | Exception rootCause = new RuntimeException("I'm the real culprit!"); 193 | 194 | log.error("Error entry", new RuntimeException("I'm an innocent bystander.", rootCause)); 195 | 196 | assertEquals(1L, countLogEntries()); 197 | assertEquals(1L, countLogEntriesAtLevel("error")); 198 | 199 | // verify log entry content 200 | FindIterable entries = collection.find(DBObject.class); 201 | for (DBObject entry : entries) { 202 | assertNotNull(entry); 203 | assertEquals("ERROR", entry.get("level")); 204 | assertEquals("Error entry", entry.get("message")); 205 | 206 | // verify throwable presence and content 207 | assertTrue("Throwable is not present in logged entry", 208 | entry.containsField("throwables")); 209 | BasicDBList throwables = (BasicDBList) entry.get("throwables"); 210 | assertEquals(2, throwables.size()); 211 | 212 | DBObject rootEntry = (DBObject) throwables.get("0"); 213 | assertTrue("Throwable message is not present in logged entry", 214 | rootEntry.containsField("message")); 215 | assertEquals("I'm an innocent bystander.", rootEntry.get("message")); 216 | 217 | DBObject chainedEntry = (DBObject) throwables.get("1"); 218 | assertTrue("Throwable message is not present in logged entry", 219 | chainedEntry.containsField("message")); 220 | assertEquals("I'm the real culprit!", chainedEntry.get("message")); 221 | } 222 | } 223 | 224 | @Test 225 | public void testQuotesInMessage() { 226 | assertEquals(0L, countLogEntries()); 227 | log.warn("Quotes\" \"embedded"); 228 | assertEquals(1L, countLogEntries()); 229 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 230 | 231 | // verify log entry content 232 | FindIterable entries = collection.find(DBObject.class); 233 | for (DBObject entry : entries) { 234 | assertNotNull(entry); 235 | assertEquals("WARN", entry.get("level")); 236 | assertEquals("Quotes\" \"embedded", entry.get("message")); 237 | } 238 | } 239 | 240 | @Test 241 | public void testPerformance() throws Exception { 242 | // Log one event to minimize start up effects on performance 243 | log.warn("Warn entry"); 244 | 245 | long NUM_MESSAGES = 100; 246 | long now = System.currentTimeMillis(); 247 | for (long i = 0; i < NUM_MESSAGES; i++) { 248 | log.warn("Warn entry"); 249 | } 250 | long dur = System.currentTimeMillis() - now; 251 | System.out.println("Milliseconds for MongoDbAppender with write concern " 252 | + appender.getWriteConcern() + " to log " + NUM_MESSAGES + " messages:" + dur); 253 | assertEquals(NUM_MESSAGES + 1, countLogEntries()); 254 | } 255 | 256 | @Test 257 | public void testRegularLoggerRecordsLoggerNameCorrectly() { 258 | log.info("From an unwrapped logger"); 259 | 260 | assertEquals(1, countLogEntries()); 261 | assertEquals(1, countLogEntriesAtLevel("info")); 262 | assertEquals(1, 263 | countLogEntriesWhere(Document 264 | .parse("{ 'loggerName.className' : 'TestWriteConcern' }"))); 265 | assertEquals(1, 266 | countLogEntriesWhere(Document.parse("{ 'class.className' : 'TestWriteConcern' }"))); 267 | } 268 | 269 | @Test 270 | public void testWrappedLoggerRecordsLoggerNameCorrectly() { 271 | WrappedLogger wrapped = new WrappedLogger(log); 272 | wrapped.info("From a wrapped logger"); 273 | 274 | assertEquals(1, countLogEntries()); 275 | assertEquals(1, countLogEntriesAtLevel("info")); 276 | assertEquals(1, 277 | countLogEntriesWhere(Document 278 | .parse("{ 'loggerName.className' : 'TestWriteConcern' }"))); 279 | assertEquals(1, 280 | countLogEntriesWhere(Document.parse("{ 'class.className' : 'WrappedLogger' }"))); 281 | } 282 | 283 | @Test 284 | public void testHostInfoRecords() throws Exception { 285 | assertEquals(0L, countLogEntries()); 286 | log.warn("Testing hostinfo"); 287 | assertEquals(1L, countLogEntries()); 288 | assertEquals(1L, countLogEntriesAtLevel("WARN")); 289 | 290 | // verify log entry content 291 | FindIterable entries = collection.find(DBObject.class); 292 | for (DBObject entry : entries) { 293 | assertNotNull(entry); 294 | assertEquals("WARN", entry.get("level")); 295 | assertEquals("Testing hostinfo", entry.get("message")); 296 | assertNotNull(entry.get("host")); 297 | DBObject hostinfo = (DBObject) entry.get("host"); 298 | assertNotNull(hostinfo.get("process")); 299 | assertEquals(InetAddress.getLocalHost().getHostName(), hostinfo.get("name")); 300 | assertEquals(ManagementFactory.getRuntimeMXBean().getName(), hostinfo.get("process")); 301 | } 302 | } 303 | 304 | private long countLogEntries() { 305 | return (collection.count()); 306 | } 307 | 308 | private long countLogEntriesAtLevel(final String level) { 309 | return (countLogEntriesWhere(Document.parse("{ 'level' : '" + level.toUpperCase() + "' }"))); 310 | } 311 | 312 | private long countLogEntriesWhere(final Document whereClause) { 313 | return collection.count(whereClause); 314 | } 315 | 316 | } 317 | -------------------------------------------------------------------------------- /src/test/java/org/log4mongo/WrappedLogger.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Robert Stewart (robert@wombatnation.com) 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package org.log4mongo; 17 | 18 | import org.apache.log4j.Level; 19 | import org.apache.log4j.Logger; 20 | 21 | import java.text.MessageFormat; 22 | 23 | /** 24 | * 25 | */ 26 | public class WrappedLogger { 27 | 28 | private final Logger delegate; 29 | 30 | public WrappedLogger(Logger logger) { 31 | this.delegate = logger; 32 | } 33 | 34 | /** 35 | * @see org.apache.log4j.Category#fatal(java.lang.Object, java.lang.Throwable) 36 | */ 37 | public void fatal(Object message, Throwable t) { 38 | delegate.fatal(message, t); 39 | } 40 | 41 | /** 42 | * @see org.apache.log4j.Category#fatal(java.lang.Object) 43 | */ 44 | public void fatal(Object message) { 45 | delegate.fatal(message); 46 | } 47 | 48 | public void fatal(String format, Object... params) { 49 | // fatal is always enabled 50 | if (params[params.length - 1] instanceof Throwable) { 51 | delegate.fatal(generateFormattedMessage(format, params), 52 | (Throwable) params[params.length - 1]); 53 | } else { 54 | delegate.fatal(generateFormattedMessage(format, params)); 55 | } 56 | } 57 | 58 | public boolean isErrorEnabled() { 59 | return delegate.isEnabledFor(Level.ERROR); 60 | } 61 | 62 | /** 63 | * @see org.apache.log4j.Category#error(java.lang.Object, java.lang.Throwable) 64 | */ 65 | public void error(Object message, Throwable t) { 66 | delegate.error(message, t); 67 | } 68 | 69 | /** 70 | * @see org.apache.log4j.Category#error(java.lang.Object) 71 | */ 72 | public void error(Object message) { 73 | delegate.error(message); 74 | } 75 | 76 | public void error(String format, Object... params) { 77 | // skip isErrorEnabled() check here since more than likely it is. 78 | // Log4j checks anyway, so... 79 | if (params[params.length - 1] instanceof Throwable) { 80 | final String message = generateFormattedMessage(format, params); 81 | delegate.error(message, (Throwable) params[params.length - 1]); 82 | } else { 83 | delegate.error(generateFormattedMessage(format, params)); 84 | } 85 | } 86 | 87 | public boolean isWarnEnabled() { 88 | return delegate.isEnabledFor(Level.WARN); 89 | } 90 | 91 | /** 92 | * @see org.apache.log4j.Category#warn(java.lang.Object, java.lang.Throwable) 93 | */ 94 | public void warn(Object message, Throwable t) { 95 | delegate.warn(message, t); 96 | } 97 | 98 | /** 99 | * @see org.apache.log4j.Category#warn(java.lang.Object) 100 | */ 101 | public void warn(Object message) { 102 | delegate.warn(message); 103 | } 104 | 105 | public void warn(String format, Object... params) { 106 | if (params[params.length - 1] instanceof Throwable) { 107 | delegate.warn(generateFormattedMessage(format, params), 108 | (Throwable) params[params.length - 1]); 109 | } else { 110 | delegate.warn(generateFormattedMessage(format, params)); 111 | } 112 | } 113 | 114 | /** 115 | * @see org.apache.log4j.Category#isInfoEnabled() 116 | */ 117 | public boolean isInfoEnabled() { 118 | return delegate.isInfoEnabled(); 119 | } 120 | 121 | /** 122 | * @see org.apache.log4j.Category#info(java.lang.Object, java.lang.Throwable) 123 | */ 124 | public void info(Object message, Throwable t) { 125 | delegate.info(message, t); 126 | } 127 | 128 | /** 129 | * @see org.apache.log4j.Category#info(java.lang.Object) 130 | */ 131 | public void info(Object message) { 132 | delegate.info(message); 133 | } 134 | 135 | public void info(String format, Object... params) { 136 | if (isInfoEnabled()) { 137 | if (params[params.length - 1] instanceof Throwable) { 138 | delegate.info(generateFormattedMessage(format, params), 139 | (Throwable) params[params.length - 1]); 140 | } else { 141 | delegate.info(generateFormattedMessage(format, params)); 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * @see org.apache.log4j.Category#isDebugEnabled() 148 | */ 149 | public boolean isDebugEnabled() { 150 | return delegate.isDebugEnabled(); 151 | } 152 | 153 | /** 154 | * @see org.apache.log4j.Category#debug(java.lang.Object, java.lang.Throwable) 155 | */ 156 | public void debug(Object message, Throwable t) { 157 | delegate.debug(message, t); 158 | } 159 | 160 | /** 161 | * @see org.apache.log4j.Category#debug(java.lang.Object) 162 | */ 163 | public void debug(Object message) { 164 | delegate.debug(message); 165 | } 166 | 167 | public void debug(String format, Object... params) { 168 | if (isDebugEnabled()) { 169 | if (params[params.length - 1] instanceof Throwable) { 170 | delegate.debug(generateFormattedMessage(format, params), 171 | (Throwable) params[params.length - 1]); 172 | } else { 173 | delegate.debug(generateFormattedMessage(format, params)); 174 | } 175 | } 176 | } 177 | 178 | private String generateFormattedMessage(String format, Object... params) { 179 | return MessageFormat.format(format, params); 180 | } 181 | 182 | /** 183 | * @see org.apache.log4j.Logger#isTraceEnabled() 184 | */ 185 | public boolean isTraceEnabled() { 186 | return delegate.isTraceEnabled(); 187 | } 188 | 189 | /** 190 | * @see org.apache.log4j.Logger#trace(java.lang.Object, java.lang.Throwable) 191 | */ 192 | public void trace(Object message, Throwable t) { 193 | delegate.trace(message, t); 194 | } 195 | 196 | /** 197 | * @see org.apache.log4j.Logger#trace(java.lang.Object) 198 | */ 199 | public void trace(Object message) { 200 | delegate.trace(message); 201 | } 202 | 203 | public void trace(String format, Object... params) { 204 | if (isTraceEnabled()) { 205 | if (params[params.length - 1] instanceof Throwable) { 206 | delegate.trace(generateFormattedMessage(format, params), 207 | (Throwable) params[params.length - 1]); 208 | } else { 209 | delegate.trace(generateFormattedMessage(format, params)); 210 | } 211 | } 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set the root logger to 2 | log4j.rootLogger=all, MongoDB 3 | 4 | # MongoDB appender classname 5 | log4j.appender.MongoDB=org.log4mongo.MongoDbAppender 6 | log4j.appender.MongoDB.databaseName=log4mongotest 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/log4j_auth.properties: -------------------------------------------------------------------------------- 1 | # Set the root logger to 2 | log4j.rootLogger=all, MongoDBAuth 3 | 4 | # MongoDB appender classname 5 | log4j.appender.MongoDBAuth=org.log4mongo.MongoDbAppender 6 | log4j.appender.MongoDBAuth.databaseName=log4mongotestauth 7 | log4j.appender.MongoDBAuth.userName=open 8 | log4j.appender.MongoDBAuth.password=sesame 9 | -------------------------------------------------------------------------------- /src/test/resources/log4j_extended.properties: -------------------------------------------------------------------------------- 1 | # Set the root logger to 2 | log4j.rootLogger=all, MongoDB 3 | 4 | # MongoDB appender classname 5 | log4j.appender.MongoDB=org.log4mongo.ExtendedMongoDbAppender 6 | log4j.appender.MongoDB.hostname=localhost 7 | log4j.appender.MongoDB.port=27017 8 | #log4j.appender.MongoDB.userName=open 9 | #log4j.appender.MongoDB.password=sesame 10 | 11 | log4j.appender.MongoDB.databaseName=extendedlog4mongotest 12 | log4j.appender.MongoDB.collectionName=logevents 13 | log4j.appender.MongoDB.rootLevelProperties=applicationName=MyProject&eventType=Development -------------------------------------------------------------------------------- /src/test/resources/log4j_extended.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/log4j_write_concern.properties: -------------------------------------------------------------------------------- 1 | # Set the root logger to 2 | log4j.rootLogger=all, MongoDB 3 | 4 | # MongoDB appender classname 5 | log4j.appender.MongoDB=org.log4mongo.MongoDbAppender 6 | log4j.appender.MongoDB.databaseName=log4mongotest 7 | # In older drivers this mode was called FSYNC_SAFE 8 | log4j.appender.MongoDB.writeConcern=FSYNCED --------------------------------------------------------------------------------