log4j日志框架的设计和实现

合集下载

log4j 漏洞原理

log4j 漏洞原理

log4j 漏洞原理
log4j是一个流行的日志框架,它提供了丰富的日志输出方式和配置选项。

然而,最近爆出了一个严重的漏洞,被命名为
log4shell,它允许攻击者远程执行任意代码,甚至获取系统权限。

该漏洞的原理是利用 log4j 的 JNDI 功能,通过构造特殊的日志消息,在服务器上触发 JNDI 查询并加载远程服务器上的恶意代码。

攻击者可通过控制恶意服务器上的代码,获取系统权限或执行任意命令,造成极大的危害。

受影响的版本包括 log4j 2.0-beta9 至 2.14.1,建议尽快升级至最新版本。

同时,可以通过禁用 JNDI 功能或限制 JNDI 查询的范围等方式,降低风险。

该漏洞已被广泛利用,各大厂商也纷纷发布了安全补丁。

作为开发者和系统管理员,我们需要及时关注并采取相应措施,确保系统安全。

- 1 -。

log4j日志脱敏处理+javaproperties文件加载

log4j日志脱敏处理+javaproperties文件加载

log4j⽇志脱敏处理+javaproperties⽂件加载Java 加载Properties 配置⽂件:ResourceBundle bundle = ResourceBundle.getBundle("log4j_filter"); // 不要写扩展名 .propertiesLOG_FILTER_SWITH = bundle.getString("log4j.filter.swith");LOG_FILTER_KEYS = bundle.getString("log4j.filter.keys"); // 直接在本类中使⽤main调⽤时⽤ Properties.class.getResourceAsStream("/log4j_filter.properties"); //Properties p = new Properties(); //p.load(in); //LOG_FILTER_SWITH = p.getProperty("log4j.filter.swith"); //LOG_FILTER_KEYS = p.getProperty("log4j.filter.keys");⽇志脱敏实现:因为只修改了⼀个log4j类的调⽤,故⽇志必须是private final static Logger logger = LogManager.getLogger(SeqConfControl.class);1 , 配置⽂件 log4j_filter.properties (⼀个参数是脱敏⽇志开关,⼀个参数是要脱敏的关键字)2,⾃定义类: org.apache.logging.log4j.spi.Log4jFilter.java 是解析处理⽇志字符串中的敏感信息;log4j原⽣类: org.apache.logging.log4j.spi.AbstractLogger.java 负责调⽤⾃⼰写的类的⽅法.3,规则⽀持k:v 和 k=v 两种形式的数据处理脱敏时v的长度如果⼤于8,采⽤ XXX****XXX的形式脱敏;如果⼩于等于8采⽤ ******* 形式脱敏效果:20:25:02,0197 [INFO ][ ][connect to SFTP . ip:10.0.26.36; port:22;uppath:null;downpath:home/selfftp/selffile/20161209/;username:selfftp;password:********][ :][hulk.frame.sftp.SFtpTools]21:06:16,0907 [INFO ][ ][接收到⼀个纯⽂本消息并开始传⼊数据库:{"data":"{\"state\":\"0\",\"hostName\":\"termfrontapp\",\"policyName\":\"check_SSPM_101\",\"stateName\":null,\"program\":null,\"entryId\":null,\"agentId\":null,\"msg\":\"{\\\21:06:16,0907 [INFO ][ ][the message to decode: {"account_num":"62294785200****0034","amount":"2.00","channel_id":"01I","p_trans_code":"1017006","card_type":"","start_time":"1481634017288","end_time":"1481634017526","term_id":"01159log4j_filter.properties:# log4j 过滤器配置⽂件,主要⽤于系统中敏感字段的脱敏# true表⽰打开 false表⽰关闭log4j.filter.swith=true# 要脱敏的关键字log4j.filter.keys=password,passwd,password1,password2,account_num⾃定义解析类:org.apache.logging.log4j.spi.Log4jFilter.javapackage org.apache.logging.log4j.spi;import java.util.ResourceBundle;import java.util.regex.Matcher;import java.util.regex.Pattern;/*** ⾃定义处理⽇志中的敏感信息* @author yangw1006@**/public class Log4jFilter {/*** ⽇志脱敏开关*/private static String LOG_FILTER_SWITH = "false";/*** ⽇志脱敏关键字*/private static String LOG_FILTER_KEYS = null;static{// 加载配置⽂件try {// 直接在本类中使⽤main调⽤时⽤ Properties.class.getResourceAsStream("/log4j_filter.properties");//InputStream in =Properties.class.getClassLoader().getResourceAsStream("log4j_filter.properties");//Properties p = new Properties();//p.load(in);//LOG_FILTER_SWITH = p.getProperty("log4j.filter.swith");//LOG_FILTER_KEYS = p.getProperty("log4j.filter.keys");ResourceBundle bundle = ResourceBundle.getBundle("log4j_filter");LOG_FILTER_SWITH = bundle.getString("log4j.filter.swith");LOG_FILTER_KEYS = bundle.getString("log4j.filter.keys");} catch (Exception e) {e.printStackTrace();}}/*** 处理⽇志字符串,返回脱敏后的字符串* @param msg* @return*/public static String invokeMsg(final String message){String msg = new String(message);if("true".equals(LOG_FILTER_SWITH)){//处理字符串if(LOG_FILTER_KEYS!=null && LOG_FILTER_KEYS.length()>0){String[] keyArr = LOG_FILTER_KEYS.split(",");for(String key: keyArr){// 找keyint index= -1;do{index = msg.indexOf(key, index+1);if(index!=-1){// 判断key是否为单词字符if(isWordChar(msg,key,index)){continue;}// 确定是单词⽆疑....................................// 寻找值的开始位置.................................int valueStart = getValueStartIndex(msg,index + key.length());//查找值的结束位置(逗号,分号)........................int valueEnd = getValuEndEIndex(msg,valueStart);// 对获取的值进⾏脱敏String subStr = msg.substring(valueStart, valueEnd);subStr = tuomin(subStr);///////////////////////////msg = msg.substring(0,valueStart) + subStr + msg.substring(valueEnd); }}while(index!=-1);}}}return msg;}private static Pattern pattern = pile("[0-9a-zA-Z]");/*** 判断从字符串msg获取的key值是否为单词, index为key在msg中的索引值* @return*/private static boolean isWordChar(String msg,String key, int index){// 必须确定key是⼀个单词............................if(index!=0){ //判断key前⾯⼀个字符char preCh = msg.charAt(index-1);Matcher match = pattern.matcher(preCh+"");if(match.matches()){return true;}}//判断key后⾯⼀个字符char nextCh = msg.charAt(index+key.length());Matcher match = pattern.matcher(nextCh+"");if(match.matches()){return true;}return false;}/*** 获取value值的开始位置* @param msg 要查找的字符串* @param valueStart 查找的开始位置* @return*/private static int getValueStartIndex(String msg, int valueStart ){// 寻找值的开始位置.................................do{char ch = msg.charAt(valueStart);if(ch == ':' || ch == '='){ // key 与 value的分隔符valueStart ++;ch = msg.charAt(valueStart);if(ch == '"'){valueStart ++;}break; //找到值的开始位置}else{valueStart ++;}}while(true);return valueStart;}/*** 获取value值的结束位置* @return*/private static int getValuEndEIndex(String msg,int valueEnd){do{if(valueEnd == msg.length()){break;}char ch = msg.charAt(valueEnd);if(ch == '"'){ // 引号时,判断下⼀个值是结束,分号还是逗号决定是否为值的结束if(valueEnd+1 == msg.length()){break;}char nextCh = msg.charAt(valueEnd+1);if(nextCh ==';' || nextCh == ','){// 去掉前⾯的 \ 处理这种形式的数据 "account_num\\\":\\\"6230958600001008\\\"while(valueEnd>0 ){char preCh = msg.charAt(valueEnd-1);if(preCh != '\\'){break;}valueEnd--;}break;}else{valueEnd ++;}}else if (ch ==';' || ch == ','){break;}else{valueEnd ++;}}while(true);return valueEnd;}private static String tuomin(String submsg){StringBuffer sbResult = new StringBuffer();if(submsg!=null && submsg.length()>0){int len = submsg.length();if(len > 8){ //8位以上的隐掉中间4位for(int i = len-1;i>=0;i--){if(len-i<5 || len-i>8){sbResult.insert(0, submsg.charAt(i));}else{sbResult.insert(0, '*');}}}else{ //8位以下的全部使⽤ *for(int i =0;i<len;i++){sbResult.append('*');}}}return sbResult.toString();}public static void main (String[] args) {//{\\\"account_num\\\":\\\"6230958600001008\\\",\\\"amount\\\":\\\"\\\"String msg = "\\\"account_num\\\":\\\"6230958600001008\\\",\\\"amount\\\":\\\"\\\"";System.out.println(invokeMsg(msg));}}log4j原⽣类 org.apache.logging.log4j.spi.AbstractLogger.java 修改的地⽅# 代码 725⾏左右的⽅法protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, final Throwable t) { // by yangwString invokeMsg = Log4jFilter.invokeMsg(message);logMessage(fqcn, level, marker, messageFactory.newMessage(invokeMsg), t);}。

