最近项目中使用到了ES用来记录日志,用起来非常的方便,基本的记录如下,实现了类似下面的效果。
这个架构不是本人搭建,自己也顺便学了一下。
下面先简单介绍一下使用log4j将日志存储在数据库,不管存储在哪,原理都是一样,只要明白日志的工作机制,就很容易搞懂,实现起来很简单。整体一个思路如下:
1.log4j.properties文件的写入
当然在这个文件里可以直接配置写入日志到MySQL,但是我没有这么做。
# Root logger option
log4j.rootLogger=WARN, stdout, file, db
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = logs/log.log
log4j.appender.file.Append = true
log4j.appender.file.Threshold = ERROR
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n
log4j.appender.db =com.gysoft.demo.DBAppender
需要说明一下,最后一行是处理日志基本信息的类。
2.处理日志信息的核心类
package com.gysoft.demo;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LocationInfo;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/6 14:22
*/
public class DBAppender extends AppenderSkeleton {
LogService logService = new LogService();
Log log = new Log();
@Override
protected void append(LoggingEvent loggingEvent) {
ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation();
if (throwableInformation != null) {
LocationInfo locationInfo = loggingEvent.getLocationInformation();
Throwable throwable = throwableInformation.getThrowable();
System.out.println(loggingEvent.toString());
//记录日志的基本信息
try {
log.setClassName(loggingEvent.getLocationInformation().getClassName());
log.setFileName(loggingEvent.getLocationInformation().getFileName());
log.setLineNumber(loggingEvent.getLocationInformation().getLineNumber());
log.setMethodName(loggingEvent.getLocationInformation().getMethodName());
log.setServerIp(getIp());
log.setLogName(loggingEvent.getLoggerName());
log.setLogLevel(loggingEvent.getLogger().getLevel().toString());
log.setLogThread(loggingEvent.getThreadName());
log.setLogMills(new Date(loggingEvent.getTimeStamp()));
log.setLogMessage(loggingEvent.getMessage().toString());
log.setThrowMessage(throwable.getMessage());
log.setThrowDetailMessage(throwable.toString());
log.setThrowStackTrace(throwable.getStackTrace().toString());
} catch (Exception e) {
errorHandler.error("日志信息封装异常");
}
System.out.println(log.toString());
//logService.saveLog(log);
saveLog(log);
System.out.println("日志保存已经交给了线程池处理,主线程继续执行后面的业务,做到异步的效果");
}
}
private void saveLog(final Log log){
//使用线程保存日志 使用异步的方式实现日志的存储
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
executorService.schedule(new Runnable() {
public void run() {
try {
//增加一个延时,看到异步的效果
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//日志的存储
logService.saveLog(log);
}
},5, TimeUnit.MILLISECONDS);
}
public void close() {
}
public boolean requiresLayout() {
return false;
}
private String getIp() throws UnknownHostException {
try {
InetAddress candidateAddress = null;
// 遍历所有的网络接口
for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
// 在所有的接口下再遍历IP
for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址
if (inetAddr.isSiteLocalAddress()) {
// 如果是site-local地址,就是它了
return inetAddr.getHostAddress();
} else if (candidateAddress == null) {
// site-local类型的地址未被发现,先记录候选地址
candidateAddress = inetAddr;
}
}
}
}
if (candidateAddress != null) {
return candidateAddress.getHostAddress();
}
// 如果没有发现 non-loopback地址.只能用最次选的方案
InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
if (jdkSuppliedAddress == null) {
throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null.");
}
return jdkSuppliedAddress.getHostAddress();
} catch (Exception e) {
UnknownHostException unknownHostException = new UnknownHostException(
"Failed to determine LAN address: " + e);
unknownHostException.initCause(e);
throw unknownHostException;
}
}
}
3. 保存日志的业务层
这里没有引入MySQL,如果自己需要实现,请添加自己的数据库连接,保存logbean对象即可。
package com.gysoft.demo;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/6 14:28
*/
public class LogService {
/**
假装这里有MySQL
* 保存日志
*/
public void saveLog(Log log){
System.out.println("插入日志bean,保存日志到数据库");
}
}
4.封装日志基本信息的bean类
package com.gysoft.demo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @Description 记录的信息
* @Author DJZ-WWS
* @Date 2019/5/6 14:17
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Log {
private String className;
private String fileName;
private String lineNumber;
private String methodName;
private String serverIp;
private String logName;
private String logLevel;
private String logThread;
/**
* 日志时间
*/
private Date logMills;
private String logMessage;
private String throwMessage;
private String throwDetailMessage;
private String throwStackTrace;
}
5.日志的测试类
package com.gysoft.demo;
import org.apache.log4j.Logger;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/6 10:09
*/
public class DemoTest {
private static Logger logger = (Logger) Logger.getLogger(DemoTest.class);
public static void main(String[] args) {
//PropertyConfigurator.configure("log4j.properties");
int a=1;
try {
if(a==1){
throw new IllegalStateException("异常异常了");
}
} catch (Exception e) {
logger.error("测试异常链的捕获 error", e);
}
logger.error("Demoe Test ----error");
logger.info("DemoTest----info");
logger.info("DemoTest----debugger");
logger.warn("Demotest -----warn");
logger.trace("DemoTest----trace");
logger.fatal("DemoTest -----fatal");
}
}
6.日志打印结果
16:39:21,917 ERROR DemoTest:28 - 测试异常链的捕获 error
java.lang.IllegalStateException: 异常异常了
at com.gysoft.demo.DemoTest.main(DemoTest.java:25)
org.apache.log4j.spi.LoggingEvent@1d56ce6a
log4j:ERROR 日志信息封装异常
Log(className=com.gysoft.demo.DemoTest, fileName=DemoTest.java, lineNumber=28, methodName=main, serverIp=192.168.3.227, logName=com.gysoft.demo.DemoTest, logLevel=null, logThread=null, logMills=null, logMessage=null, throwMessage=null, throwDetailMessage=null, throwStackTrace=null)
插入日志bean,保存日志到数据库
16:39:21,986 ERROR DemoTest:32 - Demoe Test ----error
16:39:21,986 WARN DemoTest:37 - Demotest -----warn
16:39:21,986 FATAL DemoTest:39 - DemoTest -----fatal
7.补充pom依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
总结:其实很简单,就是日志信息的总结起来就三个步骤 捕获,封装,存储。对于存储的位置就需要我们自己选择了,推荐使用Es。如果需要存储到es将对象进行json处理即可,存储到es,再通过es查询,就可以达到最开始我们需要的那种结果。
当然你如果接的log4j不好用的话你也可以使用logback,或者log4j2原理都是一样的。
这里还没有更新整合spring,rabbitmq保存日志相关东西,后续会慢慢更新