├── .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 | *
333 | * Port property doesn't contain either one port or one port per host
334 | * After parsing port property to integers, there isn't either one port or one port per host
335 | *
336 | *
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
--------------------------------------------------------------------------------