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