代码方式配置Log4j并实现线程级日志管理 第五部分

一 第三方程序不应仅限于会用

  关于日志文件备份的问题,我跟很多人都沟通过,但是结果很遗憾,绝大部分人对于Log4j的理解仅限于常规的使用,网路上能搜索到的资源绝大部分也只是关于配置的介绍。

  其实这样的现象并不少见,略微延展起来就变成了老生常谈的一个问题:对于开源框架的使用,会用即可?我记得刚毕业那会就跟同学争论过,时至今日我依然认为,会用是最起码的要求,而理解其实现的原理是必要的,因为我们无法保证在使用任何第三方程序时,不会出现问题或者特殊的需求,如果我们对于其三方程序的理解仅限于使用上,那么这些问题一旦出现——脑壳疼。

  当下的需求就是一个例子,如果不清楚Log4j是如何进行文件备份的,那么何谈实现更为复杂的需求?这里我不再重复网络上满篇的底层实现逻辑,有兴趣的朋友自行百度,简而言之一句话:文件备份逻辑在Appender的rollOver()方法里。

二 同时按日期和文件大小备份

  这是一个很常规的需求,然而Log4j对于这样的需求似乎很难支持,要么选择RollingFileAppender按文件大小进行备份,要么选择DailyRollingFileAppender按天进行文件备份。

  前几部分我简单的介绍了按代码方式配置Log4j的思路,最后的一部分,让我们来实现这个需求,通过复写来实现日志文件同时按日期和文件大小进行备份。

  实现思路是自己实现一个Appender,并重写rollOver()方法,考虑到按文件大小进行备份的实现,RollingFileAppender已经做到了,那么我们对RollingFileAppender进行一个简单的功能扩展即可。

package com.bubbling;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.helpers.CountingQuietWriter;
import org.apache.log4j.spi.LoggingEvent;

/**
 * 派生自RollingFileAppender,所有具备按文件大小备份的条件; 通过重写其rollOver()方法,补充按日期备份的逻辑,即可实现。
 * 
 * @author 胡楠
 *
 */
public class MyRollingFileAppender extends RollingFileAppender
{
	/**
	 * 按日期备份时的文件名包含的日期样式
	 */
	public static final String strDatePattern = "yyyyMMdd";
	/**
	 * 当前的日期,一旦日期发生变化,则触发对应的备份逻辑
	 */
	private String strDate = new SimpleDateFormat(strDatePattern).format(Calendar.getInstance().getTime());

