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.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.Core; 026import org.apache.logging.log4j.core.config.plugins.Plugin; 027import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 028import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 029import org.apache.logging.log4j.core.config.plugins.PluginFactory; 030import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; 031import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 032import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost; 033import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; 034import org.apache.logging.log4j.core.filter.AbstractFilterable; 035import org.apache.logging.log4j.core.util.NameUtil; 036import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider; 037import org.apache.logging.log4j.status.StatusLogger; 038import org.apache.logging.log4j.util.LoaderUtil; 039import org.apache.logging.log4j.util.Strings; 040 041import com.mongodb.DB; 042import com.mongodb.MongoClient; 043import com.mongodb.MongoCredential; 044import com.mongodb.ServerAddress; 045import com.mongodb.WriteConcern; 046 047/** 048 * The MongoDB implementation of {@link NoSqlProvider}. 049 */ 050@Plugin(name = "MongoDb", category = Core.CATEGORY_NAME, printObject = true) 051public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> { 052 053 private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED; 054 private static final Logger LOGGER = StatusLogger.getLogger(); 055 private static final int DEFAULT_PORT = 27017; 056 private static final int DEFAULT_COLLECTION_SIZE = 536870912; 057 058 private final String collectionName; 059 private final DB database; 060 private final String description; 061 private final WriteConcern writeConcern; 062 private final boolean isCapped; 063 private final Integer collectionSize; 064 065 private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName, 066 final boolean isCapped, final Integer collectionSize, final String description) { 067 this.database = database; 068 this.writeConcern = writeConcern; 069 this.collectionName = collectionName; 070 this.isCapped = isCapped; 071 this.collectionSize = collectionSize; 072 this.description = "mongoDb{ " + description + " }"; 073 } 074 075 @Override 076 public MongoDbConnection getConnection() { 077 return new MongoDbConnection(this.database, this.writeConcern, this.collectionName, this.isCapped, this.collectionSize); 078 } 079 080 @Override 081 public String toString() { 082 return this.description; 083 } 084 085 /** 086 * Factory method for creating a MongoDB provider within the plugin manager. 087 * 088 * @param collectionName The name of the MongoDB collection to which log events should be written. 089 * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to 090 * {@link WriteConcern#ACKNOWLEDGED}. 091 * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern 092 * constant. Defaults to {@link WriteConcern}. 093 * @param databaseName The name of the MongoDB database containing the collection to which log events should be 094 * written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}. 095 * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with 096 * {@code factoryClassName&factoryMethodName!=null}. 097 * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually 098 * exclusive with {@code factoryClassName&factoryMethodName!=null}. 099 * @param userName The username to authenticate against the MongoDB server with. 100 * @param password The password to authenticate against the MongoDB server with. 101 * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a 102 * {@link DB} or a {@link MongoClient}. 103 * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory 104 * class. 105 * @return a new MongoDB provider. 106 * @deprecated in 2.8; use {@link #newBuilder()} instead. 107 */ 108 @PluginFactory 109 public static MongoDbProvider createNoSqlProvider( 110 final String collectionName, 111 final String writeConcernConstant, 112 final String writeConcernConstantClassName, 113 final String databaseName, 114 final String server, 115 final String port, 116 final String userName, 117 final String password, 118 final String factoryClassName, 119 final String factoryMethodName) { 120 LOGGER.info("createNoSqlProvider"); 121 return newBuilder().setCollectionName(collectionName).setWriteConcernConstant(writeConcernConstantClassName) 122 .setWriteConcernConstant(writeConcernConstant).setDatabaseName(databaseName).setServer(server) 123 .setPort(port).setUserName(userName).setPassword(password).setFactoryClassName(factoryClassName) 124 .setFactoryMethodName(factoryMethodName).build(); 125 } 126 127 @PluginBuilderFactory 128 public static <B extends Builder<B>> B newBuilder() { 129 return new Builder<B>().asBuilder(); 130 } 131 132 public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B> 133 implements org.apache.logging.log4j.core.util.Builder<MongoDbProvider> { 134 135 @PluginBuilderAttribute 136 @ValidHost 137 private String server = "localhost"; 138 139 @PluginBuilderAttribute 140 @ValidPort 141 private String port = "" + DEFAULT_PORT; 142 143 @PluginBuilderAttribute 144 @Required(message = "No database name provided") 145 private String databaseName; 146 147 @PluginBuilderAttribute 148 @Required(message = "No collection name provided") 149 private String collectionName; 150 151 @PluginBuilderAttribute 152 private String userName; 153 154 @PluginBuilderAttribute(sensitive = true) 155 private String password; 156 157 @PluginBuilderAttribute("capped") 158 private boolean isCapped = false; 159 160 @PluginBuilderAttribute 161 private int collectionSize = DEFAULT_COLLECTION_SIZE; 162 163 @PluginBuilderAttribute 164 private String factoryClassName; 165 166 @PluginBuilderAttribute 167 private String factoryMethodName; 168 169 @PluginBuilderAttribute 170 private String writeConcernConstantClassName; 171 172 @PluginBuilderAttribute 173 private String writeConcernConstant; 174 175 public B setServer(final String server) { 176 this.server = server; 177 return asBuilder(); 178 } 179 180 public B setPort(final String port) { 181 this.port = port; 182 return asBuilder(); 183 } 184 185 public B setDatabaseName(final String databaseName) { 186 this.databaseName = databaseName; 187 return asBuilder(); 188 } 189 190 public B setCollectionName(final String collectionName) { 191 this.collectionName = collectionName; 192 return asBuilder(); 193 } 194 195 public B setUserName(final String userName) { 196 this.userName = userName; 197 return asBuilder(); 198 } 199 200 public B setPassword(final String password) { 201 this.password = password; 202 return asBuilder(); 203 } 204 205 public B setCapped(final boolean isCapped) { 206 this.isCapped = isCapped; 207 return asBuilder(); 208 } 209 210 public B setCollectionSize(final int collectionSize) { 211 this.collectionSize = collectionSize; 212 return asBuilder(); 213 } 214 215 public B setFactoryClassName(final String factoryClassName) { 216 this.factoryClassName = factoryClassName; 217 return asBuilder(); 218 } 219 220 public B setFactoryMethodName(final String factoryMethodName) { 221 this.factoryMethodName = factoryMethodName; 222 return asBuilder(); 223 } 224 225 public B setWriteConcernConstantClassName(final String writeConcernConstantClassName) { 226 this.writeConcernConstantClassName = writeConcernConstantClassName; 227 return asBuilder(); 228 } 229 230 public B setWriteConcernConstant(final String writeConcernConstant) { 231 this.writeConcernConstant = writeConcernConstant; 232 return asBuilder(); 233 } 234 235 @Override 236 public MongoDbProvider build() { 237 DB database; 238 String description; 239 if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) { 240 try { 241 final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName); 242 final Method method = factoryClass.getMethod(factoryMethodName); 243 final Object object = method.invoke(null); 244 245 if (object instanceof DB) { 246 database = (DB) object; 247 } else if (object instanceof MongoClient) { 248 if (Strings.isNotEmpty(databaseName)) { 249 database = ((MongoClient) object).getDB(databaseName); 250 } else { 251 LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is " 252 + "required.", factoryClassName, factoryMethodName); 253 return null; 254 } 255 } else if (object == null) { 256 LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName); 257 return null; 258 } else { 259 LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName, 260 factoryMethodName, object.getClass().getName()); 261 return null; 262 } 263 264 description = "database=" + database.getName(); 265 final List<ServerAddress> addresses = database.getMongo().getAllAddress(); 266 if (addresses.size() == 1) { 267 description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort(); 268 } else { 269 description += ", servers=["; 270 for (final ServerAddress address : addresses) { 271 description += " { " + address.getHost() + ", " + address.getPort() + " } "; 272 } 273 description += "]"; 274 } 275 } catch (final ClassNotFoundException e) { 276 LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e); 277 return null; 278 } catch (final NoSuchMethodException e) { 279 LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName, 280 factoryMethodName, e); 281 return null; 282 } catch (final Exception e) { 283 LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName, 284 e); 285 return null; 286 } 287 } else if (Strings.isNotEmpty(databaseName)) { 288 final List<MongoCredential> credentials = new ArrayList<>(); 289 description = "database=" + databaseName; 290 if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) { 291 description += ", username=" + userName + ", passwordHash=" 292 + NameUtil.md5(password + MongoDbProvider.class.getName()); 293 credentials.add(MongoCredential.createCredential(userName, databaseName, password.toCharArray())); 294 } 295 try { 296 final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT); 297 description += ", server=" + server + ", port=" + portInt; 298 database = new MongoClient(new ServerAddress(server, portInt), credentials).getDB(databaseName); 299 } catch (final Exception e) { 300 LOGGER.error( 301 "Failed to obtain a database instance from the MongoClient at server [{}] and " + "port [{}].", 302 server, port); 303 return null; 304 } 305 } else { 306 LOGGER.error("No factory method was provided so the database name is required."); 307 return null; 308 } 309 310 try { 311 database.getCollectionNames(); // Check if the database actually requires authentication 312 } catch (final Exception e) { 313 LOGGER.error( 314 "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.", 315 e); 316 return null; 317 } 318 319 final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName); 320 321 return new MongoDbProvider(database, writeConcern, collectionName, isCapped, collectionSize, description); 322 } 323 324 private static WriteConcern toWriteConcern(final String writeConcernConstant, 325 final String writeConcernConstantClassName) { 326 WriteConcern writeConcern; 327 if (Strings.isNotEmpty(writeConcernConstant)) { 328 if (Strings.isNotEmpty(writeConcernConstantClassName)) { 329 try { 330 final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName); 331 final Field field = writeConcernConstantClass.getField(writeConcernConstant); 332 writeConcern = (WriteConcern) field.get(null); 333 } catch (final Exception e) { 334 LOGGER.error("Write concern constant [{}.{}] not found, using default.", 335 writeConcernConstantClassName, writeConcernConstant); 336 writeConcern = DEFAULT_WRITE_CONCERN; 337 } 338 } else { 339 writeConcern = WriteConcern.valueOf(writeConcernConstant); 340 if (writeConcern == null) { 341 LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant); 342 writeConcern = DEFAULT_WRITE_CONCERN; 343 } 344 } 345 } else { 346 writeConcern = DEFAULT_WRITE_CONCERN; 347 } 348 return writeConcern; 349 } 350 } 351}