直接使用sl4j的注解接口使用不同的日志工具。
spring - log4j
maven依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
配置
log4j.properties
log4j.rootLogger=DEBUG,console,dailyFile
# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyyMMdd HH:mm:ss} [%p] %c.%M %m%n
# 日志文件按天保存,不删除
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.Threshold=DEBUG
log4j.appender.dailyFile.ImmediateFlush=true
log4j.appender.dailyFile.Append=true
log4j.appender.dailyFile.File=file.log
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=%d{yyyyMMdd HH:mm:ss} [%p] %c.%M %m%n
# 日志文件保存到一个文件,不常用
log4j.appender.logFile=org.apache.log4j.FileAppender
log4j.appender.logFile.Threshold=DEBUG
log4j.appender.logFile.ImmediateFlush=true
log4j.appender.logFile.Append=true
log4j.appender.logFile.File=D:/logs/log.log4j
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.ConversionPattern=%d{yyyyMMdd HH:mm:ss} [%p] %c.%M %m%n
使用
Logger logger = LoggerFactory.getLogger(LogTest.class);
logger.debug("123");
# 效果:
20190622 16:11:42 [DEBUG] LogTest.main 123
自定义append
对于某些不重要的日志,我们可以设置保留最近一周,我们需要自定义一个方法,改进如下:
package util;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @Description
* @Author 田云
* @Date 2019/6/22 16:03
* @Version 1.0
*/
public class CustomLogAppender extends FileAppender {
/** 不允许改写的datepattern */
private static final String datePattern = "'.'yyyy-MM-dd";
/** 最多文件增长个数 */
private int maxBackupIndex = 2;
/** 文件名+上次最后更新时间 */
private String scheduledFilename;
/**
* The next time we estimate a rollover should occur.
*/
private long nextCheck = System.currentTimeMillis() - 1;
Date now = new Date();
SimpleDateFormat sdf;
/**
* The default constructor does nothing.
*/
public CustomLogAppender() {
}
/**
* 改造过的构造器
*/
public CustomLogAppender(Layout layout, String filename, int maxBackupIndex) throws IOException {
super(layout, filename, true);
this.maxBackupIndex = maxBackupIndex;
activateOptions();
}
/** * 初始化本Appender对象的时候调用一次 */
@Override
public void activateOptions() {
super.activateOptions();
if (fileName != null) {
// perf.log now.setTime(System.currentTimeMillis());
sdf = new SimpleDateFormat(datePattern);
File file = new File(fileName);
// 获取最后更新时间拼成的文件名
scheduledFilename = fileName + sdf.format(new Date(file.lastModified()));
} else {
LogLog.error("File is not set for appender [" + name + "].");
} if (maxBackupIndex <= 0) {
LogLog.error("maxBackupIndex reset to default value[2],orignal value is:" + maxBackupIndex);
maxBackupIndex = 2;
}
}
/**
* 滚动文件的函数:<br>
* 1. 对文件名带的时间戳进行比较, 确定是否更新<br>
* 2. if需要更新, 当前文件rename到文件名+日期, 重新开始写文件<br>
* 3. 针对配置的maxBackupIndex,删除过期的文件
*/
void rollOver() throws IOException {
String datedFilename = fileName + sdf.format(now);
// 如果上次写的日期跟当前日期相同,不需要换文件
if (scheduledFilename.equals(datedFilename)) {
return;
}
// close current file, and rename it to datedFilename
this.closeFile();
File target = new File(scheduledFilename);
if (target.exists()) {
try {
target.delete();
} catch (SecurityException e) {
e.printStackTrace();
}
}
File file = new File(fileName);
boolean result = file.renameTo(target);
if (result) {
LogLog.debug(fileName + " -> " + scheduledFilename);
} else {
LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");
}
// 删除过期文件
if (maxBackupIndex > 0) {
File folder = new File(file.getParent());
List<String> maxBackupIndexDates = getMaxBackupIndexDates();
for (File ff : folder.listFiles()) {
// 遍历目录,将日期不在备份范围内的日志删掉
if (ff.getName().startsWith(file.getName()) && !ff.getName().equals(file.getName())) {
// 获取文件名带的日期时间戳
String markedDate = ff.getName().substring( file.getName().length());
if (!maxBackupIndexDates.contains(markedDate)) {
result = ff.delete();
}
if (result) {
LogLog.debug(ff.getName() + " -> deleted ");
} else {
LogLog.error("Failed to deleted old DayRollingFileAppender file :" + ff.getName());
}
}
}
}
try {
// This will also close the file. This is OK since multiple
// close operations are safe.
this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
} catch (IOException e) {
errorHandler.error("setFile(" + fileName + ", false) call failed.");
}
scheduledFilename = datedFilename;
// 更新最后更新日期戳
}
/**
* Actual writing occurs here.
* 这个方法是写操作真正的执行过程.
*/
@Override
protected void subAppend(LoggingEvent event) {
long n = System.currentTimeMillis();
if (n >= nextCheck) {
// 在每次写操作前判断一下是否需要滚动文件
now.setTime(n);
nextCheck = getNextDayCheckPoint(now);
try {
rollOver();
} catch (IOException ioe) {
LogLog.error("rollOver() failed.", ioe);
}
} super.subAppend(event);
}
/**
* 获取下一天的时间变更点
*
* @param now
* @return
*/
long getNextDayCheckPoint(Date now) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);// 注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的 calendar.add(Calendar.DATE, 1);
return calendar.getTimeInMillis();
}
/**
* 根据maxBackupIndex配置的备份文件个数,获取要保留log文件的日期范围集合
* @return list<'fileName+yyyy-MM-dd'>
*/
List<String> getMaxBackupIndexDates() {
List<String> result = new ArrayList<String>();
if (maxBackupIndex > 0) {
for (int i = 1; i <= maxBackupIndex; i++) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
// 注意MILLISECOND,毫秒也要置0...否则错了也找不出来的
calendar.add(Calendar.DATE, -i);
result.add(sdf.format(calendar.getTime()));
}
}
return result;
}
public int getMaxBackupIndex() {
return maxBackupIndex;
}
public void setMaxBackupIndex(int maxBackupIndex) {
this.maxBackupIndex = maxBackupIndex;
}
public String getDatePattern() {
return datePattern;
}
}
自定义的配置
# 按天存储,保存7天(日志文件不需要全部保留的情况)
log4j.appender.customer=util.CustomLogAppender
log4j.appender.customer.File=file.log
log4j.appender.customer.maxBackupIndex=7
log4j.appender.customer.layout=org.apache.log4j.PatternLayout
log4j.appender.customer.layout.ConversionPattern=%d{yyyyMMdd HH:mm:ss} [%p] %c.%M %m%n
springboot - logback
1、引用,web的引用里面已经引入了。
2、配置文件
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback</contextName>
<property name="logback.logdir" value="./log/"/>
<property name="logback.appname" value="java"/>
<!--输出到控制台 ConsoleAppender-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--展示格式 layout-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d [%thread] %-5level %logger{36} %line : %msg%n</pattern>
</layout>
</appender>
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高,
所以我们使用下面的策略,可以避免输出 Error 的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error-->
<level>ERROR</level>
<!--匹配到就禁止-->
<onMatch>DENY</onMatch>
<!--没有匹配到就允许-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
的日志改名为今天的日期。即,<File> 的日志都是当天的。
-->
<File>${logback.logdir}/info.${logback.appname}.log</File>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logdir}/info.${logback.appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line : %msg%n</pattern>
</encoder>
</appender>
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Error</level>
</filter>
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
的日志改名为今天的日期。即,<File> 的日志都是当天的。
-->
<File>${logback.logdir}/error.${logback.appname}.log</File>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logdir}/error.${logback.appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line : %msg%n</pattern>
</encoder>
</appender>
<!--指定最基础的日志输出级别-->
<root level="INFO">
<!--appender将会添加到这个loger-->
<appender-ref ref="console"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</root>
</configuration>
3、使用
@Slf4j
public class LogServiceImpl implements LogService {
public PageResult<LogModel> query(LogModelVO logModelVO) {
log.info("123")
}
}
springboot - log4j2
推荐
引用
<!--log4j2 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--异步日志依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.7</version>
</dependency>
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="WARN" monitorInterval="30">
<properties>
<property name="logDir" value="log"/>
</properties>
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="${logDir}/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingRandomAccessFile name="RollingFileInfo" fileName="${logDir}/info.log"
filePattern="${logDir}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"
immediateFlush="false">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500 MB"/>
</Policies>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="RollingFileWarn" fileName="${logDir}/warn.log"
filePattern="${logDir}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"
immediateFlush="false">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="RollingFileError" fileName="${logDir}/error.log"
filePattern="${logDir}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"
immediateFlush="false">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500 MB"/>
</Policies>
</RollingRandomAccessFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"/>
<logger name="org.mybatis" level="INFO"/>
<root level="all" includeLocation="true">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
启动函数
system.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
然后直接使用sl4j即可。
自定义输出目录
参考:https://giraffetree.me/2019/12/06/log4j2-slf4j-routing/
配置 ```xml <?xml version=”1.0” encoding=”UTF-8”?>
测试
```bash
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import java.util.concurrent.ThreadLocalRandom;
/** * @author GiraffeTree * @date 2019/12/5 */
@Slf4j
public class Log4j2Test {
private final static Marker BOY = MarkerFactory.getMarker("boy");
private final static Marker GIRL = MarkerFactory.getMarker("girl");
private final static Marker OTHER = MarkerFactory.getMarker("other");
public static void main(String[] args) {
testRouting(1000, 3);
}
public static void addRandomLog() {
int num = ThreadLocalRandom.current().nextInt(0, 1000);
if (num < 950) {
ThreadContext.put("logFileName", "David");
log.info("current: {}", num);
} else {
log.error("current: {}", num);
}
}
public static void testRouting(int loopCount, int userSize) {
long l1 = System.currentTimeMillis();
int count = loopCount;
while (count > 0) {
writeMultipleUsersLog(userSize);
count--;
}
long l2 = System.currentTimeMillis();
System.out.println(String.format("size:%d loopCount:%d cost: %dms", userSize, loopCount, l2 - l1));
}
public static void writeMultipleUsersLog(int size) {
for (int i = 0; i < size; i++) {
String userName = "user" + i;
ThreadContext.put("logFileName", userName);
int num = ThreadLocalRandom.current().nextInt(0, 1200);
if (num < 500) {
log.info(BOY, "current: {}", num);
} else if (num < 1000) {
log.info(GIRL, "current: {}", num);
} else {
log.info(OTHER, "current: {}", num);
}
}
}
}