RCP程序的日志收集与分析实践

RCP程序的日志收集与分析实践

RCP随着信息技术的不断发展,越来越多的企业和组织依赖于软件系统进行业务运营和管理。

而软件系统的稳定性和可靠性一直是企业和组织的重要关注点。

在这个过程中,日志是非常重要的一环。

软件系统会产生大量的日志,这些日志包含了系统的运行状态、异常信息、性能指标等重要信息。

通过对这些日志的收集和分析,可以及时发现系统故障、优化性能,提高系统的可靠性和稳定性。

本文将介绍RCP 程序的日志收集与分析实践。

一、RCP 程序简介RCP ( Rich Client Platform)是一个基于Eclipse 的框架,用于开发客户端应用程序。

RCP 程序可以在多个操作系统平台上运行,包括Windows、Linux 和Mac OS 等。

RCP 程序的架构分为三层:应用层、中间件层和系统层。

应用层包括用户界面、业务逻辑和数据持久化。

中间件层包括服务注册、服务提供和服务调用等。

系统层包括操作系统和底层接口。

二、RCP 程序的日志收集RCP 程序的日志信息最终会被输出到一个日志文件中。

在调试和问题排查时,需要查看日志文件中的信息。

因此,正确地配置日志输出非常重要。

下面介绍RCP 程序日志收集的实践:1. 使用log4j 作为日志框架:log4j 是一个开源的Java 日志框架,被广泛应用于Java 应用程序的日志记录。

