Java tutorial
/* * Copyright (C) 2009 Peter Monks (pmonks@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.log4mongo; import com.mongodb.*; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import org.apache.log4j.spi.ErrorCode; import org.bson.BSONObject; import org.bson.Document; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Log4J Appender that writes log events into a MongoDB document oriented database. Log events are * fully parsed and stored as structured records in MongoDB (this appender does not require, nor use * a Log4J layout). * <p> * The appender does <u>not</u> create any indexes on the data that's stored - it is assumed that if * query performance is required, those would be created externally (e.g., in the MongoDB shell or * other external application). * * @author Peter Monks (pmonks@gmail.com) * @see <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Appender.html">Log4J * Appender Interface</a> * @see <a href="http://www.mongodb.org/">MongoDB</a> */ public class MongoDbAppender extends BsonAppender { private final static String DEFAULT_MONGO_DB_HOSTNAME = "localhost"; private final static String DEFAULT_MONGO_DB_PORT = "27017"; private final static String DEFAULT_MONGO_DB_DATABASE_NAME = "log4mongo"; private final static String DEFAULT_MONGO_DB_COLLECTION_NAME = "logevents"; private WriteConcern concern; private String hostname = DEFAULT_MONGO_DB_HOSTNAME; private String port = DEFAULT_MONGO_DB_PORT; private String databaseName = DEFAULT_MONGO_DB_DATABASE_NAME; private String collectionName = DEFAULT_MONGO_DB_COLLECTION_NAME; private String userName = null; private String password = null; private String writeConcern = null; private MongoClient mongo = null; private MongoCollection collection = null; private boolean initialized = false; /** * @see org.apache.log4j.Appender#requiresLayout() */ public boolean requiresLayout() { return (false); } /** * @see org.apache.log4j.AppenderSkeleton#activateOptions() */ @Override public void activateOptions() { try { // Close previous connections if reactivating if (mongo != null) { close(); } MongoCredential credentials = null; if (userName != null && userName.trim().length() > 0) { credentials = MongoCredential.createCredential(userName, databaseName, password.toCharArray()); password = null; } mongo = getMongo(getServerAddresses(hostname, port), (credentials != null) ? Arrays.asList(credentials) : null); MongoDatabase database = getDatabase(mongo, databaseName); setCollection(database.getCollection(collectionName)); initialized = true; } catch (Exception e) { errorHandler.error("Unexpected exception while initialising MongoDbAppender.", e, ErrorCode.GENERIC_FAILURE); } } /* * This method could be overridden to provide the DB instance from an existing connection. */ protected MongoDatabase getDatabase(MongoClient mongo, String databaseName) { return mongo.getDatabase(databaseName); } /* * This method could be overridden to provide the Mongo instance from an existing connection. */ protected MongoClient getMongo(List<ServerAddress> addresses) { if (addresses.size() < 2) { return new MongoClient(addresses.get(0)); } else { // Replica set return new MongoClient(addresses); } } private MongoClient getMongo(List<ServerAddress> addresses, List<MongoCredential> credentials) { if (credentials == null) { return this.getMongo(addresses); } if (addresses.size() < 2) { return new MongoClient(addresses.get(0), credentials); } else { // Replica set return new MongoClient(addresses, credentials); } } /** * Note: this method is primarily intended for use by the unit tests. * * @param collection * The MongoDB collection to use when logging events. */ public void setCollection(final MongoCollection collection) { assert collection != null : "collection must not be null"; this.collection = collection; } /** * @see org.apache.log4j.Appender#close() */ public void close() { if (mongo != null) { collection = null; mongo.close(); } } /** * @return The hostname of the MongoDB server <i>(will not be null, empty or blank)</i>. */ public String getHostname() { return hostname; } /** * @param hostname * The MongoDB hostname to set <i>(must not be null, empty or blank)</i>. */ public void setHostname(final String hostname) { assert hostname != null : "hostname must not be null"; assert hostname.trim().length() > 0 : "hostname must not be empty or blank"; this.hostname = hostname; } /** * @return The port of the MongoDB server <i>(will be greater than 0)</i>. */ public String getPort() { return port; } /** * @param port * The port to set <i>(must not be null, empty or blank)</i>. */ public void setPort(final String port) { assert port != null : "port must not be null"; assert port.trim().length() > 0 : "port must not be empty or blank"; this.port = port; } /** * @return The database used in the MongoDB server <i>(will not be null, empty or blank)</i>. */ public String getDatabaseName() { return databaseName; } /** * @param databaseName * The database to use in the MongoDB server <i>(must not be null, empty or * blank)</i>. */ public void setDatabaseName(final String databaseName) { assert databaseName != null : "database must not be null"; assert databaseName.trim().length() > 0 : "database must not be empty or blank"; this.databaseName = databaseName; } /** * @return The collection used within the database in the MongoDB server <i>(will not be null, * empty or blank)</i>. */ public String getCollectionName() { return collectionName; } /** * @param collectionName * The collection used within the database in the MongoDB server <i>(must not be * null, empty or blank)</i>. */ public void setCollectionName(final String collectionName) { assert collectionName != null : "collection must not be null"; assert collectionName.trim().length() > 0 : "collection must not be empty or blank"; this.collectionName = collectionName; } /** * @return The userName used to authenticate with MongoDB <i>(may be null)</i>. */ public String getUserName() { return userName; } /** * @param userName * The userName to use when authenticating with MongoDB <i>(may be null)</i>. */ public void setUserName(final String userName) { this.userName = userName; } /** * @param password * The password to use when authenticating with MongoDB <i>(may be null)</i>. */ public void setPassword(final String password) { this.password = password; } /** * @return the writeConcern setting for Mongo. */ public String getWriteConcern() { return writeConcern; } /** * @param writeConcern * The WriteConcern setting for Mongo.<i>(may be null). If null, set to default of * dbCollection's writeConcern.</i> */ public void setWriteConcern(final String writeConcern) { this.writeConcern = writeConcern; concern = WriteConcern.valueOf(writeConcern); } public WriteConcern getConcern() { if (concern == null) { concern = getCollection().getWriteConcern(); } return concern; } /** * @param bson * The BSON object to insert into a MongoDB database collection. */ @Override public void append(BSONObject bson) { if (initialized && bson != null) { try { getCollection().insertOne(new Document(bson.toMap())); } catch (MongoException e) { errorHandler.error("Failed to insert document to MongoDB", e, ErrorCode.WRITE_FAILURE); } } } /** * Returns true if appender was successfully initialized. If this method returns false, the * appender should not attempt to log events. * * @return true if appender was successfully initialized */ public boolean isInitialized() { return initialized; } /** * @return The MongoDB collection to which events are logged. */ protected MongoCollection getCollection() { if (concern == null) { return collection; } return collection.withWriteConcern(concern); } /** * Returns a List of ServerAddress objects for each host specified in the hostname property. * Returns an empty list if configuration is detected to be invalid, e.g.: * <ul> * <li>Port property doesn't contain either one port or one port per host</li> * <li>After parsing port property to integers, there isn't either one port or one port per host * </li> * </ul> * * @param hostname * Blank space delimited hostnames * @param port * Blank space delimited ports. Must specify one port for all hosts or a port per * host. * * @return List of ServerAddresses to connect to */ private List<ServerAddress> getServerAddresses(String hostname, String port) { List<ServerAddress> addresses = new ArrayList<ServerAddress>(); String[] hosts = hostname.split(" "); String[] ports = port.split(" "); if (ports.length != 1 && ports.length != hosts.length) { errorHandler.error("MongoDB appender port property must contain one port or a port per host", null, ErrorCode.ADDRESS_PARSE_FAILURE); } else { List<Integer> portNums = getPortNumbers(ports); // Validate number of ports again after parsing if (portNums.size() != 1 && portNums.size() != hosts.length) { errorHandler.error("MongoDB appender port property must contain one port or a valid port per host", null, ErrorCode.ADDRESS_PARSE_FAILURE); } else { boolean onePort = (portNums.size() == 1); int i = 0; for (String host : hosts) { int portNum = (onePort) ? portNums.get(0) : portNums.get(i); addresses.add(new ServerAddress(host.trim(), portNum)); i++; } } } return addresses; } private List<Integer> getPortNumbers(String[] ports) { List<Integer> portNumbers = new ArrayList<>(); for (String port : ports) { try { Integer portNum = Integer.valueOf(port.trim()); if (portNum < 0) { errorHandler.error("MongoDB appender port property can't contain a negative integer", null, ErrorCode.ADDRESS_PARSE_FAILURE); } else { portNumbers.add(portNum); } } catch (NumberFormatException e) { errorHandler.error("MongoDB appender can't parse a port property value into an integer", e, ErrorCode.ADDRESS_PARSE_FAILURE); } } return portNumbers; } }