com.google.code.log4mongo.MongoDbAppender.java Source code

Java tutorial

Introduction

Here is the source code for com.google.code.log4mongo.MongoDbAppender.java

Source

/*
 * 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 com.google.code.log4mongo;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.spi.ErrorCode;

import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.ServerAddress;

/**
 * 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).
 * 
 * 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>
 * @version $Id$
 */
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 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 Mongo mongo = null;
    private DBCollection 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();
            }

            List<ServerAddress> addresses = getServerAddresses(hostname, port);
            if (addresses.size() < 2) {
                mongo = new Mongo(addresses.get(0));
            } else {
                // Replication set
                mongo = new Mongo(addresses);
            }
            DB database = mongo.getDB(databaseName);

            if (userName != null && userName.trim().length() > 0) {
                if (!database.authenticate(userName, password.toCharArray())) {
                    throw new RuntimeException("Unable to authenticate with MongoDB server.");
                }

                // Allow password to be GCed
                password = null;
            }

            setCollection(database.getCollection(collectionName));
            initialized = true;
        } catch (Exception e) {
            errorHandler.error("Unexpected exception while initialising MongoDbAppender.", e,
                    ErrorCode.GENERIC_FAILURE);
        }
    }

    /**
     * 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 DBCollection collection) {
        // PRECONDITIONS
        assert collection != null : "collection must not be null.";

        // Body
        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) {
        // PRECONDITIONS
        assert hostname != null : "hostname must not be null";
        assert hostname.trim().length() > 0 : "hostname must not be empty or blank";

        // Body
        this.hostname = hostname;
    }

    /**
     * @return The port of the MongoDB server <i>(will be > 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) {
        // PRECONDITIONS
        assert port != null : "port must not be null";
        assert port.trim().length() > 0 : "port must not be empty or blank";

        // Body
        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) {
        // PRECONDITIONS
        assert databaseName != null : "database must not be null";
        assert databaseName.trim().length() > 0 : "database must not be empty or blank";

        // Body
        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) {
        // PRECONDITIONS
        assert collectionName != null : "collection must not be null";
        assert collectionName.trim().length() > 0 : "collection must not be empty or blank";

        // Body
        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;
    }

    /**
     * @param bson The BSON object to insert into a MongoDB database collection.
     */
    @Override
    public void append(DBObject bson) {
        if (initialized && bson != null) {
            try {
                getCollection().insert(bson);
            } 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 DBCollection getCollection() {
        return (collection);
    }

    /**
     * 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 = getPortNums(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);
                    try {
                        addresses.add(new ServerAddress(host.trim(), portNum));
                    } catch (UnknownHostException e) {
                        errorHandler.error("MongoDB appender hostname property contains unknown host", e,
                                ErrorCode.ADDRESS_PARSE_FAILURE);
                    }
                    i++;
                }
            }
        }
        return addresses;
    }

    private List<Integer> getPortNums(String[] ports) {
        List<Integer> portNums = new ArrayList<Integer>();

        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 {
                    portNums.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 portNums;
    }

}