在RCP 程序中,可以通过在插件中集成log4j,并通过配置文件设置日志输出级别和输出目的地。

下面是一个示例的log4j 配置文件:```log4j.rootLogger=INFO,FILElog4j.appender.FILE=org.apache.log4j.RollingFileAppenderlog4j.appender.FILE.File=${workspace_loc}/logs/eclipse.loglog4j.appender.FILE.MaxFileSize=10MBlog4j.appender.FILE.MaxBackupIndex=5yout=org.apache.log4j.PatternLayoutyout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %p [%c] - %m%n```其中,rootLogger 设置日志输出级别为INFO,表示只输出INFO 级别及以上的日志信息;appender.FILE 设置输出目的为一个滚动式日志文件,文件大小不超过10MB,保留5 个备份文件;layout.ConversionPattern 设置日志输出格式。

JAVAlog4j同一个类日志输出到不同的log文件中配置

JAVAlog4j同一个类日志输出到不同的log文件中配置

JAVAlog4j同一个类日志输出到不同的log文件中配置不能通过简单的配置log4j配置文件来实现,而需要在代码中调用Log4j的类来实现。

下面给出实现类:/** * @author QG * * 2010-7-22 上午10:27:50 LoggerRun.java */public class LoggerRun { //设定两个Logpublic static Logger infoLogger = Logger.getLogger("info.logger");public static Logger errorLogger = Logger.getLogger("error.logger");public static final String PROFILE = "log4j.properties";//设定异常log输出的路径private static final String PATH = Constants.PATH;static{try{URL configFileResource = (new File(LoggerRun.class.getResource("/").getPath()+PROFILE)).toUR L();PropertyConfigurator.configure(configFileResource);}catch(Exception e){e.printStackTrace();}}public LoggerRun(){try {Date date = new Date();SimpleDateFormat sf = new SimpleDateFormat("yyyyMMddHHmmss");String fileName = PATH + "exception_" +sf.format(date).toString() + ".log";FileAppender exceptionAppender = new FileAppender(new SimpleLayout(), fileName);errorLogger.addAppender(exceptionAppender);} catch (Exception e) {e.printStackTrace();}}}同时给出配置文件配置信息:.logger=INFO, info,stdoutlog4j.category.error.logger=ERROR,errorlog4j.appender.stdout=org.apache.log4j.ConsoleAppender yout=org.apache.log4j.PatternLayo utyout.ConversionPattern=%d{yyyy/ MM/dd HH:mm:ss.SSS} %-5p %m%=org.apache.log4j.RollingFileAppe nder.File=bin/log/info.log.MaxFileSize=100kb.MaxBackupIndex=4yout=org.apache.log4j.PatternLayout yout.ConversionPattern=%d{yyyy/MM /dd HH:mm:ss.SSS} %-5p %m%nlog4j.appender.error=org.apache.log4j.RollingFileApp enderlog4j.appender.error.File=bin/log/error.loglog4j.appender.error.MaxFileSize=100kblog4j.appender.error.MaxBackupIndex=4yout=org.apache.log4j.PatternLayout yout.ConversionPattern=%d{yyyy/M M/dd HH:mm:ss.SSS} %-5p %m%n在给出测试类:public class TestLog {public static final Logger logger=Logger.getLogger(T estLog.class);/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubfor(int i=0;i<10;i++){if(i<5){("TEST The Logger DUBUG");}else{LoggerRun.errorLogger.error("TEST THe LOGGER ERROR");}}}}同一个类中的日志按类型输出到了不同的日志文件中。

logback日志输出资料

logback日志输出资料

java日志,需要知道的几件事如果对于commons-loging 、log4j 、slf4j 、LogBack 等都已经非常清楚了,可以忽略本文。

几次解决日志冲突问题时对这几个概念的简单总结,希望对这块基础没有理解透的同学能有所帮助,当然如果对这块有更深刻理解的同学,也贡献出自己的知识和见解。

一、概念Commons-logging : apache最早提供的日志的门面接口。

避免和具体的日志方案直接耦合。

类似于JDBC 的api 接口,具体的的JDBC driver 实现由各数据库提供商实现。

通过统一接口解耦,不过其内部也实现了一些简单日志方案。

Log4j : 经典的一种日志解决方案。

内部把日志系统抽象封装成Logger 、appender 、pattern 等实现。

我们可以通过配置文件轻松的实现日志系统的管理和多样化配置。

Slf4j : 全称为Simple Logging Facade for JAVA:java简单日志门面。

是对不同日志框架提供的一个门面封装。

可以在部署的时候不修改任何配置即可接入一种日志实现方案。

和commons-loging 应该有一样的初衷。

个人感觉设从计上更好一些,没有commons 那么多潜规则。

同时有两个额外特点:1. 能支持多个参数,并通过{} 占位符进行替换,避免老写logger.isXXXEnabled 这种无奈的判断,带来性能提升见:/faq.html#logging_performance 。

2.OSGI 机制更好兼容支持一图胜千言,官网上的一个图:从上图可以发现,选择还是很多的。

Logback : LOGBack 作为一个通用可靠、快速灵活的日志框架,将作为Log4j 的替代和SLF4J 组成新的日志系统的完整实现。

官网上称具有极佳的性能,在关键路径上执行速度是log4j 的10 倍,且内存消耗更少。

具体优势见:http://logback.qos.ch/reasonsToSwitch.html二、常见日志方案和注意事项mons-logging+log4j : 经典的一个日志实现方案。

log4j2配置文件log4j2.xml解析

log4j2配置文件log4j2.xml解析

log4j2配置⽂件log4j2.xml解析⼀、背景最近由于项⽬的需要,我们把log4j 1.x的版本全部迁移成log4j 2.x 的版本,那随之⽽来的slf4j整合log4j的配置(使⽤Slf4j集成Log4j2构建项⽬⽇志系统的完美解决⽅案)以及log4j2配置⽂件的详解,就需要我们来好好聊⼀聊了。

本⽂就专门来讲解下log4j2.xml配置⽂件的各项标签的意义。

⼆、配置全解1.关于配置⽂件的名称以及在项⽬中的存放位置log4j 2.x版本不再⽀持像1.x中的.properties后缀的⽂件配置⽅式,2.x版本配置⽂件后缀名只能为”.xml”,”.json”或者”.jsn”.系统选择配置⽂件的优先级(从先到后)如下:(1).classpath下的名为log4j2-test.json 或者log4j2-test.jsn的⽂件.(2).classpath下的名为log4j2-test.xml的⽂件.(3).classpath下名为log4j2.json 或者log4j2.jsn的⽂件.(4).classpath下名为log4j2.xml的⽂件.我们⼀般默认使⽤log4j2.xml进⾏命名。

如果本地要测试,可以把log4j2-test.xml放到classpath,⽽正式环境使⽤log4j2.xml,则在打包部署的时候不要打包log4j2-test.xml即可。

2.缺省默认配置⽂件<?xml version="1.0" encoding="UTF-8"?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/></Console></Appenders><Loggers><Root level="error"><AppenderRef ref="Console"/></Root></Loggers></Configuration>3.配置⽂件节点解析(1).根节点Configuration有两个属性:status和monitorinterval,有两个⼦节点:Appenders和Loggers(表明可以定义多个Appender和Logger). status⽤来指定log4j本⾝的打印⽇志的级别.monitorinterval⽤于指定log4j⾃动重新配置的监测间隔时间,单位是s,最⼩是5s.(2).Appenders节点,常见的有三种⼦节点:Console、RollingFile、File.Console节点⽤来定义输出到控制台的Appender.name:指定Appender的名字.target:SYSTEM_OUT 或 SYSTEM_ERR,⼀般只设置默认:SYSTEM_OUT.PatternLayout:输出格式,不设置默认为:%m%n.File节点⽤来定义输出到指定位置的⽂件的Appender.name:指定Appender的名字.fileName:指定输出⽇志的⽬的⽂件带全路径的⽂件名.PatternLayout:输出格式,不设置默认为:%m%n.RollingFile节点⽤来定义超过指定⼤⼩⾃动删除旧的创建新的的Appender.name:指定Appender的名字.fileName:指定输出⽇志的⽬的⽂件带全路径的⽂件名.PatternLayout:输出格式,不设置默认为:%m%n.filePattern:指定新建⽇志⽂件的名称格式.Policies:指定滚动⽇志的策略,就是什么时候进⾏新建⽇志⽂件输出⽇志.TimeBasedTriggeringPolicy:Policies⼦节点,基于时间的滚动策略,interval属性⽤来指定多久滚动⼀次,默认是1 hour。

log4j2 filepermission的使用

log4j2 filepermission的使用

log4j2 filepermission的使用Log4j2是一个强大的Java日志框架,可以帮助开发人员在应用程序中实现灵活和高效的日志记录。

其中一个重要的功能是文件权限管理,它可以帮助开发人员控制日志文件的访问权限,并保护敏感数据不被非授权人员获取。

文件权限管理在Log4j2中通过使用FilePermissions属性来实现。

FilePermissions属性是一个用于指定文件权限的字符串,它可以设置读、写和执行权限。

下面是一些常用的FilePermissions属性的示例:1. "rw-rw-rw-":所有用户都有读和写的权限,没有执行的权限。

2. "rwxr-x---":所有用户都有读、写和执行的权限,但其他用户没有读、写和执行的权限。

3. "rw-------":只有文件所有者有读和写的权限,其他用户没有任何的权限。

在Log4j2中,可以通过在配置文件中添加<Properties>元素来定义FilePermissions属性的值。

例如:```<Properties><Property name="filePermissions">rw-rw-rw-</Property></Properties>```此外,还可以通过使用系统属性来动态设置FilePermissions属性的值。

例如,可以在程序启动时设置System Property来指定FilePermissions属性的值。

下面是一个设置FilePermissions属性的示例:```javaSystem.setProperty("log4j2.filePermissions", "rw-rw-rw-");```设置FilePermissions属性后,Log4j2会将此属性应用到所有生成的日志文件上。

idea插件开发日志打印

idea插件开发日志打印

idea插件开发日志打印开发日志打印是一种常见的技术实践,它可以帮助开发人员在开发过程中记录关键信息、调试代码以及追踪问题。

在开发 IDEA 插件时,我们可以使用不同的方法来实现日志打印。

下面我将从多个角度来回答这个问题。

首先,我们可以使用 Java 自带的日志框架,如java.util.logging 或 log4j。

这些框架提供了一套丰富的 API,可以用于在插件代码中打印日志。

我们可以通过配置日志级别来控制日志的输出,从而在不同的场景下灵活地记录信息。

使用这些框架,我们可以在插件代码中添加日志语句,并在运行时将日志输出到控制台或者指定的日志文件中。

其次,JetBrains 提供了自己的日志框架,即 JetBrains Logger。

这个框架是专门为开发 IDEA 插件而设计的,它提供了一些特定的 API,可以方便地在插件代码中打印日志。

使用JetBrains Logger,我们可以通过调用`Logger.getInstance(Class)` 方法获取一个 Logger 实例,然后使用该实例的方法来记录日志。

这个框架还支持在插件配置文件中配置日志级别和输出目标。

此外,我们还可以使用 System.out.println 或System.err.println 方法在插件代码中打印日志。

这种方法简单直接,适用于快速调试和验证想法。

但需要注意的是,这种方式输出的日志会直接打印在控制台上,可能会与其他插件或应用程序的输出混合在一起,不够规范和可控。

另外,为了更好地管理和查看日志,我们可以考虑使用专门的日志库或工具。

例如,我们可以使用 Logback 或 Log4j2 这样的日志库,它们提供了更丰富的功能和配置选项。

此外,IDEA 自带了一个 Log Viewer 插件,可以方便地查看和搜索日志文件。

总结来说,在开发 IDEA 插件时,我们可以选择使用 Java 自带的日志框架、JetBrains Logger、System.out.println 或者第三方日志库来实现日志打印。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

1 简介 日志系统是一种不可或缺的跟踪调试工具,特别是在任何无人职守的后台程序以及那些没有跟踪调试环境的系统中有着广泛的应用。 长期以来, 日志系统作为一种应用程序服务,对于跟踪调试、程序状态记录、崩溃数据恢复都有非常现实的意义。这种服务通常以两种方式存在: 1. 日志系统作为服务进程存在。 Windows 中的的事件日志服务就属于这种类型,该类型的日志系统通常通过消息队列机制将所需要记录的日志由日志发送端发送给日志服务。日志发送端和日志保存端通常不在同一进程当中,日志的发送是异步过程。这种日志服务通常用于管理员监控各种系统服务的状态。 2. 日志系统作为系统调用存在。 Java 世界中的日志系统和 Unix 环境下诸多守护进程所使用的日志系统都属于这种类型。日志系统的代码作为系统调用被编译进日志发送端,日志系统的运行和业务代码的运行在同一进程空间。日志的发送多数属于同步过程。这种日志服务由于能够同步反映处系统运行状态,通常用于调试跟踪和崩溃恢复。 本文建立的日志系统基本属于第二种类型,但又有所不同。该日志系统将利用 Java 线程技术实现一个既能够反映统一线程空间中程序运行状态的同步日志发送过程,又能够提供快速的日志记录服务,还能够提供灵活的日志格式配置和过滤机制。 1.1 系统调试的误区 在控制台环境上调试 Java 程序时,此时往控制台或者文本文件输出一段文字是查看程序运行状态最简单的做法,但这种方式并不能解决全部的问题。有时候,对于一个我们无法实时查看系统输出的系统或者一个确实需要保留我们输出信息的系统,良好的日志系统显得相当必要。 因此,不能随意的输出各种不规范的调试信息,这些随意输出的信息是不可控的,难以清除,可能为后台监控、错误排除和错误恢复带来相当大的阻力。 1.2 日志系统框架的基本功能 一个完备的日志系统框架通常应当包括如下基本特性:

1. 所输出的日志拥有自己的分类。这样在调试时便于针对不同系统的不同模块进行查询,从而快速定位到发生日志事件的代码。 2. 日志按照某种标准分成不同级别。分级以后的日志,可以用于同一分类下的日志筛选。 3. 支持多线程。日志系统通常会在多线程环境中使用,特别是在 Java 系统当中,因此作为一种系统资源,日志系统应当保证是线程安全的。 4. 支持不同的记录媒介。不同的工程项目往往对日志系统的记录媒介要求不同,因此日志系统必须提供必要的开发接口,以保证能够比较容易的更换记录介质。 5. 高性能。日志系统通常要提供高速的日志记录功能以应对大系统下大请求流量下系统的正常运转。 6. 稳定性。日志系统必须是保持高度的稳定性,不能因为日志系统内部错误导致主要业务代码的崩溃。

1.3 常用日志系统简介 在 Java 世界中,以下三种日志框架比较优秀: 1) Log4J 最早的 Java 日志框架之一,由 Apache 基金会发起,提供灵活而强大的日志记录机制。但是其复杂的配置过程和内部概念往往令使用者望而却步。

