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.ArrayList; 022import java.util.List; 023 024import org.apache.logging.log4j.Logger; 025import org.apache.logging.log4j.core.appender.AbstractAppender; 026import org.apache.logging.log4j.core.config.plugins.Plugin; 027import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 028import org.apache.logging.log4j.core.config.plugins.PluginFactory; 029import org.apache.logging.log4j.core.util.Loader; 030import org.apache.logging.log4j.core.util.NameUtil; 031import org.apache.logging.log4j.nosql.appender.NoSqlProvider; 032import org.apache.logging.log4j.status.StatusLogger; 033import org.apache.logging.log4j.util.Strings; 034 035import com.mongodb.DB; 036import com.mongodb.MongoClient; 037import com.mongodb.MongoCredential; 038import com.mongodb.ServerAddress; 039import com.mongodb.WriteConcern; 040 041/** 042 * The MongoDB implementation of {@link NoSqlProvider}. 043 */ 044@Plugin(name = "MongoDb", category = "Core", printObject = true) 045public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> { 046 047 private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED; 048 private static final Logger LOGGER = StatusLogger.getLogger(); 049 050 private final String collectionName; 051 private final DB database; 052 private final String description; 053 private final WriteConcern writeConcern; 054 055 private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName, 056 final String description) { 057 this.database = database; 058 this.writeConcern = writeConcern; 059 this.collectionName = collectionName; 060 this.description = "mongoDb{ " + description + " }"; 061 } 062 063 @Override 064 public MongoDbConnection getConnection() { 065 return new MongoDbConnection(this.database, this.writeConcern, this.collectionName); 066 } 067 068 @Override 069 public String toString() { 070 return this.description; 071 } 072 073 /** 074 * Factory method for creating a MongoDB provider within the plugin manager. 075 * 076 * @param collectionName The name of the MongoDB collection to which log events should be written. 077 * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to 078 * {@link WriteConcern#ACKNOWLEDGED}. 079 * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern 080 * constant. Defaults to {@link WriteConcern}. 081 * @param databaseName The name of the MongoDB database containing the collection to which log events should be 082 * written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}. 083 * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with 084 * {@code factoryClassName&factoryMethodName!=null}. 085 * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually 086 * exclusive with {@code factoryClassName&factoryMethodName!=null}. 087 * @param userName The username to authenticate against the MongoDB server with. 088 * @param password The password to authenticate against the MongoDB server with. 089 * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a 090 * {@link DB} or a {@link MongoClient}. 091 * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory 092 * class. 093 * @return a new MongoDB provider. 094 */ 095 @PluginFactory 096 public static MongoDbProvider createNoSqlProvider( 097 @PluginAttribute("collectionName") final String collectionName, 098 @PluginAttribute("writeConcernConstant") final String writeConcernConstant, 099 @PluginAttribute("writeConcernConstantClass") final String writeConcernConstantClassName, 100 @PluginAttribute("databaseName") final String databaseName, 101 @PluginAttribute("server") final String server, 102 @PluginAttribute("port") final String port, 103 @PluginAttribute("userName") final String userName, 104 @PluginAttribute(value = "password", sensitive = true) final String password, 105 @PluginAttribute("factoryClassName") final String factoryClassName, 106 @PluginAttribute("factoryMethodName") final String factoryMethodName) { 107 DB database; 108 String description; 109 if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) { 110 try { 111 final Class<?> factoryClass = Loader.loadClass(factoryClassName); 112 final Method method = factoryClass.getMethod(factoryMethodName); 113 final Object object = method.invoke(null); 114 115 if (object instanceof DB) { 116 database = (DB) object; 117 } else if (object instanceof MongoClient) { 118 if (Strings.isNotEmpty(databaseName)) { 119 database = ((MongoClient) object).getDB(databaseName); 120 } else { 121 LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is " 122 + "required.", factoryClassName, factoryMethodName); 123 return null; 124 } 125 } else if (object == null) { 126 LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName); 127 return null; 128 } else { 129 LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName, 130 factoryMethodName, object.getClass().getName()); 131 return null; 132 } 133 134 description = "database=" + database.getName(); 135 final List<ServerAddress> addresses = database.getMongo().getAllAddress(); 136 if (addresses.size() == 1) { 137 description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort(); 138 } else { 139 description += ", servers=["; 140 for (final ServerAddress address : addresses) { 141 description += " { " + address.getHost() + ", " + address.getPort() + " } "; 142 } 143 description += "]"; 144 } 145 } catch (final ClassNotFoundException e) { 146 LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e); 147 return null; 148 } catch (final NoSuchMethodException e) { 149 LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName, 150 factoryMethodName, e); 151 return null; 152 } catch (final Exception e) { 153 LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName, 154 e); 155 return null; 156 } 157 } else if (Strings.isNotEmpty(databaseName)) { 158 List<MongoCredential> credentials = new ArrayList<>(); 159 description = "database=" + databaseName; 160 if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) { 161 description += ", username=" + userName + ", passwordHash=" 162 + NameUtil.md5(password + MongoDbProvider.class.getName()); 163 credentials.add(MongoCredential.createCredential(userName, databaseName, password.toCharArray())); 164 } 165 try { 166 if (Strings.isNotEmpty(server)) { 167 final int portInt = AbstractAppender.parseInt(port, 0); 168 description += ", server=" + server; 169 if (portInt > 0) { 170 description += ", port=" + portInt; 171 database = new MongoClient(new ServerAddress(server, portInt), credentials).getDB(databaseName); 172 } else { 173 database = new MongoClient(new ServerAddress(server), credentials).getDB(databaseName); 174 } 175 } else { 176 database = new MongoClient(new ServerAddress(), credentials).getDB(databaseName); 177 } 178 } catch (final Exception e) { 179 LOGGER.error( 180 "Failed to obtain a database instance from the MongoClient at server [{}] and " + "port [{}].", 181 server, port); 182 return null; 183 } 184 } else { 185 LOGGER.error("No factory method was provided so the database name is required."); 186 return null; 187 } 188 189 try { 190 database.getCollectionNames(); // Check if the database actually requires authentication 191 } catch (final Exception e) { 192 LOGGER.error( 193 "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.", 194 e); 195 return null; 196 } 197 198 WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName); 199 200 return new MongoDbProvider(database, writeConcern, collectionName, description); 201 } 202 203 private static WriteConcern toWriteConcern(final String writeConcernConstant, 204 final String writeConcernConstantClassName) { 205 WriteConcern writeConcern; 206 if (Strings.isNotEmpty(writeConcernConstant)) { 207 if (Strings.isNotEmpty(writeConcernConstantClassName)) { 208 try { 209 final Class<?> writeConcernConstantClass = Loader.loadClass(writeConcernConstantClassName); 210 final Field field = writeConcernConstantClass.getField(writeConcernConstant); 211 writeConcern = (WriteConcern) field.get(null); 212 } catch (final Exception e) { 213 LOGGER.error("Write concern constant [{}.{}] not found, using default.", 214 writeConcernConstantClassName, writeConcernConstant); 215 writeConcern = DEFAULT_WRITE_CONCERN; 216 } 217 } else { 218 writeConcern = WriteConcern.valueOf(writeConcernConstant); 219 if (writeConcern == null) { 220 LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant); 221 writeConcern = DEFAULT_WRITE_CONCERN; 222 } 223 } 224 } else { 225 writeConcern = DEFAULT_WRITE_CONCERN; 226 } 227 return writeConcern; 228 } 229}