环境
idea 2019.2
springboot版本 2.2.2.RELEASE
阿里云 mongodb
项目结构
maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>log4j2_mongodb_pool_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>log4j2_mongodb_pool_demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--注意spring-boot-starter 去掉自带的logging,下面会配置log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.10.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-nosql</artifactId>
<version>2.9.1</version>
</dependency>
<!--log4j2异步AsyncLogger需要这个依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
# log4j2
# 如果没有指定 log4j2文件 的名字,默认加载 log4j2.xml
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<Configuration status="OFF" monitorInterval="30">
<!--Appender 输出源,可以简单理解为配置要写到哪里怎么写-->
<!--Logger 日志分根器,可以简单理解为配置哪些信息写到哪些append里-->
<Appenders>
<!--建议bufferSize配置,减少频繁写操作。为了方便看到是否有往数据库写成功,我在这里故意设置1表示每来一条记录都刷新往NoSql写 -->
<NoSql name="mongoAppender" bufferSize="100">
<!--写到mongodb数据库名为"log"里的集合名为"log4j2"中,数据库需要先手动创建好,集合不需要手动创建,如果集合"log4j2"不存在,会自动创建-->
<!-- 我的本地mongodb没开启账号验证,不需要验证账号密码权限 -->
<!-- <MongoDb databaseName="log" collectionName="log4j2" server="127.0.0.1" port="27017"/>-->
<!-- 连接远程mongodb,需要校验账号密码,注意账号与数据库的权限问题
(这种写法不能指定设置校验权限的数据库,只有用户拥有要访问的databaseName权限才能正常访问),如果没权限会报错:
The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider-->
<!-- <MongoDb databaseName="log" collectionName="log4j2" server="" port="3717"-->
<!-- username="" password=""/>-->
<!-- 用配置类连接mongodb方式设置连接池(上面2种连接,不需要配置类),可以在factoryMethodName 方法中可以指定校验权限的数据库 -->
<MongoDb databaseName="log" collectionName="log4j2"
factoryClassName="com.example.log4j2_mongodb_pool_demo.config.MongoLog4jAppender"
factoryMethodName="getMongoClient"/>
</NoSql>
<Console name="STDOUT" target="SYSTEM_OUT">
<!--筛选过滤,要打印到当前appender的日志信息 如果满足level的接收,不满足的拒绝-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!--输出格式布局,每个转换说明符以百分号(%)开头,'%'后面的转换字符有如下:-->
<!--
p (level) 日志级别
c(logger) Logger的Name
C (class) Logger调用者的全限定类名 ***
d (date) 日期
highlight 高亮颜色
l (location) 调用位置 ***
L (line) 行号
m (msg/message) 输出的内容
M (methode) 调用方法 ***
maker marker的全限定名
n 输出平台相关的换行符,如'\n' '\r\n'
pid (processId) 进程ID
level (p)日志级别
r JVM启动后经过的微秒
t (tn/thread/threadName) 线程名称
T (tid/threadId) 线程ID
tp (threadPriority) 线程优先级
x (NDC) 线程Context堆栈
-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %l - %msg%n" />
</Console>
</Appenders>
<Loggers>
<!--日志级别level以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--日志信息 优先让 子类Logger匹配 -->
<!--Logger 父子Logger区分,举个粟子 name为"a"的Logger 是 name为"a.b"的父Logger, 而Root的name是"" 所以Root是所有Logger的父Logger-->
<!--Logger name="mongologger"表示捕获日志信息name为mongologger或mongologger.xxx的信息,
子类Logger捕获了日志信息不会再被父Logger捕获,即使子类捕获日志信息由于level不满足不会去打印这条日志信息。-->
<!--注意 Logger 的 additivity 传递性,默认true
当additivity="true"时,表示当Logger捕获到日志信息并且日志信息的level大于等于当前Logger的属性level,
日志信息会打印到该Logger所有的appender中包括它的所有父logger的appender(不会管父logger的级别如何),
所以呢,如果Logger的additivity不设置"false"的话,很有可能会出现重复打印的哦-->
<!--AsyncLogger 是异步的,additivity需要设置为false,否则可能出现OOM,这个异步底层框架是disruptor,
如果没有disruptor3.4及以上依赖包,AsyncLogger日志会打印不出来-->
<AsyncLogger name="http" level="debug" additivity="false">
<!--被当前Logger捕获到的日志信息level大于等于当前Logger的level属性时写入到 mongoAppender 里-->
<!--每个Logger 可以设置多个appender ,如果有多个appender 会写入每个appender里-->
<appender-ref ref="mongoAppender"/>
</AsyncLogger>
<!--Root 的name="" , 是所有其他配置的Logger的父Logger-->
<!--如果 Root的level="DEBUG",而且没有设置子类捕获过滤如"org"之类的日志信息的话,会发现控制台会打印非常多的调试信息-->
<!--解决办法提高Root的level级别,或者设置子类Logger去捕获过滤相关不想要打印的日志信息,注意level级别给低点,并且设置additivity="false",如下粟子-->
<!--
<Logger name="org.apache.ibatis" level="DEBUG" additivity="false">
<AppenderRef ref="STDOUT" />
</Logger>
-->
<Root level="info" >
<appender-ref ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
mongo-pool.properties
#spring.data.mongodb.address=127.0.0.1:27111,127.0.0.1:27112,127.0.0.1:27113
spring.data.mongodb.address=serverIp:port
#spring.data.mongodb.replicaSet=mySet
#database
spring.data.mongodb.database=log
spring.data.mongodb.username=xxx
spring.data.mongodb.password=xxx
#author,校验权限数据库
spring.data.mongodb.authenticationDatabase=admin
# Configure spring.data.mongodbDB Pool
spring.data.mongodb.minConnectionsPerHost=10
spring.data.mongodb.maxConnectionsPerHost=100
spring.data.mongodb.threadsAllowedToBlockForConnectionMultiplier=5
spring.data.mongodb.serverSelectionTimeout=30000
spring.data.mongodb.maxWaitTime=120000
spring.data.mongodb.maxConnectionIdleTime=0
spring.data.mongodb.maxConnectionLifeTime=0
spring.data.mongodb.connectTimeout=10000
spring.data.mongodb.socketTimeout=0
spring.data.mongodb.socketKeepAlive=false
spring.data.mongodb.sslEnabled=false
spring.data.mongodb.sslInvalidHostNameAllowed=false
spring.data.mongodb.alwaysUseMBeans=false
spring.data.mongodb.heartbeatSocketTimeout=20000
spring.data.mongodb.heartbeatConnectTimeout=20000
spring.data.mongodb.minHeartbeatFrequency=500
spring.data.mongodb.heartbeatFrequency=10000
spring.data.mongodb.localThreshold=15
MongoLog4jAppender
package com.example.log4j2_mongodb_pool_demo.config;
import com.example.log4j2_mongodb_pool_demo.utils.PropertyUtil;
import com.mongodb.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @author:
* @create: 2019-12-10 22:14
**/
public class MongoLog4jAppender {
public static List<String> address;
public static String replicaSet;
public static String database;
public static String username;
public static String password;
public static Integer minConnectionsPerHost = 0;
public static Integer maxConnectionsPerHost = 100;
public static Integer threadsAllowedToBlockForConnectionMultiplier = 5;
public static Integer serverSelectionTimeout = 30000;
public static Integer maxWaitTime = 120000;
public static Integer maxConnectionIdleTime = 0;
public static Integer maxConnectionLifeTime = 0;
public static Integer connectTimeout = 10000;
public static Integer socketTimeout = 0;
public static Boolean socketKeepAlive = false;
public static Boolean sslEnabled = false;
public static Boolean sslInvalidHostNameAllowed = false;
public static Boolean alwaysUseMBeans = false;
public static Integer heartbeatConnectTimeout = 20000;
public static Integer heartbeatSocketTimeout = 20000;
public static Integer minHeartbeatFrequency = 500;
public static Integer heartbeatFrequency = 10000;
public static Integer localThreshold = 15;
public static String authenticationDatabase;
static {
Properties prop = PropertyUtil.getConfig("mongo-pool.properties");
String addressValue = PropertyUtil.getPropValue(prop,"spring.data.mongodb.address");
System.out.println("addressValue"+addressValue);
address = new ArrayList<>();
if(addressValue != null && !"".equals(addressValue.trim())){
String[] as = addressValue.split(",");
for (String s: as){
address.add(s);
}
}
authenticationDatabase = PropertyUtil.getPropValue(prop,"spring.data.mongodb.authenticationDatabase");
database = PropertyUtil.getPropValue(prop,"spring.data.mongodb.database");
username = PropertyUtil.getPropValue(prop,"spring.data.mongodb.username");
password = PropertyUtil.getPropValue(prop,"spring.data.mongodb.password");
maxConnectionsPerHost = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.maxConnectionsPerHost"));
minConnectionsPerHost = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.minConnectionsPerHost"));
replicaSet = PropertyUtil.getPropValue(prop,"spring.data.mongodb.replicaSet");
threadsAllowedToBlockForConnectionMultiplier = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.threadsAllowedToBlockForConnectionMultiplier"));
serverSelectionTimeout = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.serverSelectionTimeout"));
maxWaitTime = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.maxWaitTime"));
maxConnectionIdleTime = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.maxConnectionIdleTime"));
maxConnectionLifeTime = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.maxConnectionLifeTime"));
connectTimeout = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.connectTimeout"));
socketTimeout = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.socketTimeout"));
sslEnabled = "true".equals(PropertyUtil.getPropValue(prop,"spring.data.mongodb.sslEnabled"))?true:false;
sslInvalidHostNameAllowed = "true".equals(PropertyUtil.getPropValue(prop,"spring.data.mongodb.sslInvalidHostNameAllowed"))?true:false;
alwaysUseMBeans = "true".equals(PropertyUtil.getPropValue(prop,"spring.data.mongodb.alwaysUseMBeans"))?true:false;
heartbeatFrequency = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.heartbeatFrequency"));
minHeartbeatFrequency = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.minHeartbeatFrequency"));
heartbeatConnectTimeout = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.heartbeatConnectTimeout"));
heartbeatSocketTimeout = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.heartbeatSocketTimeout"));
localThreshold = Integer.parseInt(PropertyUtil.getPropValue(prop,"spring.data.mongodb.localThreshold"));
}
public static MongoClient getMongoClient() {
//本地的mongodb无密码校验无权限校验
if(address.get(0).contains("localhost") || address.get(0).contains("127.0.0.1")){
//mongodb://admin:admin@127.0.0.1/?authSource=admin
MongoClientURI uri = new MongoClientURI("mongodb://localhost:27017/");
return new MongoClient(uri);
}
//线上的mongodb(包括正式 测试服都会密码、权限校验)
// 客户端配置(连接数、副本集群验证)
MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
builder.connectionsPerHost(maxConnectionsPerHost);
builder.minConnectionsPerHost(minConnectionsPerHost);
if (replicaSet != null) {
builder.requiredReplicaSetName(replicaSet);
}
builder.threadsAllowedToBlockForConnectionMultiplier(
threadsAllowedToBlockForConnectionMultiplier );
builder.serverSelectionTimeout(serverSelectionTimeout);
builder.maxWaitTime(maxWaitTime);
builder.maxConnectionIdleTime(maxConnectionIdleTime);
builder.maxConnectionLifeTime(maxConnectionLifeTime);
builder.connectTimeout(connectTimeout);
builder.socketTimeout(socketTimeout);
builder.sslEnabled(sslEnabled);
builder.sslInvalidHostNameAllowed(sslInvalidHostNameAllowed);
builder.alwaysUseMBeans(alwaysUseMBeans);
builder.heartbeatFrequency(heartbeatFrequency);
builder.minHeartbeatFrequency(minHeartbeatFrequency);
builder.heartbeatConnectTimeout(heartbeatConnectTimeout);
builder.heartbeatSocketTimeout(heartbeatSocketTimeout);
builder.localThreshold(localThreshold);
MongoClientOptions mongoClientOptions = builder.build();
// MongoDB地址列表
List<ServerAddress> serverAddresses = new ArrayList<>();
for (String s : address) {
String[] hostAndPort = s.split(":");
String host = hostAndPort[0];
Integer port = Integer.parseInt(hostAndPort[1]);
ServerAddress serverAddress = new ServerAddress(host, port);
serverAddresses.add(serverAddress);
}
//System.out.println("serverAddresses:" + serverAddresses.toString());
// 连接认证
List<MongoCredential> mongoCredentialList = new ArrayList<>();
if (username != null) {
mongoCredentialList.add(MongoCredential.createScramSha1Credential(
username,
authenticationDatabase != null ? authenticationDatabase : database, password.toCharArray()));
}
//System.out.println("mongoCredentialList:" + mongoCredentialList.toString());
// 创建客户端和Factory
MongoClient mongoClient = new MongoClient(serverAddresses,mongoCredentialList, mongoClientOptions);
return mongoClient;
}
}
PropertyUtil
package com.example.log4j2_mongodb_pool_demo.utils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
/**
* @author:
* @create:
**/
public class PropertyUtil {
/**
* 读取 classpath 下 指定的properties配置文件,加载到Properties并返回Properties
* @param name 配置文件名,如:mongo.properties
* @return
*/
public static Properties getConfig(String name){
Properties props=null;
try{
props = new Properties();
InputStream in = PropertyUtil.class.getClassLoader().getResourceAsStream(name);
BufferedReader bf = new BufferedReader(new InputStreamReader(in));
props.load(bf);
in.close();
}catch(Exception ex){
ex.printStackTrace();
}
return props;
}
public static String getPropValue(Properties prop,String key){
if(key == null || "".equals(key.trim())){
return null;
}
String value = prop.getProperty(key);
if(value == null){
return null;
}
value = value.trim();
//判断是否是环境变量配置属性,例如 server.env=${serverEnv:local}
if(value.startsWith("${") && value.endsWith("}") && value.contains(":")){
int indexOfColon = value.indexOf(":");
String envName = value.substring(2,indexOfColon);
//获取系统环境变量 envName 的内容,如果没有找到,则返回defaultValue
String envValue = System.getenv(envName);
if(envValue == null){
//配置的默认值
return value.substring(indexOfColon+1,value.length()-1);
}
return envValue;
}
return value;
}
public static void main(String[] args) {
Properties prop = PropertyUtil.getConfig("mongo-pool.properties");
//
System.out.println(prop.getProperty("spring.data.mongodb.database"));
//建议采用下面这种获取方法,能够处理 环境变量配置属性 例如 server.env=${serverEnv:local}
System.out.println( PropertyUtil.getPropValue( prop , "spring.data.mongodb.database" ) );
}
}
Test
package com.example.log4j2_mongodb_pool_demo.controller;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.UUID;
/**
* @author:
* @create: 2019-12-13 11:42
**/
@RestController
public class Test {
public final static Logger httpLogger = LogManager.getLogger("http");
@RequestMapping("/test")
public String test(){
//ThreadContext 可以设置自定义字段, log4j2+mongodb 会记录固定的字段,如果自己想要添加一些自己的字段,那就可以使用ThreadContext
//更多细节 见官网
HashMap<String,String> contextMap = new HashMap<>();
contextMap.put("time", System.currentTimeMillis()+"");
ThreadContext.putAll(contextMap);
httpLogger.info("hehe");
//用完记得清除,虽然 ThreadContext.putAll 能够覆盖之前设置的 contextMap
ThreadContext.clearMap();
return "ok";
}
}
注:
mongodb 新建个数据库 log
注意: 连接 mongodb 的 账号 密码 需要在admin 数据库中设置相应额权限。上面代码配置中 我指定的校验权限的数据库是admin , 可以设置校验用户权限的数据库为指定的数据库, 不过需要给用户设置在该数据库的权限。
权限如果有问题的话,会连接失败!
我的阿里云 mongodb, 用户权限如下:
权限怎么设置,我这里就不描述了,时间有限。如果是图形客户端一般有设置权限的ui操作,当然肯定可以通过命令操作权限,问下度娘。
demo源码
注:代码中 连接的mongodb 数据库 地址 端口 账号 密码 等信息 需要自己去根据自己的实际情况设置。
链接:https://pan.baidu.com/s/1IV6KJrP0_ZsyyYj0TlRXPw
提取码:p931
下面是我的记录日志 如下:
最后
更多细节 可以参考下官方文档
https://logging.apache.org/log4j/2.x/manual/appenders.html#NoSQLAppender