2) JDK1.4 Logging Framework 继 Log4J 之后, JDK 标准委员会将 Log4J 的基本思想吸收到 JDK 当中,在 JDK1.4 中发布了第一个日志框架接口,并提供了一个简单实现。

3) Commons Logging Framwork 该框架同样是 Apache 基金会项目,其出现主要是为了使得 Java 项目能够在 Log4J 和 JDK1.4 l Logging Framework 的使用上随意进行切换,因此该框架提供了统一的调用接口和配置方法。

2 系统设计 由 于 Log4J 得到广泛应用,从使用者的角度考虑,本文所设计的框架,采用了部分 Log4J 的接口和概念,但内部实现则完全不同。使用 Java 实现日志框架,关键的技术在于前面提及的日志框架特性的内部实现,特别是:日志的分类和级别、日志分发框架的设计、日志记录器的设计以及在设计中的高性能 和高稳定性的考虑。

2.1 系统架构 日 志系统框架可以分为日志记录模块和日志输出模块两大部分。日志记录模块负责创建和管理日志记录器 (Logger) ,每一个 Logger 对象负责按照不同的级别 (LoggerLevel) 接收各种记录了日志信息的日志对象 (LogItem) , Logger 对象首先获取所有需要记录的日志,并且同步地将日志分派给日志输出模块。日志输出模块则负责日志 输出器 (Appender) 的创建和管理,以及日志的输出。系统中允许有多个不同的日志 输出器 ,日志 输出器 负责将日志记录到存储介质当中。系统结构如下图 1 所示:

