001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.nosql.appender.mongodb; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Method; 021import java.util.List; 022 023import com.mongodb.DB; 024import com.mongodb.MongoClient; 025import com.mongodb.ServerAddress; 026import com.mongodb.WriteConcern; 027import org.apache.logging.log4j.Logger; 028import org.apache.logging.log4j.core.appender.AbstractAppender; 029import org.apache.logging.log4j.core.config.plugins.Plugin; 030import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 031import org.apache.logging.log4j.core.config.plugins.PluginFactory; 032import org.apache.logging.log4j.core.util.Loader; 033import org.apache.logging.log4j.core.util.NameUtil; 034import org.apache.logging.log4j.nosql.appender.NoSqlProvider; 035import org.apache.logging.log4j.status.StatusLogger; 036 037/** 038 * The MongoDB implementation of {@link NoSqlProvider}. 039 */ 040@Plugin(name = "MongoDb", category = "Core", printObject = true) 041public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> { 042 private static final Logger LOGGER = StatusLogger.getLogger(); 043 044 private final String collectionName; 045 private final DB database; 046 private final String description; 047 048 private final WriteConcern writeConcern; 049 050 private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName, 051 final String description) { 052 this.database = database; 053 this.writeConcern = writeConcern; 054 this.collectionName = collectionName; 055 this.description = "mongoDb{ " + description + " }"; 056 } 057 058 @Override 059 public MongoDbConnection getConnection() { 060 return new MongoDbConnection(this.database, this.writeConcern, this.collectionName); 061 } 062 063 @Override 064 public String toString() { 065 return this.description; 066 } 067 068 /** 069 * Factory method for creating a MongoDB provider within the plugin manager. 070 * 071 * @param collectionName The name of the MongoDB collection to which log events should be written. 072 * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to 073 * {@link WriteConcern#ACKNOWLEDGED}. 074 * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern 075 * constant. Defaults to {@link WriteConcern}. 076 * @param databaseName The name of the MongoDB database containing the collection to which log events should be 077 * written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}. 078 * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with 079 * {@code factoryClassName&factoryMethodName!=null}. 080 * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually 081 * exclusive with {@code factoryClassName&factoryMethodName!=null}. 082 * @param username The username to authenticate against the MongoDB server with. 083 * @param password The password to authenticate against the MongoDB server with. 084 * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a 085 * {@link DB} or a {@link MongoClient}. 086 * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory 087 * class. 088 * @return a new MongoDB provider. 089 */ 090 @PluginFactory 091 public static MongoDbProvider createNoSqlProvider( 092 @PluginAttribute("collectionName") final String collectionName, 093 @PluginAttribute("writeConcernConstant") final String writeConcernConstant, 094 @PluginAttribute("writeConcernConstantClass") final String writeConcernConstantClassName, 095 @PluginAttribute("databaseName") final String databaseName, 096 @PluginAttribute("server") final String server, 097 @PluginAttribute("port") final String port, 098 @PluginAttribute("username") final String username, 099 @PluginAttribute(value = "password", sensitive = true) final String password, 100 @PluginAttribute("factoryClassName") final String factoryClassName, 101 @PluginAttribute("factoryMethodName") final String factoryMethodName) { 102 DB database; 103 String description; 104 if (factoryClassName != null && factoryClassName.length() > 0 && 105 factoryMethodName != null && factoryMethodName.length() > 0) { 106 try { 107 final Class<?> factoryClass = Loader.loadClass(factoryClassName); 108 final Method method = factoryClass.getMethod(factoryMethodName); 109 final Object object = method.invoke(null); 110 111 if (object instanceof DB) { 112 database = (DB) object; 113 } else if (object instanceof MongoClient) { 114 if (databaseName != null && databaseName.length() > 0) { 115 database = ((MongoClient) object).getDB(databaseName); 116 } else { 117 LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is " 118 + "required.", factoryClassName, factoryMethodName); 119 return null; 120 } 121 } else if (object == null) { 122 LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName); 123 return null; 124 } else { 125 LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName, 126 factoryMethodName, object.getClass().getName()); 127 return null; 128 } 129 130 description = "database=" + database.getName(); 131 final List<ServerAddress> addresses = database.getMongo().getAllAddress(); 132 if (addresses.size() == 1) { 133 description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort(); 134 } else { 135 description += ", servers=["; 136 for (final ServerAddress address : addresses) { 137 description += " { " + address.getHost() + ", " + address.getPort() + " } "; 138 } 139 description += "]"; 140 } 141 } catch (final ClassNotFoundException e) { 142 LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e); 143 return null; 144 } catch (final NoSuchMethodException e) { 145 LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName, 146 factoryMethodName, e); 147 return null; 148 } catch (final Exception e) { 149 LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName, 150 e); 151 return null; 152 } 153 } else if (databaseName != null && databaseName.length() > 0) { 154 description = "database=" + databaseName; 155 try { 156 if (server != null && server.length() > 0) { 157 final int portInt = AbstractAppender.parseInt(port, 0); 158 description += ", server=" + server; 159 if (portInt > 0) { 160 description += ", port=" + portInt; 161 database = new MongoClient(server, portInt).getDB(databaseName); 162 } else { 163 database = new MongoClient(server).getDB(databaseName); 164 } 165 } else { 166 database = new MongoClient().getDB(databaseName); 167 } 168 } catch (final Exception e) { 169 LOGGER.error("Failed to obtain a database instance from the MongoClient at server [{}] and " 170 + "port [{}].", server, port); 171 return null; 172 } 173 } else { 174 LOGGER.error("No factory method was provided so the database name is required."); 175 return null; 176 } 177 178 if (!database.isAuthenticated()) { 179 if (username != null && username.length() > 0 && password != null && password.length() > 0) { 180 description += ", username=" + username + ", passwordHash=" 181 + NameUtil.md5(password + MongoDbProvider.class.getName()); 182 MongoDbConnection.authenticate(database, username, password); 183 } else { 184 LOGGER.error("The database is not already authenticated so you must supply a username and password " 185 + "for the MongoDB provider."); 186 return null; 187 } 188 } 189 190 WriteConcern writeConcern; 191 if (writeConcernConstant != null && writeConcernConstant.length() > 0) { 192 if (writeConcernConstantClassName != null && writeConcernConstantClassName.length() > 0) { 193 try { 194 final Class<?> writeConcernConstantClass = Loader.loadClass(writeConcernConstantClassName); 195 final Field field = writeConcernConstantClass.getField(writeConcernConstant); 196 writeConcern = (WriteConcern) field.get(null); 197 } catch (final Exception e) { 198 LOGGER.error("Write concern constant [{}.{}] not found, using default.", 199 writeConcernConstantClassName, writeConcernConstant); 200 writeConcern = WriteConcern.ACKNOWLEDGED; 201 } 202 } else { 203 writeConcern = WriteConcern.valueOf(writeConcernConstant); 204 if (writeConcern == null) { 205 LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant); 206 writeConcern = WriteConcern.ACKNOWLEDGED; 207 } 208 } 209 } else { 210 writeConcern = WriteConcern.ACKNOWLEDGED; 211 } 212 213 return new MongoDbProvider(database, writeConcern, collectionName, description); 214 } 215}