	/*
	 * 注意:别对这个方法理解有问题,这里仅仅是发生了文件备份后的处理逻辑,不是判断是否进行备份的逻辑
	 */
	public void rollOver()
	{
		File target;
		File file;
		// 如果设置了最大文件备份数量(每日的哟),那么执行以下逻辑
		if (maxBackupIndex > 0)
		{
			file = new File(getBackupFileName(maxBackupIndex));

			if (file.exists())
			{
				file.delete();
			}
			// 这里的逻辑略绕,从当前已备份到的最大序号开始,倒着遍历,看不懂的跟踪调试下就好
			for (int i = maxBackupIndex - 1; i >= 1; i--)
			{
				file = new File(getBackupFileName(i));

				if (file.exists())
				{
					target = new File(getBackupFileName(i + 1));
					file.renameTo(target);
				}
			}

			target = new File(getBackupFileName(1));
			this.closeFile();

			file = new File(fileName);
			file.renameTo(target);
		}
		// 如果没有设置过最大文件备份数量,则给他21个亿
		else
		{
			for (int i = 1; i < Integer.MAX_VALUE; i++)
			{
				target = new File(getBackupFileName(i));

				if (!target.exists())
				{
					this.closeFile();
					file = new File(fileName);
					file.renameTo(target);
					break;
				}
			}
		}
		// 以上仅处理了按文件大小备份,下面处理日期发生变化后的处理
		try
		{
			String strFile = this.getFile();

			if (!(strDate.equals(strFile.substring(strFile.indexOf("-") + 1, strFile.lastIndexOf(".log")))))
			{
				StringBuilder builder = new StringBuilder(strFile.substring(0, strFile.lastIndexOf("-") + 1));
				builder.append(strDate);
				builder.append(".log");
				this.fileName = builder.toString();
			}

			this.setFile(fileName, false, bufferedIO, bufferSize);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	/*
	 * 注意:这里才是判断是否备份的逻辑,依然重写,使日期变更也触发rollOver()方法
	 */
	protected void subAppend(LoggingEvent event)
	{
		String strCurrentDate = new SimpleDateFormat(strDatePattern).format(Calendar.getInstance().getTime());
		// 满足以下所有条件才进行文件备份
		if (fileName != null && ((CountingQuietWriter) qw).getCount() >= maxFileSize || !strDate.equals(strCurrentDate))
		{
			// 如果是因为日期变动导致的备份,需要调整当前时间
			if (!strDate.equals(strCurrentDate))
			{
				strDate = strCurrentDate;
			}

			this.rollOver();
		}

		super.subAppend(event);
	}

	/**
	 * 此方法用于自行改写备份文件名,我喜欢***yyyyMMdd-00001.log这样的格式,看着舒服
	 * 
	 * @param maxBackupIndex
	 * @return
	 */
	private String getBackupFileName(int maxBackupIndex)
	{
		StringBuilder builder = new StringBuilder();
		builder.append(fileName.substring(0, fileName.indexOf(".log")));
		builder.append("-");
		builder.append(String.format("%05d", maxBackupIndex));
		builder.append(".log");
		return builder.toString();
	}
}

三 调整ThreadLogger使用复写的Appender

  实现了自定义的Appender之后,我们把ThreadLogger中创建Logger对象的方法重构下,不再使用原RollingFileAppender对象,而且因为加入了文件大小和文件备份数量等参数,LogUtil也需要调整。

public class ThreadLogger
{
	……
	private String filePath = "";
	private LogTarget logTarget = LogTarget.File;
	private LogLevel logLevel = LogLevel.Debug;
	// 补充新的属性,文件大小
	private String maxFileSize = "1MB";
	// 补充新的属性,文件备份数量
	private int maxBackupIndex = 10;
	……
	private Logger getFileLogger()
	{
		// 初始化一个RollingFileAppender对象,此处调整为自定义的Appender
		MyRollingFileAppender appender = new MyRollingFileAppender();
		// 设置文件备份数量
		appender.setMaxBackupIndex(maxBackupIndex);
		// 设置文件大小
		appender.setMaxFileSize(maxFileSize);
		// 设置Appender对象名,若不设置则运行时报错
		appender.setName(filePath);
		……
	}
	……
}

四 简单测试下

  最后简单验证下,因为没有仔细的完善所有执行逻辑,仅仅为了举个例子,所以执行结果大致符合预期就算是大功告成,各位看官有兴趣可以自行对逻辑进行处理。

package com.bubbling;

import com.bubbling.LogUtil.LogLevel;

public class LogUtilTest
{
	public static void main(String[] args)
	{
		long id = Thread.currentThread().getId();
		LogUtil.setFilePath("D:\\测试\\" + id + ".log");
		LogUtil.setLogLevel(LogLevel.Debug);

		for (int i = 0; i < 5000000; i++)
		{
			LogUtil.debug("测试线程:" + id + "debug输出");
			LogUtil.info("测试线程:" + id + "info输出");
			LogUtil.warn("测试线程:" + id + "warn输出");
			LogUtil.error("测试线程:" + id + "error输出");
		}
	}
}

在这里插入图片描述

五 结语

  从一开始的需求,到逐步的实现,这其中并没有什么特别高深莫测的东西,我始终主张能能最简单的实现,来满足当下的需求即可,直到对性能有了更高的要求,再去仔细分析优化空间。

  但这并不是说下手写代码的时候不需要考虑性能问题,严谨的逻辑(一般就好)是每一个程序员的基础,查阅源码、文档及跟踪调试是基本功。

  不要总是想着复制粘贴,如果你对技能提升有渴望,请多花些心思去思考设计方案,而不是急着拷贝别人的代码。

  程序员应该多花时间在设计上,实现上消耗的时间应该是最少的,代码的编写也不是一步到位的,随着实现的深入,逐步重构,完善代码层级结构,设计模式也就渐渐显现出来,合理的设计也对后期的维护提供了坚实的基础。虽说道理如此简单,但是真的能沉下心来这样做的,怕是十不足一。

猜你喜欢

转载自blog.csdn.net/o983950935/article/details/85202893
今日推荐