扩展log4j系列[二]为DailyRollingFileAppender加上maxBackupIndex属性
在log4j的大多数appender中,都有maxBackupIndex属性,但是这个DailyRollingFileAppender没有,也就是说它会每天滚一个文件,却没有办法控制文件总个数。这绝对是系统的一个“着火点”,下面就开始动手改造了:
一。研究整个log4j的appender结构:
对框架的一个模块进行扩展,并非总是直接继承某个类就好了,如果不进一步深入研究就有可能掉入某些陷阱。(比如扩展log4j的Logger类,直接继承它并不能得到任何好处,具体解释清参考官方文档。),还好log4j对level,appender,layerout都扩展有很好支持的。
然后就是看log4j的配置文件了。 配置文件是可以直接配置扩展appender属性的,这样就替我们节省了一堆定义、解析、处理的过程
- <SPAN style="COLOR: #ff0000"># 给自己的类取个对应的名</SPAN>
- log4j.appender.appenderName=fully.qualified.name.of.appender.class
- <SPAN style="COLOR: #ff0000">#还可以给自己的类property设置值,也就是说扩展的maxBackupIndex属性可以配置</SPAN>
- log4j.appender.appenderName.option1=value1
- ...
- log4j.appender.appenderName.optionN=valueN
# 给自己的类取个对应的名log4j.appender.appenderName=fully.qualified.name.of.appender.class #还可以给自己的类property设置值,也就是说扩展的maxBackupIndex属性可以配置log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN
二。大致胸有成竹后,可以开始看DailyRollingFileAppender的源码了。
直接看属性跟方法结构
大致可以猜出这个类做了如下几个事情:继承了根类appender、支持DatePattern解析并针对DatePattern设置的滚动条件组装filename、实现“监听”方法,到时间点切换logfile。。。 大部分的工作都给我们做好了:)
现在唯一需要改动的就是,“切换文件”方法,在切换新文件的同时,删除掉最老的n个log。
- /**
- Rollover the current file to a new file.
- */
- void rollOver() throws IOException {
- /* Compute filename, but only if datePattern is specified */
- if (datePattern == null) {
- errorHandler.error("Missing DatePattern option in rollOver().");
- return;
- }
- String datedFilename = fileName+sdf.format(now);
- // It is too early to roll over because we are still within the
- // bounds of the current interval. Rollover will occur once the
- // next interval is reached.
- if (scheduledFilename.equals(datedFilename)) {
- return;
- }
- // close current file, and rename it to datedFilename
- this.closeFile();
- File target = new File(scheduledFilename);
- if (target.exists()) {
- target.delete();
- }
- 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+"].");
- }
- 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;
- }
/**Rollover the current file to a new file.*/void rollOver() throws IOException {/* Compute filename, but only if datePattern is specified */if (datePattern == null) {errorHandler.error("Missing DatePattern option in rollOver().");return;}String datedFilename = fileName+sdf.format(now);// It is too early to roll over because we are still within the// bounds of the current interval. Rollover will occur once the// next interval is reached.if (scheduledFilename.equals(datedFilename)) {return;}// close current file, and rename it to datedFilenamethis.closeFile();File target = new File(scheduledFilename);if (target.exists()) {target.delete();}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+"].");}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;}
看到这里就发现问题了,由于DatePattern格式可配置,那么产生的滚动的文件名也是不同的,也没有什么规律可循。
比如".yyyy-ww",是按周滚动,当配置改成".yyyy-MM "按月滚动之后,通过文件名匹配删除旧文件将会导致错误。
另外,日志文件的切换不是定时轮询而是事件促发机制,只有在进行写操作的时候才会去判断是否需要滚动文件!那么写操作在跨过一个滚动周期执行的时候,文件名会产生空缺而不保证连续性。
也许这就是log4j本身没有对这个appender做文件个数限制的原因吧。
三。妥协吧。
框架的功能总是尽量强大的,但使用总是最简单的功能!在IDC环境中通常是不允许按时间滚动记log的,主要是防止日志文件撑爆硬盘成为着火点。 这里考虑启用按时间滚动,主要是性能日志的统计脚本需要日志文件以日期为名按天存储,并且只需要备份前一天的即可.
那么我的需求就简单了:简化功能!
仿造DailyRollingFileAppender实现1.仅支持按天滚动的 、2.格式写死的DatePattern ,3.最大备份文件个数为n的appender 。(备份数可配考虑灵活性,但一定要有参数检查预防万一!)
限制datepattern,一方面可以防止配错,弄成按月滚动肯定死翘翘;另一方面也容易处理MaxBackupIndex删除历史文件。 more,既然知道是按天滚动,check的方法当然可以简化了:
最终修改版的按天滚动appender如下:
- package cxxxxxxxj;
- 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;
- import org.apache.log4j.FileAppender;
- import org.apache.log4j.Layout;
- import org.apache.log4j.helpers.LogLog;
- import org.apache.log4j.spi.LoggingEvent;
- /**
- * 扩展的一个按天滚动的appender类
- * 暂时不支持datePattern设置,但是可以配置maxBackupIndex
- * @author weisong
- *
- */
- public class DayRollingFileAppender extends FileAppender {
- /**不允许改写的datepattern */
- private 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 DayRollingFileAppender() {
- }
- /**
- 改造过的构造器
- */
- public DayRollingFileAppender (Layout layout, String filename,
- int maxBackupIndex) throws IOException {
- super(layout, filename, true);
- this.maxBackupIndex = maxBackupIndex;
- activateOptions();
- }
- /**
- * 初始化本Appender对象的时候调用一次
- */
- 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;
- }
- }
- /**
- 滚动文件的函数:
- 1.对文件名带的时间戳进行比较,确定是否更新
- 2.if需要更新,当前文件rename到文件名+日期, 重新开始写文件
- 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()) {
- target.delete();
- }
- 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. 这个方法是写操作真正的执行过程!
- * */
- 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;
- }
- // public static void main(String[] args) {
- // DayRollingFileAppender da = new DayRollingFileAppender();
- // da.setMaxBackupIndex(2);
- // da.sdf = new SimpleDateFormat(da.getDatePattern());
- // System.out.println(da.getMaxBackupIndexDates());
- //
- // File f = new File("e:/log/b2c/perf.log");
- // System.out.println("f.name=" + f.getName());
- // File p = new File(f.getParent());
- // for(File ff : p.listFiles()) {
- // System.out.println(ff);
- // }
- // }
- }
转载于:https://www.cnblogs.com/cuker919/archive/2010/01/26/4878635.html
扩展log4j系列[二]为DailyRollingFileAppender加上maxBackupIndex属性相关推荐
- opencv python教程简书_OpenCV-Python系列二:常用的图像属性
对于图像,我们经常需要知道关于图像的特殊属性,比如宽度,高度,面积,像素点数目等等,那么在opencv-python中,这些信息如何获取呢? 本文结构: 1.基本图像属性 2. 对于opencv中的特 ...
- 微服务架构系列二:密码强度评测的实现与实验
本文是继<微服务架构系列一:关键技术与原理研究>的后续,系列一中论述了微服务研究的背景和意义,主要调研了传统架构的发展以及存在的问题和微服务架构的由来,然后针对微服务架构的设计原则.容器技 ...
- [知识库分享系列] 二、.NET(ASP.NET)
最近时间又有了新的想法,当我用新的眼光在整理一些很老的知识库时,发现很多东西都已经过时,或者是很基础很零碎的知识点.如果分享出去大家不看倒好,更担心的是会误人子弟,但为了保证此系列的完整,还是选择分享 ...
- 数据与广告系列二十六:知识迁移的Embedding应用,智能化定向的解药
作者·黄崇远 『数据虫巢』 全文共4348字 题图ssyer.com " 效果广告后定向时代,虽有千般万难,我们一样还得解决定向智能的问题." 需要提前说明的是,这一篇会涉及到本系 ...
- 数据与广告系列二十二:智能化投放中扩量场景的技术实现策略
作者·黄崇远 公众号 『数据虫巢』 全文8968字 题图ssyer.com " 人不够,系统给你凑." 这个系列上一篇<数据与广告系列二十一:关于品牌广告,奔涌吧后浪> ...
- 搜索引擎ElasticSearchV5.4.2系列二之ElasticSearchV5.4.2+kibanaV5.4.2+x-packV5.4.2安装
相关博文: 搜索引擎ElasticSearchV5.4.2系列一之ES介绍 搜索引擎ElasticSearchV5.4.2系列二之ElasticSearchV5.4.2+klanaV5.4.2+x-p ...
- SonarQube系列二、分析dotnet core/C#代码
来源:https://www.cnblogs.com/7tiny/p/11342902.html [前言] 本系列主要讲述sonarqube的安装部署以及如何集成jenkins自动化分析.netcor ...
- JAVA面试常考系列二
转载自 JAVA面试常考系列二 题目一 解释一下线程和进程 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调 ...
- xen虚拟化实战系列(二)之xen虚拟机安装
xen虚拟化实战系列文章列表 xen虚拟化实战系列(一)之xen虚拟化环境安装 xen虚拟化实战系列(二)之xen虚拟机安装 xen虚拟化实战系列(三)之xen虚拟机复制 xen虚拟化实战系列(四)之 ...
最新文章
- sort,uniq,fmt,wc,提取开头和结尾的命令的使用
- 国学大师文怀沙郑州演讲 称骂河南就是骂娘
- [Python人工智能] 一.TensorFlow环境搭建及神经网络入门
- 2019ICPC(沈阳) - Fish eating fruit(树形dp+树根转移)
- Ajax Toolkit AutoComplete 几种用法
- 杭州人有福了!菜鸟配送升级新增24小时送药服务
- Python内置数学模块全整理,易查易阅
- window cmd 命令大全 (order)
- 适配新路由3(D2)的LEDE/OpenWrt固件
- Linux 命令(179)—— nice 命令
- 解決一例Win7 X64 SP1系統0x00000124藍屏的故障
- 【转帖】Photoshop为长腿MM制作拼接效果的照片
- 编程中的命名方式和常用命名名称
- 为什么你看书记不住,记住不会用?-我的技术学习方法论
- 【求助】 C++如何在堆区创建 string 数组并且访问,o(╥﹏╥)o
- 注解详解和Spring注解增强(基础内功)
- Program Files (x86)\sogoupinyin\Components\程序异常结束
- 在微信小程序里引入Vant Weapp组件库详细步骤
- joplin的安装和使用
- 解析las文件_Java读取解析激光点云数据(Las/Laz)
热门文章
- 怎么查看任天堂账号是哪个服务器的,科普:任天堂账号和NS的本地用户有什么区别?...
- android 动态画直线,Android使用自定义view在指定时间内匀速画一条直线的实例代码...
- 牛客网 java刷题_牛客网刷题(纯java题型 1~30题)
- 数据科学入门与实战:玩转pandas之二
- python访问excel的类_Python实现的Excel文件读写类
- linux判断usb进程命令,一种在Linux系统下审计USB设备历史使用情况的方法与流程...
- QT使用插件开发界面
- Spring Cloud Sleuth + Zipkin + RabbitMQ +MySQL(三)
- java string == 比较,Java 基础 之 String 的比较
- STC15W408读取HX711称重数据串口发送