此次对于项目中的日志打印做了一些了解及线上系统的应用。
项目中用到的日志打印的工具是org.apache.log4j中的工具。此次主要用到的是三个:
org.apache.log4j.ConsoleAppender(控制台)
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
在小项目中,日志按天拆分就够了,此时我就没有自己去重写日志打印工具了,直接利用的org.apache.log4j.DailyRollingFileAppender来生成,其中配置如下:
# log4j配置 info级别,控制台输出,文件输出
log4j.rootCategory=INFO,stdout,dailyRollingFile
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %t %5p %c{1}:%L - %m%n
# 文件输出
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.file=./logs/channelserver.log
log4j.appender.dailyRollingFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %t %5p %c{1}:%L - %m%n
log4j.appender.dailyRollingFile.Append=true
最终日志文件的形式为:
这种形式拆分日志时,没有对日志文件进行压缩,所以日志文件占用的总内存和不拆分的时候是一样的,只是在去服务器找日志时方便一点,避免日志文件过大,不易操作。
下面介绍自定义的日志打印拆分及压缩:
我们去查看DailyRollingFileAppender、RollingFileAppender都是继承自FileAppender,那么我们自定义一个类,继承FileAppender,然后实现我们自己的日志打印拆分压缩的逻辑。其中DailyRollingFileAppender中已经实现了日志按日期拆分的逻辑,RollingFileAppender实现了按大小拆分的逻辑,此时我主要用到的是DailyRollingFileAppender,然后由于DailyRollingFileAppender中缺少按大小拆分的逻辑及压缩,那么此时我们将RollingFileAppender中按大小拆分的逻辑引用过来,那么此时我们就还缺少一个压缩的逻辑,此时压缩的逻辑我们可以自己编写,同时也可以将日志文件名称重新规范一下,最终代码如下:
package me.config;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.FileAppender;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.CountingQuietWriter;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.spi.LoggingEvent;
/**
* 类描述:日志文件生成类
* 1、根据日期拆分
* 2、根据大小拆分
* 3、拆分后的日志压缩
*
* @Author:wangjinhui
* @date:2018年12月03日
* @Version:1.1.0
*/
public class TestDailyRollingFileAppender extends FileAppender {
static final int TOP_OF_TROUBLE = -1;
static final int TOP_OF_MINUTE = 0;
static final int TOP_OF_HOUR = 1;
static final int HALF_DAY = 2;
static final int TOP_OF_DAY = 3;
static final int TOP_OF_WEEK = 4;
static final int TOP_OF_MONTH = 5;
private String datePattern = "'.'yyyy-MM-dd";
private String scheduledFilename;//上一次生成的文件名称
private long nextCheck = System.currentTimeMillis() - 1L;//下一次的校验时间
Date now = new Date();
SimpleDateFormat sdf;
TestRollingCalendar rc = new TestRollingCalendar();
int checkPeriod = -1;
static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
protected long maxFileSize = 10485760L;//文件的最大大小
private long nextRollover = 0L;//下一次处理文件压缩的大小
private int fileIndex = 0;//生成的文件的下标
private String fileNamePrefix;//文件名称前缀
private String fileNameSuffix;//文件名称后缀
public TestDailyRollingFileAppender() {
}
public TestDailyRollingFileAppender(Layout layout, String filename, String datePattern) throws IOException {
super(layout, filename, true);
this.datePattern = datePattern;
this.activateOptions();
}
public void setDatePattern(String pattern) {
this.datePattern = pattern;
}
public String getDatePattern() {
return this.datePattern;
}
public void activateOptions() {
super.activateOptions();
if (this.datePattern != null && this.fileName != null) {
this.now.setTime(System.currentTimeMillis());
this.sdf = new SimpleDateFormat(this.datePattern);
int type = this.computeCheckPeriod();
this.printPeriodicity(type);
this.rc.setType(type);
File file = new File(this.fileName);
// this.scheduledFilename = this.fileNamePrefix + this.sdf.format(new Date(file.lastModified())) + "_" + fileIndex + this.fileNameSuffix;
//给参数fileNamePrefix与fileNameSuffix赋值初始值
if (StringUtils.isEmpty(this.fileNamePrefix) || StringUtils.isEmpty(this.fileNameSuffix)) {
this.setFileNamePrefix(this.fileName);
this.setFileNameSuffix(this.fileName);
}
this.scheduledFilename = initScheduleFilename(this.sdf.format(new Date(file.lastModified())));
} else {
LogLog.error("Either File or DatePattern options are not set for appender [" + this.name + "].");
}
}
/**
* 利用递归确定此次在生成文件时的名称
* @param lastModifiedDate
* @return
*/
public String initScheduleFilename(String lastModifiedDate){
scheduledFilename = this.fileNamePrefix + lastModifiedDate + "_" + fileIndex + this.fileNameSuffix;
File target = new File(scheduledFilename + ".zip");
if (target.exists()) {
fileIndex++;
return initScheduleFilename(lastModifiedDate);
} else {
return scheduledFilename;
}
}
void printPeriodicity(int type) {
switch(type) {
case 0:
LogLog.debug("Appender [" + this.name + "] to be rolled every minute.");
break;
case 1:
LogLog.debug("Appender [" + this.name + "] to be rolled on top of every hour.");
break;
case 2:
LogLog.debug("Appender [" + this.name + "] to be rolled at midday and midnight.");
break;
case 3:
LogLog.debug("Appender [" + this.name + "] to be rolled at midnight.");
break;
case 4:
LogLog.debug("Appender [" + this.name + "] to be rolled at start of week.");
break;
case 5:
LogLog.debug("Appender [" + this.name + "] to be rolled at start of every month.");
break;
default:
LogLog.warn("Unknown periodicity for appender [" + this.name + "].");
}
}
int computeCheckPeriod() {
TestRollingCalendar rollingCalendar = new TestRollingCalendar(gmtTimeZone, Locale.getDefault());
Date epoch = new Date(0L);
if (this.datePattern != null) {
for(int i = 0; i <= 5; ++i) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.datePattern);
simpleDateFormat.setTimeZone(gmtTimeZone);
String r0 = simpleDateFormat.format(epoch);
rollingCalendar.setType(i);
Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
String r1 = simpleDateFormat.format(next);
if (r0 != null && r1 != null && !r0.equals(r1)) {
return i;
}
}
}
return -1;
}
/**
* 重新生成文件的方法
* @throws IOException
*/
void rollOver() throws IOException {
if (this.datePattern == null) {
this.errorHandler.error("Missing DatePattern option in rollOver().");
} else {
String datedFilename = this.fileNamePrefix + this.sdf.format(this.now) + "_" + fileIndex + this.fileNameSuffix;
fileIndex++;
if (!this.scheduledFilename.equals(datedFilename)) {
long size = ((CountingQuietWriter)this.qw).getCount();
this.nextRollover = size + this.maxFileSize;
this.closeFile();
File file = new File(this.fileName);
//新建压缩文件
FileInputStream fis = null;
ZipOutputStream out = null;
byte[] buf = new byte[1024];
try {
fis = new FileInputStream(file);
out = new ZipOutputStream(new FileOutputStream(scheduledFilename + ".zip"));
out.putNextEntry(new ZipEntry(file.getPath()));
LogLog.debug(fileName + " -> " + scheduledFilename + ".zip");
int len;
while ((len = fis.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
fis.close();
LogLog.debug(fileName + " -> " + scheduledFilename + ".zip successful!");
} catch (Exception e) {
LogLog.error("Failed to zip [" + this.fileName + "] is error.");
} finally {
if (out != null) {
out.closeEntry();
out.close();
}
if (fis != null)
fis.close();
}
//压缩文件生成后,源文件删除
file.delete();
//新建日志文件
try {
this.setFile(this.fileName, true, this.bufferedIO, this.bufferSize);
this.nextRollover = 0L;
} catch (IOException var6) {
this.errorHandler.error("setFile(" + this.fileName + ", true) call failed.");
}
//重新赋值前一次的文件名称
this.scheduledFilename = datedFilename;
}
}
}
protected void setQWForFiles(Writer writer) {
this.qw = new CountingQuietWriter(writer, this.errorHandler);
}
public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) throws IOException {
super.setFile(fileName, append, this.bufferedIO, this.bufferSize);
if (append) {
File f = new File(fileName);
((CountingQuietWriter)this.qw).setCount(f.length());
}
}
protected void subAppend(LoggingEvent event) {
long n = System.currentTimeMillis();
long size = ((CountingQuietWriter)this.qw).getCount();
if (n >= this.nextCheck) {
this.now.setTime(n);
this.nextCheck = this.rc.getNextCheckMillis(this.now);
try {
this.rollOver();
fileIndex = 0;
} catch (IOException var5) {
if (var5 instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("rollOver() failed.", var5);
}
} else if (size >= this.maxFileSize && size >= this.nextRollover) {
try {
this.rollOver();
} catch (IOException var5) {
if (var5 instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("rollOver() failed.", var5);
}
}
super.subAppend(event);
}
public void setMaxFileSize(String value) {
this.maxFileSize = OptionConverter.toFileSize(value, this.maxFileSize + 1L);
}
public void setFileNamePrefix(String fileNamePrefix) {
this.fileNamePrefix = fileName.substring(0, fileName.lastIndexOf("."));
}
public void setFileNameSuffix(String fileNameSuffix) {
this.fileNameSuffix = fileName.substring(fileName.lastIndexOf("."));
}
}
配置文件如下:
# log4j配置 info级别,控制台输出,文件输出
log4j.rootCategory=INFO,stdout,dailyRollingFile
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %t %5p %c{1}:%L - %m%n
# 文件输出
#log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile=me.config.TestDailyRollingFileAppender
log4j.appender.dailyRollingFile.file=./logs/channelserver.log
log4j.appender.dailyRollingFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %t %5p %c{1}:%L - %m%n
log4j.appender.dailyRollingFile.Append=true
log4j.appender.dailyRollingFile.MaxFileSize=100KB
最终的结果如图:
此处还是有点儿缺陷,就是fileNamePrefix及fileNameSuffix两个参数是通过配置文件log4j.appender.dailyRollingFile.file的值来拆分的,如果命名不规范的话,最终的结果就会出错,所以此处也是可以做成配置的,如同log4j.appender.dailyRollingFile.MaxFileSize参数配置一样去配置即可。