图 1 ,日志系统结构图 下图 2 使用 UML 类图给出了日志系统的架构:

图 2 ,日志系统框架架构图 在图 2 给出的架构中, 日志记录器 Logger 是整个日志系统框架的用户使用接口,程序员可以通过该接口记录日志,为了实现对日志进行分类,系统设计允许存在多个 Logger 对象,每一个 Logger 负责一类日志的记录, Logger 类同时实现了对其对象本身的管理。 LoggerLevel 类定义了整个日志系统的级别,在客户端创建和发送日志时,这些级别会被使用到。 Logger 对象在接收到客户端创建和发送的日志消息时,同时将该日志消息包装成日志系统内部所使用的日志对象 LogItem ,日志对象除了发送端所发送的消息以外,还会包装诸如发送端类名、发送事件、发送方法名、发送行号等等。这些额外的消息对于系统的跟踪和调试都非常有价 值。包装好的 LogItem 最终被发送给 输出器 ,由这些 输出 器负责将日志信息写入最终媒介, 输出器 的类型和个数均不固定,所有的 输出器 通过 AppenderManager 进行管理,通常通过配置文件即可方便扩展出多个 输出器 。

2.2 日志记录部分的设计 如前文所述,日志记录部分负责接收日志系统客户端发送来的日志消息、日志对象的管理等工作。下面详细描述了日志记录部分的设计要点:

1. 日志记录器的管理 系统通过保持多个 Logger 对象的方式来进行日志记录的分类。每一个 Logger 对象代表一类日志分类。因此, Logger 对象的名称属性是其唯一标识,通过名称属性获取一个 Logger 对象: Logger logger = Logger.getLogger(“LoggerName”); 一般的,使用类名来作为日志记录器的名称,这样做的好处在于能够尽量减少日志记录器命名之间的冲突(因为 Java 类使用包名),同时能够将日志记录分类得尽可能的精细。因此,假定有一 UserManager 类需要使用日志服务,则更一般的使用方式为:

Logger logger = Logger.getLogger(UserManager.class); 2. 日志分级的实现 按照日志目的不同,将日志的级别由低到高分成五个级别: DEBUG - 表示输出的日志为一个调试信息 INFO - 表示输出的日志是一个系统提示 WARN - 表示输出的日志是一个警告信息 ERROR - 表示输出的日志是一个系统错误 FATAL - 表示输出的日志是一个导致系统崩溃严重错误 这些日志级别定义在 LoggerLevel 接口中,被日志记录器 Logger 在内部使用。而对于日志系统客户端则可使用 Logger 类接口对直接调用并输出这些级别的日志, Logger 的这些接口描述如下:

public void debug(String msg); // 输出调试信息 public void info(String msg); // 输出系统提示 public void warn(String msg); // 输出警告信息 public void fatal(String msg); // 输出系统错误 public void error(String msg); // 输出严重错误 通过对 Logger 对象上这些接口的调用,直接为日志信息赋予了级别属性,这样为后继的按照不同级别进行输出的工作奠定了基础。

3. 日志对象信息的获取 日志对象上包含了一条日志所具备的所有信息。通常这些信息包括:输 出日志的时间、 Java 类、类成员方法、所在行号、日志体、日志级别等等。在 JDK1.4 中可以通过在方法中抛出并且捕获住一个异常,则在捕捉到的异常对象中已经由 JVM 自动填充好了系统调用的堆栈,在 JDK1.4 中则可以使用 java.lang.StackTraceElement 获取到每一个堆栈项的基本信息,通过对日志客户端输出日志方法调用层数的推算,则可以比较容易的获取到 StackTraceElement 对象,从而获取到输出日志时的 Java 类、类成员方法、所在行号等信息。在 JDK1.3 或者更早的版本中,相应的工作则必须通过将异常的堆栈信息输出到字符串中,并分析该字符串格式得到。 2.3 日志 输出部分 的设计 日志输出部分的设计具有一定的难度,在本文设计的日志系统中,日志的输出、多线程的支持、日志系统的扩展性、日志系统的效率等问题都交由日志输出部分进行管理。

1. 日志输出器的继承结构 在日志的输出部分采用了二层结构,即定义了一个抽象的日志输出器( AbstractLoggerAppender ) , 然后从该抽象类继承出实际的日志输出器。 AbstractLoggerAppender 定义了一系列的对日志进行过滤的方法,而具体输出到存储媒介的方法则是一个抽象方法,由子类实现。在系统中默认实现了控制台输出器和文件输出器两种,其中 控制台输出器的实现颇为简单。

2. 文件输出器的内部实现 在日志记录部分的实现中,并没有考虑多线程、高效率等问题,因此文 件输出器必须考虑这些问题的处理。在文件输出器内部使用 java.lang.Vector 定 义了一个线程安全的高速缓冲,所有通过日志记录部分分派到文件输出器的日志被直接放置到该高速缓冲当中。同时在文件输出器内部定义一个工作线程,负责定期 将高速缓冲中的内容保存到文件,在保存的过程中同时可以进行日志文件的备份等工作。由于采用了高速缓冲的结构,很显然日志客户端的调用已经不再是一个同步 调用,从而不再会需要等到文件操作后才返回,提高的系统调用的速度。该原理 如图 3 所示:

图 3 ,文件输出器内部结构 2.4 设计难点 通过上述设计,一个具有良好扩展能力的高性能日志系统框架就已经具有了一定的雏形。在设计过程中几个难点问题需要进一步反思。

一、是否整个系统应当采用完全异步的结构,通过类似于消息机制的方式来进行由日志客户端发送日志给日志系统。这种方式可以作为日志系统框架另一种运行方式,在后继设计中加以考虑。

相关文档
最新文档