精华内容
下载资源
问答
  • Java系统日志管理

    万次阅读 2018-07-06 15:24:06
    在一个系统中日志管理是一个很重要的部分,因为当系统发布到线网后出了问题只能看系统日志了,这个时候系统日志起到了一个错误排查功能,同时也可以通过系统日志统计用户吞吐量等等,总之系统日志是系统管理一个重点...
    在一个系统中日志管理是一个很重要的部分,因为当系统发布到线网后出了问题只能看系统日志了,这个时候系统日志起到了一个错误排查功能,同时也可以通过系统日志统计用户吞吐量等等,总之系统日志是系统管理一个重点。
    
    本系统架构为SpringMVC,myBatis,Shrio等等。

    1.SpringMVC异常处理
    SpringMVC负责接收用户请求并进行处理然后将结果返回给用户,那么为了不让异常抛给用户,我们一般在Controller类下每个方法都加上一个try{}catch(Exception e){},实例代码如下:
    /**
    	 * pengweikang 20170220 用户登陆
    	 * 
    	 * @param cgbUser
    	 *            用户信息
    	 * @param session
    	 * @return
    	 */
    	@RequestMapping(value = "/login", method = RequestMethod.POST)
    	public @ResponseBody String userLogin(HttpServletRequest request) {
    		try {
    		     .....	 //Service方法调用
    		} catch (Exception e) {
    		     .....      //异常处理
    		} 
    		return null;
    	}

    该方法的缺点是代码冗余,不利于维护,一看就不想是一个专业的软件工程师应该写的,优化办法如下:

    其实SpringMVC给我们提供了一个控制器增强标签,名称为@ControllerAdvice,通过这个标签就可以统一实现异常处理,代码如下:
    创建统一的异常处理类CGBExceptionHandler.java

    import javax.servlet.http.HttpServletRequest;
    import org.apache.commons.lang.exception.ExceptionUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    
    import ch.qos.logback.classic.Level;
    import net.sf.json.JSONObject;
    
    /**
    *@author		create by pengweikang
    *@date		2018年6月27日--下午12:49:23
    *@problem
    *@answer
    *@action
    */
    @ControllerAdvice
    public class CGBExceptionHandler {
    	ch.qos.logback.classic.Logger loggerback = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger("error");
    	{
    		loggerback.setLevel(Level.ERROR);
    	}
        @ExceptionHandler(Exception.class)
        @ResponseBody
        public Object handleException(Exception e,HttpServletRequest rquest) {
        	loggerback.error("错误日志记录");
        	Map dataMap  = rquest.getParameterMap();
            Set<String> keySet = 	dataMap.keySet();
            for(String key : keySet) {
                String [] datas = (String[])dataMap.get(key);
                String value = new String();
                for(String data : datas) {
        	        value +=data+",";
                }
        	    loggerback.error("Param:"+key+" = "+ value.substring(0, value.length() - 1));//将请求参数保存在日志中
            }
        	loggerback.error(ExceptionUtils.getFullStackTrace(e));  // 记录错误信息
            String msg = e.getMessage();
            if (msg == null || msg.equals("")) {
                msg = "服务器出错";
            }
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("message", msg);
            jsonObject.put("state", 0);
            return jsonObject.toString();
        }
    }

    这个时候你的Controller就不用在添加try-catch异常捕获了, 一但方法出了异常就会跳转到CGBExceptionHandler.java这个类的handleException方法。

    2.logback日志管理

    在这个方法中我们将方法调用的的错误消息都记录在日志中,并且将方法调用的参数也报错在错误日志中,logback配置内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://www.padual.com/java/logback.xsd"
        debug="false" scan="true" scanPeriod="30 second">
    
        <property name="PROJECT" value="recognizeSystem" /> 
        <property name="ROOT" value="/opt/apache-tomcat-7.0.82/logs/${PROJECT}/" />
        <property name="FILESIZE" value="50MB" />
        <property name="MAXHISTORY" value="100" />
        <timestamp key="DATETIME" datePattern="yyyy-MM-dd HH:mm:ss" />
        <!-- 控制台打印 -->
         <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
         	
         	<filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
         
            <encoder charset="utf-8">
                <pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
                </pattern>
            </encoder>
        </appender>
        <!-- ERROR 输入到文件,按日期和文件大小 -->
        <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder charset="utf-8">
                <pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
                </pattern>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${ROOT}%d/error.%i.log</fileNamePattern>
                <maxHistory>${MAXHISTORY}</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${FILESIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
        </appender>
        
        <!-- WARN 输入到文件,按日期和文件大小 -->
        <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder charset="utf-8">
                <pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
                </pattern>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>WARN</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${ROOT}%d/warn.%i.log</fileNamePattern>
                <maxHistory>${MAXHISTORY}</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${FILESIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
        </appender>
        
        <!-- INFO 输入到文件,按日期和文件大小 -->
        <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder charset="utf-8">
                <pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
                </pattern>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>INFO</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${ROOT}%d/info.%i.log</fileNamePattern>
                <maxHistory>${MAXHISTORY}</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${FILESIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
        </appender>
        <!-- DEBUG 输入到文件,按日期和文件大小 -->
        <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder charset="utf-8">
                <pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
                </pattern>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>DEBUG</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${ROOT}%d/debug.%i.log</fileNamePattern>
                <maxHistory>${MAXHISTORY}</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${FILESIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
        </appender>
        <!-- TRACE 输入到文件,按日期和文件大小 -->
        <appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder charset="utf-8">
                <pattern>[%-5level] %d{${DATETIME}} [%thread] %logger{36} - %m%n
                </pattern>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>TRACE</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${ROOT}%d/trace.%i.log</fileNamePattern>
                <maxHistory>${MAXHISTORY}</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${FILESIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
        </appender>
        
        <!-- SQL相关日志输出-->
        <logger name="org.apache.ibatis" level="INFO" additivity="false" />
        <logger name="org.mybatis.spring" level="INFO" additivity="false" />
        <logger name="com.github.miemiedev.mybatis.paginator" level="INFO" additivity="false" />
        
        <!-- Logger 根目录 -->
        <root level="DEBUG">
             <appender-ref ref="STDOUT" />
             <appender-ref ref="DEBUG" /> 
            <appender-ref ref="ERROR" />
            <appender-ref ref="WARN" />
            <appender-ref ref="INFO" /> 
            <appender-ref ref="TRACE" />
        </root>
    </configuration>

    该配置文件内容为将不同类型的日志按不同文件进行报错,并且每天记录不同的日志,文件夹按日期进行命名,儿控制台只打印错误日志。

    3.测试

    测试代码如下:

    @Controller
    @RequestMapping(value="/test")
    public class TestController {
    @RequestMapping(value="/exception")
    	public @ResponseBody String throwexcep(int data) throws Exception {
    		int a = data/0;// 一定会抛出 java.lang.ArithmeticException: / by zero		
    		return null;
    	}
    }
    请求 http://localhost:8080/logSystem/test/exception?data=100

    首先看tomcat的log文件夹如下图所示:



    error.0.log日志记录如下:

    [ERROR] 2018-07-06 15:05:39 [http-bio-8080-exec-3] error - 错误日志记录
    [ERROR] 2018-07-06 15:09:30 [http-bio-8080-exec-2] error - 错误日志记录
    [ERROR] 2018-07-06 15:09:30 [http-bio-8080-exec-2] error - Param:data = 100
    [ERROR] 2018-07-06 15:09:30 [http-bio-8080-exec-2] error - ParameterMap:org.apache.catalina.util.ParameterMap@c7832d1
    [ERROR] 2018-07-06 15:09:30 [http-bio-8080-exec-2] error - java.lang.ArithmeticException: / by zero
    	at com.goldenbridge.recognizesystem.controller.ActivitiController.throwexcep(ActivitiController.java:137)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222)
    	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737)
    	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:860)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
    	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
    	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
    	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
    	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at com.goldenbridge.recognizesystem.utils.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:34)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
    	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
    	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1115)
    	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
    	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    	at java.lang.Thread.run(Thread.java:748)
    
    

    到此系统日志管理配置完成!


    展开全文
  • Java系统中常用日志框架

    万次阅读 多人点赞 2019-06-03 17:17:50
    文章目录日志介绍日志概念日志作用Java中常用日志框架Java常用日志框架之间的关系日志门面框架日志实现框架 日志介绍 日志概念 日志:在计算机领域,日志文件(logfile)是一个记录了发生在运行中的操作系统或其他...

    日志介绍

    日志概念

    日志:在计算机领域,日志文件(logfile)是一个记录了发生在运行中的操作系统或其他软件中的事件的文件,或者记录了在网络聊天软件的用户之间发送的消息1

    日志记录(Logging):是指保存日志的行为。最简单的做法是将日志写入单个存放日志的文件。

    日志级别2

    • FATAL — 表示需要立即被处理的系统级错误。当该错误发生时,表示服务已经出现了某种程度的不可用,系统管理员需要立即介入。这属于最严重的日志级别,因此该日志级别必须慎用,如果这种级别的日志经常出现,则该日志也失去了意义。通常情况下,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无法恢复的错误而退出时。当然,如果某个系统的子系统遇到了不可恢复的错误,那该子系统的调用方也可以记入FATAL级别日志,以便通过日志报警提醒系统管理员修复;
    • ERROR — 该级别的错误也需要马上被处理,但是紧急程度要低于FATAL级别。当ERROR错误发生时,已经影响了用户的正常访问。从该意义上来说,实际上ERROR错误和FATAL错误对用户的影响是相当的。FATAL相当于服务已经挂了,而ERROR相当于好死不如赖活着,然而活着却无法提供正常的服务,只能不断地打印ERROR日志。特别需要注意的是,ERROR和FATAL都属于服务器自己的异常,是需要马上得到人工介入并处理的。而对于用户自己操作不当,如请求参数错误等等,是绝对不应该记为ERROR日志的;
    • WARN — 该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日志,例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等。对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要及时查看并处理的。因此此种级别的日志也不应太多,能不打WARN级别的日志,就尽量不要打;
    • INFO — 该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。通过查看INFO级别的日志,可以很快地对系统中出现的 WARN,ERROR,FATAL错误进行定位。INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%;
    • DEBUG or TRACE — 这两种日志具体的规范应该由项目组自己定义,该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。通过该种日志,可以查看某一个操作每一步的执 行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。可以保证在不重现错误的情况下,也可以通过DEBUG(或TRACE)级别的日志对问题进行诊断。需要注意的是,DEBUG日志也需要规范日志格式,应该保证除了记录日志的开发人员自己外,其他的如运维,测试人员等也可以通过 DEBUG(或TRACE)日志来定位问题;

    日志级别优先级:

    ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF

    日志作用

    日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。

    Java中常用日志框架

    在Java程序中常用日志框架可以分为两类:

    • 无具体实现的抽象门面框架,如:Commons Logging、SLF4J
    • 具体实现的框架,如:Log4j,Log4j 2,Logback,Jul

    Java常用日志框架历史:

    • 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。
    • 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议sun引入Log4j到java的标准库中,但Sun拒绝了。
    • 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势。
    • 接着,Apache推出了Jakarta Commons Logging,JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是log4j,也可以是Java Util Logging。
    • 后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了slf4j(日志门面接口,类似于Commons Logging)和Logback(Slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
    • 现今,Java日志领域被划分为两大阵营:Commons Logging阵营和SLF4J阵营。
      Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出slf4j的发展趋势更好:
    • Apache眼看有被Logback反超的势头,于2012-07重写了log4j 1.x,成立了新的项目Log4j 2。Log4j 2具有logback的所有特性。

    Java常用日志框架之间的关系

    • Log4j2与Log4j1发生了很大的变化,log4j2不兼容log4j1。
    • Commons Logging和Slf4j是日志门面(门面模式是软件工程中常用的一种软件设计模式,也被称为正面模式、外观模式。它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用)。log4j和Logback则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用这只需要关注接口而无需关注具体的实现,做到解耦。
    • 比较常用的组合使用方式是Slf4j与Logback组合使用,Commons Logging与Log4j组合使用。
    • Logback必须配合Slf4j使用。由于Logback和Slf4j是同一个作者,其兼容性不言而喻。

    日志门面框架

    日志门面:是门面模式的一个典型的应用,门面模式,也称外观模式,请参照我的博文设计模式之外观模式,日志门面框架就使一套提供了日志相关功能的接口而无具体实现的框架,其调用具体的实现框架来进行日志记录。也就是说日志门面天然的兼容日志实现框架。典型的日志门面就是Commons Logging、SLF4J。

    日志门面的优点:

    日志门面是介于具体的日志框架与系统之间的桥梁,通过日志门面框架的应用实现了系统与具体实现日志框架的解耦。无论具体实现的日志框架如何变化,都不会影响系统日志的记录功能,更无须更改系统代码,符合“开放-闭合原则”。

    Commons Logging

    Apache Commons Logging是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供API,日志实现和包装器实现。

    SLF4J

    Java简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有Java Logging API、Log4j及logback等框架。以MIT 授权方式发布。SLF4J 的作者就是 Log4j和Logback 的作者 Ceki Gülcü.

    详细请参考我的博文日志框架门面之SLF4J

    Commons Logging和SLF4J实现机制

    Commons logging实现机制:

    Commons logging是通过动态查找机制,在程序运行时,使用自己的ClassLoader寻找和载入本地具体的实现。详细策略可以查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。由于OSGi不同的插件使用独立的ClassLoader,OSGI的这种机制保证了插件互相独立, 其机制限制了commons logging在OSGi中的正常使用。

    Slf4j实现机制:

    Slf4j在编译期间,静态绑定本地的LOG库,因此可以在OSGi中正常使用。它是通过查找类路径下org.slf4j.impl.StaticLoggerBinder,然后绑定工作都在这类里面进。

    日志实现框架

    Jul

    Jul:Java Util Logging,自Java1.4以来的官方日志实现。

    Log4j

    Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。

    Log4j的使用请参考我的博文日志框架之Log4j

    Log4j2

    Apache Log4j 2是apache开发的一款Log4j的升级产品,并且不兼容Log4j。

    Logback

    Logback是一个日志框架,Log4j是同一作者,都出自Ceki Gülcü之手。j
    Logback的使用请参考我的博文日志框架之Logback

    Java 日志框架的选择

    1. 成本考虑:Logback文档免费。Logback的所有文档是全面免费提供的,不象Log4J那样只提供部分免费文档而需要用户去购买付费文档。
    2. 资源开销:Commons Logging相比较与SLF4J开销更高.
    3. 性能:Logback相比Log4j、Log4j2拥有更好的性能。Logback声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。

    1. https://zh.wikipedia.org/zh-hans/日志文件 ↩︎

    2. https://zhuanlan.zhihu.com/p/27363484 ↩︎

    展开全文
  • java日志系统详解

    万次阅读 2014-07-27 10:30:40
    Java日志系统确实比较丰富,常用的有log4j、JUL、logback等等,同时伴随着日志系统的发展,出现了日志框架commons-logging和slf4j。 简短地描述下日志发展,最先出现的是apache开源社区的log4j,这个日志确实是...

    本文转自:点击打开链接


    Java日志系统确实比较丰富,常用的有log4j、JUL、logback等等,同时伴随着日志系统的发展,出现了日志框架commons-logging和slf4j。

    简短地描述下日志发展,最先出现的是apache开源社区的log4j,这个日志确实是应用最广泛的日志工具,成为了java日志的事实上的标准。然而,当时Sun公司在jdk1.4中增加了JUL日志实现,企图对抗log4j,但是却造成了混乱,这个也是被人诟病的一点。当然也有其他日志工具的出现,这样必然造成开发者的混乱,因为这些日志系统互相没有关联,替换和统一也就变成了比较棘手的一件事。想象下你的应用使用log4j,然后使用了一个其他团队的库,他们使用了JUL,你的应用就得使用两个日志系统了,然后又有第二个库出现了,使用了simplelog。这个时候估计让你崩溃了,这是要闹哪样?这个状况交给你来想想办法,你该如何解决呢?进行抽象,抽象出一个接口层,对每个日志实现都适配或者转接,这样这些提供给别人的库都直接使用抽象层即可。不错,开源社区提供了commons-logging抽象,被称为JCL,也就是日志框架了,确实出色地完成了兼容主流的日志实现(log4j、JUL、simplelog),基本一统江湖,就连顶顶大名的spring也是依赖了JCL。看起来事物确实是美好,但是美好的日子不长,接下来另一个优秀的日志框架slf4j的加入导致了更加混乱的场面。比较巧的是slf4j的作者(Ceki Gülcü)就是log4j的作者,他觉得JCL不够优秀,所以他要自己搞一套更优雅的出来,于是slf4j日志体系诞生了,并为slf4j实现了一个亲子——logback,确实更加优雅,但是由于之前很多代码库已经使用JCL,虽然出现slf4j和JCL之间的桥接转换,但是集成的时候问题依然多多,对很多新手来说确实会很懊恼,因为比单独的log4j时代“复杂”多了,可以关注下这个,抱怨声确实很多。到此本来应该完了,但是Ceki Gülcü觉得还是得回头拯救下自己的“大阿哥”——log4j,于是log4j2诞生了,同样log4j2也参与到了slf4j日志体系中,想必将来会更加混乱。接下来详细解读日志系统的配合使用问题。

     

    JCL的实现原理,使用JCL一般(如果是log4j可以不需要)需要一个配置commons-logging.properties在classpath上,这个文件有一行代码:

    org.apache.commons.logging.LogFactory= org.apache.commons.logging.impl.LogFactoryImpl

     这个是告诉JCL使用哪个日志实现,JCL会在classpath下去加载对应的日志工厂实现类,具体的日志工厂实现类可以是log4j实现,可以是jul实现等等。用户只需要依赖JCL的api即可,对日志系统的替换只需要修改下commons-logging.properties文件切换到对应的日志工厂实现即可。但是我们也可以看到因为JCL是运行时去加载classpath下的实现类,会有classloader问题。而且因为log4j尚不支持参数占位符打日志的方式,所以JCL也会更加无力。

     

    slf4j的设计确实比较优雅,采用比较熟悉的方式——接口和实现分离,有个纯粹的接口层——slf4j-api工程,这个里边基本完全定义了日志的接口,所以对于开发来说,只需要使用这个即可。有接口就要有实现,比较推崇的实现是logback,logback完全实现了slf4j-api的接口,并且性能也比log4j更好,同时实现了变参占位符日志输出方式等等新特性。刚刚也提到log4j的使用比较普遍,所以支持这批用户依然是必须的,slf4j-log4j12也实现了slf4j-api,这个算是对log4j的适配器。同样推理,也会有对JUL的适配器slf4j-jdk14等等。为了使使用JCL等等其他日志系统后者实现的用户可以很简单地切换到slf4j上来,给出了各种桥接工程,比如:jcl-over-slf4j会把对JCL的调用都桥接到slf4j上来,可以看出jcl-over-slf4j的api和JCL是相同的,所以这两个jar是不能共存的。jul-to-slf4j是把对jul的调用桥接到slf4j上,log4j-over-slf4j是把对log4j的调用桥接到slf4j。下边用一个图来表示下这个家族的大致成员


     
     如上图,最上层表示桥阶层,下层表示具体的实现层,中间是接口层,可以看出
    这个图中所有的jar都是围绕着slf4j-api活动的,其中slf4j-jul的jar名称是slf4j-jdk14。slf4j-api和具体的实现层是怎么绑定的呢?这个其实是在编译时绑定的,这个可能不好理解,最直接的表达方式是不需要想jcl那样配置一下,只需要把slf4j-api和slf4j-log4j放到classpath上,即实现绑定。原理可以下载slf4j-api的源码查看,这个设计还是很巧妙的,slf4j-api中会去调用StaticLoggerBinder这个类获取绑定的工厂类,而每个日志实现会在自己的jar中提供这样一个类,这样slf4j-api就实现了编译时绑定实现。但是这样接口的源码编译需要依赖具体的实现了,不太合理吧?当时我也有这样的迷惑,因为打开slf4j-api的jar,看不到StaticLoggerBinder,就查看了slf4j-api的源码,在源码中看到了StaticLoggerBinder这个类,猜想应该是slf4j-api在打包过程中有动作,删除了自己保重的那个类,结果不出所料,确实是pom中的ant-task给处理了,pom中处理方式如下:

     

    Xml代码 
    1. <plugin>  
    2.         <groupId>org.apache.maven.plugins</groupId>  
    3.         <artifactId>maven-antrun-plugin</artifactId>  
    4.         <executions>  
    5.           <execution>  
    6.             <phase>process-classes</phase>  
    7.             <goals>  
    8.              <goal>run</goal>  
    9.             </goals>  
    10.           </execution>  
    11.         </executions>  
    12.         <configuration>  
    13.           <tasks>  
    14.             <echo>Removing slf4j-api's dummy StaticLoggerBinder and StaticMarkerBinder</echo>  
    15.             <delete dir="target/classes/org/slf4j/impl"/>  
    16.           </tasks>  
    17.         </configuration>  
    18.       </plugin>  

     

    打出来的slf4j-api是不完整的,只有找到包含StaticLoggerBinder这个类的包才可以,于是slf4j-log4j和logback-classic都提供了这个类。另外,slf4j-log4j和logback以及slf4j-jdk14是不能同时和slf4j共存的,也就是说只能有一个实现存在,不然启动会提示有多个绑定,判断多个实现的代码也很简单,如下:

     

    Java代码 
    1. private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";  
    2.   
    3.   private static void singleImplementationSanityCheck() {  
    4.     try {  
    5.       ClassLoader loggerFactoryClassLoader = LoggerFactory.class  
    6.           .getClassLoader();  
    7.       Enumeration paths;  
    8.       if (loggerFactoryClassLoader == null) {  
    9.         paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);  
    10.       } else {  
    11.         paths = loggerFactoryClassLoader  
    12.             .getResources(STATIC_LOGGER_BINDER_PATH);  
    13.       }  
    14.       // use Set instead of list in order to deal with  bug #138  
    15.       // LinkedHashSet appropriate here because it preserves insertion order during iteration  
    16.       Set implementationSet = new LinkedHashSet();  
    17.       while (paths.hasMoreElements()) {  
    18.         URL path = (URL) paths.nextElement();  
    19.         implementationSet.add(path);  
    20.       }  
    21.       if (implementationSet.size() > 1) {  
    22.         Util.report("Class path contains multiple SLF4J bindings.");  
    23.         Iterator iterator = implementationSet.iterator();  
    24.         while(iterator.hasNext()) {  
    25.           URL path = (URL) iterator.next();  
    26.           Util.report("Found binding in [" + path + "]");  
    27.         }  
    28.         Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");  
    29.       }  
    30.     } catch (IOException ioe) {  
    31.       Util.report("Error getting resources from path", ioe);  
    32.     }  
    33.   }  

     

    同时这个图中桥阶层和对应的实现jar是不能共存的,比如log4j-over-slf4j和slf4j-log4j,jul-to-slf4j和slf4j-jdk14,这个很好理解,会有死循环,启动也会报错。这种想象也就是说jar之前有互斥性,怎么使用maven有效解决“全局排除”会在以后的博文中讲解。jcl-over-slf4j是把对jcl的调用桥接到slf4j上,前文说到它和jcl是互斥的。图中的红线就表示互斥关系。

    当然slf4j也提供了可以把对slf4j的调用桥接到JCL上的工程包——slf4j-jcl,可以看出slf4j的设计者考虑非常周到,想想这样的情况:遗留系统使用的是JCL+log4j,因为系统功能演进,依赖了其他业务线的库,恰好那个库依赖了slf4j-api,并且应用需要关心这个库的日志,那么就需要转接日志到JCL上即可。细心的你可能一经发现,slf4j-jcl和jcl-over-slf4j也是互斥的,太多互斥的了:(。

    对于log4j2的加入,也很简单,和logback是很相似的,如下图:



     红线依然表示依赖的互斥,当然log4j-slf4j-impl也会和logback-classic、slf4j-log4j、slf4j-jdk14互斥。

     

    常见的问题:

    1.slf4j-api和实现版本不对应,尤其是1.6.x和1.5.x不兼容,如果没有特殊需求,直接升级到最新版本。

    2.slf4j的多个实现同时存在,比如slf4j-log4j和logback-classic,排除其中一个即可。

    3.log4j和logback不能同时使用?可以同时使用,这两个并不矛盾,遗留系统可能直接使用了log4j的代码,并且不能通过log4j-over-slf4j桥接,那么可以让他继续使用log4j,这里有详细的介绍。

    4.该如何选用这些呢?建议在非特殊情况下,都使用slf4j-api+logback,不要直接使用日志实现,性能没什么影响。对于要提供给别人的类库,建议使用slf4j-api,使用方可以自由选择具体的实现,并且建议类库不要依赖具体的日志实现。对于自己的桌面小应用,可以直接使用log4j,毕竟只是随便做做。

    5.logback因为木有spring提供的启动listener,所以要自己写?可以看看这里,开源社区已经做好了。

    6.日志系统一般不会影响到系统性能,除非你的系统对性能非常苛刻,如果这样你可以考虑使用Blitz4j,这个是Netflix社区对log4j的性能改进版,不过他们依然建议去使用log4j或者logback。

     

    展开全文
  • Java日志系统

    千次阅读 2016-10-24 20:30:27
    Java世界中,有很多的日志工具库来实现日志功能,避免我们重复造轮子,下面我们就来逐一了解日志工具。1. 最原始的日志记录方式最早期的Java程序中,使用System.out.println()把需要显示的内容打印到屏幕,这种...

    0. 概述

    在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息。
    在Java世界中,有很多的日志工具库来实现日志功能,避免我们重复造轮子,下面我们就来逐一了解日志工具。

    1. 最原始的日志记录方式

    最早期的Java程序中,使用System.out.println()把需要显示的内容打印到屏幕,这种方式使用起来非常简单,但是缺点却是非常多的:
    - 输出内容不可控
    - 可读性差
    - 大量的IO操作使程序变慢

    public class SystemOutPrintln {
    
        public static boolean debug = false;
        public static void main(String[] args) {
            for(int count = 0; count < 4; count++) {
                if(count % 2 == 0) {
                    debug = true;
                } else {
                    debug = false;
                }
                if(debug) {
                    System.out.println("系统信息:第" + count + "次打印。");
                }
            }
        }
    }

    2. JDK的Logging

    从JDK1.4开始,JDK自带了一套日至系统,其最大的优点是不需要任何其他类库的支持,只要有JDK就可以运行,但是其易用性、功能和扩展性很差,因此在商业上很少使用。
    JDK Logging把日志分为9个级别,分别为:ALL、FINEST、FINER、FINE、CONFIG、INFO、WARNING、SERVER、OFF,等级依次升高,较高等级屏蔽较低等级。
    下面来看一个例子:

    import java.util.logging.ConsoleHandler;
    import java.util.logging.Handler;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    /**
     * @JDK 1.8
     * @author weegee
     */
    public class JDKLogging {
    
        public static final Logger logger = Logger.getLogger(JDKLogging.class.toString());
    
        //初始化日志
        static {
            //添加一个控制台输出
            Handler console = new ConsoleHandler();
            //添加到logger中
            logger.addHandler(console);
        }
    
        public static void main(String[] args) {
            //设置日志级别为CONFIG
            logger.setLevel(Level.CONFIG);
            logger.fine("FINE");
            logger.config("CONFIG");
            logger.info("INFO");
            logger.warning("WARNING");
            logger.log(Level.SEVERE,"SERVER");
        }
    }

    输出结果为:

    十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
    信息: INFO
    十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
    信息: INFO
    十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
    警告: WARNING
    十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
    警告: WARNING
    十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
    严重: SERVER
    十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
    严重: SERVER

    这里我们会碰到几个问题:
    1. 我们会惊奇为什么明明设置Level为CONFIG,为什么CONFIG等级的信息没有输出?
    2. 为什么信息输出了两次?
    3. 输出的日志信息的格式、时间和方法名是怎么来的?

    我们依次解答:
    1. 对于JDK自带的Logging,会有一个对应的配置文件logging.properties,位置在$JAVA_HOME/jre/lib/logging.properties,我们首先看一个原始的配置文件。可以看到里面设置了默认的等级为INFO,因此INFO等级以下的信息就不会输出,要想输出其他等级的信息需要修改这里的level信息。

    ############################################################
    #   Default Logging Configuration File
    #
    # You can use a different file by specifying a filename
    # with the java.util.logging.config.file system property.  
    # For example java -Djava.util.logging.config.file=myfile
    ############################################################
    
    ############################################################
    #   Global properties
    ############################################################
    
    # "handlers" specifies a comma separated list of log Handler 
    # classes.  These handlers will be installed during VM startup.
    # Note that these classes must be on the system classpath.
    # By default we only configure a ConsoleHandler, which will only
    # show messages at the INFO and above levels.
    handlers= java.util.logging.ConsoleHandler
    
    # To also add the FileHandler, use the following line instead.
    #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
    
    # Default global logging level.
    # This specifies which kinds of events are logged across
    # all loggers.  For any given facility this global level
    # can be overriden by a facility specific level
    # Note that the ConsoleHandler also has a separate level
    # setting to limit messages printed to the console.
    .level= INFO
    
    ############################################################
    # Handler specific properties.
    # Describes specific configuration info for Handlers.
    ############################################################
    
    # default file output is in user's home directory.
    java.util.logging.FileHandler.pattern = %h/java%u.log
    java.util.logging.FileHandler.limit = 50000
    java.util.logging.FileHandler.count = 1
    java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
    
    # Limit the message that are printed on the console to INFO and above.
    java.util.logging.ConsoleHandler.level = INFO
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
    
    # Example to customize the SimpleFormatter output format 
    # to print one-line log message like this:
    #     <level>: <log message> [<date/time>]
    #
    # java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
    
    ############################################################
    # Facility specific properties.
    # Provides extra control for each logger.
    ############################################################
    
    # For example, set the com.xyz.foo logger to only log SEVERE
    # messages:
    com.xyz.foo.level = SEVERE
    1. 信息输出两次的原因是每个Logger都有一个默认的Handler,在配置文件中有这么一行

      handlers= java.util.logging.ConsoleHandler

      因此我们不需要再给logger添加Handle,在程序中去掉static代码块的内容就会使日志只打印一次。
    2. 在配置文件中

      java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
      java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

      指定了获取得方法名和输出格式。

    这里我们就需要了解一下Logging的工作流程。
    - Logger
    1. 代码需要输入日志的地方都会用到Logger,这几乎是一个JDK logging模块的代言人,用Logger.getLogger(XXX)获得一个logger,然后使用logger做日志的输出;
    2. Logger其实只是一个逻辑管理单元,其多数操作都只是作为一个中继者传递别的<角色>,比如说:Logger.getLogger(“xxx”)的调用将会依赖于LogManager类,使用Logger输入日志信息的时候会调用Logger中的所有Handler进行日志的输入;
    3. Logger是有层次关系的,我们可一般性的理解为包名之间的父子继承关系。每个Logger通常以java包名为其名称。子Logger通常会从父Logger继承Logger级别、Handler、ResourceBundle名(与国际化信息有关)等,例如:

        ```
        public static final Logger logger = Logger.getLogger(JDKLogging.class.toString());
        ```
    
        和
    
        ```
        public static final Logger logger = Logger.getLogger("com.weegee.log.Logging");
        ```
    

    JDKLogging是包com.weegee.log.Logging下的java程序,那么通过java程序获得的Logger就继承通过包名获得的Logger,子类会继承父类的输出格式等信息;
    4. 整个JVM会存在一个名称为空的root logger,所有匿名的logger都会把root logger作为其父。
    - LogManager:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。LogManager中会有一个Hashtable
    [private Hashtable<String,WeakReference<Logger>> loggers]
    用于存储目前所有的logger,如果需要获取logger的时候,Hashtable已经有存在logger的话就直接返回Hashtable中的,如果hashtable中没有logger,则新建一个同时放入Hashtable进行保存。
    - Handler:用来控制日志输出的,比如JDK自带的ConsoleHanlder把输出流重定向到System.err输出,每次调用Logger的方法进行输出时都会调用Handler的publish方法,每个logger有多个handler。我们可以利用handler来把日志输入到不同的地方(比如文件系统或者是远程Socket连接)。
    - Formatter:日志在真正输出前需要进行一定的格式话:比如是否输出时间?时间格式?是否输入线程名?是否使用国际化信息等都依赖于Formatter。
    - Log Level:不必说,这是做容易理解的一个,也是logging为什么能帮助我们适应从开发调试到部署上线等不同阶段对日志输出粒度的不同需求。JDK Log级别从高到低为OFF(231-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-231),每个级别分别对应一个数字,输出日志时级别的比较就依赖于数字大小的比较。但是需要注意的是:不仅是logger具有级别,handler也是有级别,也就是说如果某个logger级别是FINE,客户希望输入FINE级别的日志,如果此时logger对应的handler级别为INFO,那么FINE级别日志仍然是不能输出的。

    总结对应关系:

    • LogManagerlogger是1对多关系,整个JVM运行时只有一个LogManager,且所有的logger均在LogManager中;
    • loggerhandler是多对多关系,logger在进行日志输出的时候会调用所有的hanlder进行日志的处理;
    • handlerformatter是一对一关系,一个handler有一个formatter进行日志的格式化处理;
    • loggerlevel是一对一关系
    • hanlderlevel也是一对一关系 。

    Logging的配置
    JDK默认的logging配置文件为:$JAVA_HOME/jre/lib/logging.properties,可以使用系统属性java.util.logging.config.file指定相应的配置文件对默认的配置文件进行覆盖,配置文件中通常包含以下几部分定义:
    1. handlers:用逗号分隔每个Handler,这些handler将会被加到root logger中。也就是说即使我们不给其他logger配置handler属性,在输出日志的时候logger会一直找到root logger,从而找到handler进行日志的输入;
    2. .level是root logger的日志级别;
    3. <handler>.xxx是配置具体某个handler的属性,比如java.util.logging.ConsoleHandler.formatter便是为ConsoleHandler配置相应的日志Formatter;
    4. logger的配置,所有以[.level]结尾的属性皆被认为是对某个logger的级别的定义,如com.xyz.foo = SEVERE是给名为[com.xyz.foo]的logger定义级别为SEVERE。由于存在继承关系,因此[com.xyz.foo.level]的logger也是SEVERE。除了级别之外,还可以为logger定义handler和useParentHandlers(默认是为true)属性,如com.xyz.foo.handler=com.xyz.test.FooFileHandler(需要是一个extends java.util.logging.Handler的类)、com.xyz.foo.useParentHandlers=false(意味着com.bes.server这个logger进行日志输出时,日志仅仅被处理一次,用自己的handler输出,不会传递到父logger的handler)。
    一个配置文件的例子:

    #Level的五个等级SEVERE(最高值) 、WARNING 、INFO 、CONFIG 、FINE 、FINER 、FINEST(最低值)  
    
    #为 Handler 指定默认的级别(默认为 Level.INFO)。   
    java.util.logging.ConsoleHandler.level=INFO  
    # 指定要使用的 Formatter 类的名称(默认为 java.util.logging.SimpleFormatter)。   
    java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter  
    
    # 为 Handler 指定默认的级别(默认为 Level.ALL)。   
    java.util.logging.FileHandler.level=INFO  
    # 指定要使用的 Formatter 类的名称(默认为 java.util.logging.XMLFormatter)。   
    java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter  
    # 指定要写入到任意文件的近似最大量(以字节为单位)。如果该数为 0,则没有限制(默认为无限制)。   
    java.util.logging.FileHandler.limit=1024000  
    # 指定有多少输出文件参与循环(默认为 1)。   
    java.util.logging.FileHandler.count=1  
    # 为生成的输出文件名称指定一个模式。有关细节请参见以下内容(默认为 "%h/java%u.log")。   
    java.util.logging.FileHandler.pattern=C:/SSLog%u.log  
    # 指定是否应该将 FileHandler 追加到任何现有文件上(默认为 false)。   
    java.util.logging.FileHandler.append=true  
    
    
    
    handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler 

    有关Logging的源码分析请参考JDK Logging深入分析

    3. Log4j

    Log4j是目前应用最广泛的日志控件,它把日志分为ALL、TRACE、DEBUG、INFO、WARNING、ERROR、FITAL、OFF等几个等级,等级依次升高,依然是高等级屏蔽低等级。使用Log4j需要下载相应的jar包

    import org.apache.log4j.Logger;
    
    public class Log4jExample {
        public static final Logger LOGGER = Logger.getLogger(Log4jExample.class);
    
        public static void main(String[] args) {
    
            try {
                String s = null;
                s.length();
            } catch (Exception e) {
                LOGGER.trace("TRACE",e);
                LOGGER.debug("DEBUG",e);
                LOGGER.info("INFO",e);
                LOGGER.warn("WARNING",e);
                LOGGER.error("ERROR",e);
            }
        }
    }

    此外还要为Log4j编写配置文件制定日志的输出信息,不然会提示错误,配置文件为名称为log4j.properties

    # ERROR级别,输出到A1
    log4j.rootLogger = ERROR, A1
    # 定义A1为控制台输出
    log4j.appender.A1 = org.apache.log4j.ConsoleAppender
    # 定义A1输出格式
    log4j.appender.A1.layout = org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

    写好的配置文件要放到classpath路径下,这样程序会自动找到该文件,如果程序找不到log4j.properties文件会打印错误

    log4j:WARN No appenders could be found for logger (com.weegee.log.Log4j.Log4jExample).
    log4j:WARN Please initialize the log4j system properly.
    log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

    另外,在使用Log4j打印日志的时候

    LOGGER.debug(str1 + str2 + str3 + str4 + ...);

    如果DEBUG级别不能使用(可能是要求的等级更高,比如ERROR),LOGGER.debug()会直接返回,但是上面的代码仍会浪费很多时间,因为括号内进行字符串拼接会浪费时间,因此在打印之前最好使用LOGGER.isDebugEnable()或者LOGGER.isEnableFoe(Priority.DEBUG)判断当前等级是否可用,可用再进行打印。
    要是没有放到classpath路径下,我们需要额外的一行代码来指定配置文件

    PropertyConfigurator.configure("src/log.properties");

    配置文件不仅仅可以用.properties文件,也可以使用XML文件,上面的配置文件改成XML格式的文件后

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="console" class="org.apache.log4j.ConsoleAppender">
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n" />
            </layout>
        </appender>
    </log4j:configuration>

    访问String类型的变量s的length()属性是ERROR级别的错误,因此输出结果为:

    2016-10-07 20:19:35:0186 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR
    java.lang.NullPointerException
        at com.weegee.log.Log4j.Log4jExample.main(Log4jExample.java:20)

    Log4j在输出内容的时候并不同于常规的打开、写以及关闭文件,它只在初始化的时候打开文件,在程序结束的时候关闭文件,这样减少了I/O的次数,提高了运行的效率。
    Log4j的配置文件相当灵活,可以输出了数据库、文件、邮箱等等,后面的内容我们再来详细介绍Log4j的配置文件。

    4. commons-logging

    commons-logging属于Apache commons类库,它并不是一个日志控件,仅仅是用来统一JDK的Logging和Log4j的API,如果classpath中又Log4j则使程序使用Log4j,如果没有则使用JDK的Logging,具体的日志功能则交给JDK的Logging和Log4j。对于不能确定日志方式的系统,commons-Logging是个不错的选择。Spring、Hibernate以及Struts等都是使用commons-logging。

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    public class CommonsLogging {
        public static final Log LOG = LogFactory.getLog(CommonsLogging.class);
    
        public static void main(String[] args) {
    
            try {
                String s = null;
                s.length();
            } catch (Exception e) {
                LOG.trace("TRACE",e);
                LOG.debug("DEBUG",e);
                LOG.info("INFO",e);
                LOG.warn("WARNING",e);
                LOG.error("ERROR",e);
            }
        }
    }

    如果有Log4j,commons-logging则会把输出日志的任务交给Log4j,则输出内容为

    2016-10-07 20:47:08:0144 [com.weegee.log.commonslogging.CommonsLogging]-[ERROR] ERROR
    java.lang.NullPointerException
        at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)

    如果没有Log4j,则会使用JDK的Logging,输出为

    十月 07, 2016 8:45:33 下午 com.weegee.log.commonslogging.CommonsLogging main
    信息: INFO
    java.lang.NullPointerException
        at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)
    十月 07, 2016 8:45:33 下午 com.weegee.log.commonslogging.CommonsLogging main
    警告: WARNING
    java.lang.NullPointerException
        at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)
    十月 07, 2016 8:45:33 下午 com.weegee.log.commonslogging.CommonsLogging main
    严重: ERROR
    java.lang.NullPointerException
        at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)

    因为Logging的默认配置文件等级为INFO,因此输出了INFO及其以上等级的信息。
    当然我们也可以显示的启用Log4j,需要编写commons-logging.properties配置文件并放在classpath下

    org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
    org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl

    5. Log4j详解

    5.1 配置日志输出地为控制台

    try {
                String s = null;
                s.length();
            } catch (Exception e) {
                LOGGER.trace("TRACE" + " : " + "null");
                LOGGER.debug("DEBUG" + " : " + "null");
                LOGGER.info("INFO" + " : " + "null");
                LOGGER.warn("WARNING" + " : " + "null");
                LOGGER.error("ERROR" + " : " + "null");
            }

    配置文件为

    # 根记录器级别为ERROR,输出到A1
    log4j.rootLogger=ERROR, A1
    # com.weegee.log包下的记录器为DEBUG级别
    log4j.category.com.weegee.log=DEBUG
    # 控制台输出
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    # DEBUG以上级别输出
    log4j.appender.A1.Threshold=DEBUG
    # 编码方式
    log4j.appender.A1.Encoding=UTF-8
    # 是否立即输出
    log4j.appender.A1.ImmediateFlush=true
    # 使用System.err输出
    log4j.appender.A1.Target=System.err
    # 输出格式
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    # 输出时间格式
    log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

    输出内容

    2016-10-07 21:09:48:0123 [com.weegee.log.Log4j.Log4jExample]-[DEBUG] DEBUG : null
    2016-10-07 21:09:48:0128 [com.weegee.log.Log4j.Log4jExample]-[INFO] INFO : null
    2016-10-07 21:09:48:0128 [com.weegee.log.Log4j.Log4jExample]-[WARN] WARNING : null
    2016-10-07 21:09:48:0131 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null

    5.2 配置日志输出地为文件

    # 根记录器级别为ERROR,输出到文件
    log4j.rootLogger=DEBUG,f
    # Log4jExample类为DEBUG级别
    log4j.category.com.weegee.log.Log4j.Log4jExample=DEBUG
    # 输出到文件
    log4j.appender.f = org.apache.log4j.FileAppender
    # 输出文件位置
    log4j.appender.f.File = log/log1.log
    # 编码方式
    log4j.appender.f.Encoding=UTF-8
    # 是否在文件末尾追加内容
    log4j.appender.f.Append=true
    # 输出格式
    log4j.appender.f.layout=org.apache.log4j.PatternLayout
    # 输出时间格式
    log4j.appender.f.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

    5.3 配置输出按大小滚动的文件

    输出文件达到指定的大小会自动更名,比如日志文件为logInfo.log,指定大小为1K,当logInfo.log达到1K的时候则会自动生成新的文件logInfo.log.1、logInfo.log.2、…,最多的文件数也可以设置。

    # 根记录器级别为ERROR,输出到文件,滚动文件
    log4j.rootLogger=DEBUG, rolling_file
    # Log4jExample类为DEBUG级别
    log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
    # 输出到滚动文件
    log4j.appender.rolling_file=org.apache.log4j.RollingFileAppender
    # DEBUG以上输出
    log4j.appender.rolling_file.Threshold=DEBUG
    # 滚动文件位置
    log4j.appender.rolling_file.File=log/rolling.log
    # 是否追加
    log4j.appender.rolling_file.Append=true
    # 文件大小
    log4j.appender.rolling_file.MaxFileSize=1KB
    # 最多文件数
    log4j.appender.rolling_file.MaxBackupIndex=10
    # 是否立即输出
    log4j.appender.rolling_file.ImmediateFlush=true
    # 编码方式
    log4j.appender.rolling_file.Encoding=UTF-8
    log4j.appender.rolling_file.layout=org.apache.log4j.PatternLayout
    log4j.appender.rolling_file.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

    5.4 配置输出按日期滚动的文件

    每当时间发生变化,文件自动更名(原来的文件还存在)。

    # 根记录器级别为ERROR,输出到文件,滚动文件
    log4j.rootLogger=DEBUG, dailly_rolling
    # Log4jExample类为DEBUG级别
    log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
    # 输出到日期文件
    log4j.appender.dailly_rolling=org.apache.log4j.DailyRollingFileAppender
    # 输出文件位置
    log4j.appender.dailly_rolling.File=log/daily_rolling.log
    # 滚动日期格式
    log4j.appender.dailly_rolling.DatePattern=.yyyy-MM-dd
    log4j.appender.dailly_rolling.layout=org.apache.log4j.PatternLayout
    log4j.appender.dailly_rolling.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%C]-[%p] %m%n

    5.5 输出到数据库

    # 根记录器级别为ERROR,输出到文件,滚动文件
    log4j.rootLogger=DEBUG, DATABASE
    # Log4jExample类为DEBUG级别
    log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
    log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
    log4j.appender.DATABASE.Threshold=DEBUG
    log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/log4j
    log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
    log4j.appender.DATABASE.user=root
    log4j.appender.DATABASE.password=123456
    log4j.appender.DATABASE.sql=INSERT INTO tb_log (date, priority, message, classname ) VALUES ('%d', '%p', '%m', '%c')
    log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
    log4j.appender.DATABASE.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%C]-[%p] %m%n

    5.6 输出到SMTP邮箱

    发送到邮箱需要使用Java Mail包,下载地址mail.jar
    SMTPAppender的默认级别书ERROR,低于该级别的信息不会被发送邮件,在使用SMTP邮件的时候要注意,如果短时间发送大量邮件会被网易、谷歌等大多数邮件服务器封掉IP,因此要谨慎使用。

    # 根记录器级别为ERROR,输出到文件,滚动文件
    
    log4j.rootLogger=DEBUG, MAIL
    # Log4jExample类为DEBUG级别
    log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
    
    ## MAIL
    log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
    log4j.appender.MAIL.Threshold=DEBUG
    # 缓存文件大小到达512K时发送邮件
    log4j.appender.MAIL.BufferSize=512
    # 发送邮件的服务器
    log4j.appender.MAIL.SMTPHost=smtp.163.com
    # 邮件主题
    log4j.appender.MAIL.Subject=Log4J Error Message
    # 用户名
    log4j.appender.MAIL.SMTPUsername=username
    # 密码
    log4j.appender.MAIL.SMTPPassword=password
    # 发件人地址
    log4j.appender.MAIL.From=email_address
    # 日志邮件的接收者
    log4j.appender.MAIL.To=email_address
    log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
    log4j.appender.MAIL.layout.ConversionPattern=[ErrorMessage] %d - %c -%-4r [%t] %-5p %c %x - %m%n

    5.7 输出到SOCKET套接字

    # 根记录器级别为ERROR,输出到文件,滚动文件
    log4j.rootLogger=DEBUG, SOCKET
    # Log4jExample类为DEBUG级别
    log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
    # 套接字输出
    log4j.appender.SOCKET=org.apache.log4j.net.SocketAppender
    # 远程主机名称
    log4j.appender.SOCKET.RemoteHost=127.0.0.1
    # 端口
    log4j.appender.SOCKET.Port=9876
    # 连接超时时间
    log4j.appender.SOCKET.ReconnectionDelay=30000
    # 是否发送本地信息给服务器
    log4j.appender.SOCKET.LocationInfo=true
    log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
    log4j.appender.SOCET.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSS} [%C]-[%p] %m%n

    这里的服务器设置的是127.0.0.1,代表本机,然后监听端口是9876,我们还需要一个程序用来监听日志的输出并把日志显示在某个地方,Log4j自带一个简单的服务器。

    import org.apache.log4j.net.SimpleSocketServer;
    public class RunSimpleSocketServer {
        public static void main(String[] args) {
            String port = "9876";
            String file = "logProperties/socketFile.properties";
            /**
             * SimpleSocketServer.main有两个参数,第一个是监听的端口,要和
             * 配置文件中一致,第二个参数是接收道德日志信息输出的位置,这里我们
             * 使用file的日志记录输出到文件
             */
            SimpleSocketServer.main(new String[] {port, file});
        }
    }

    然后在socketFile.properties中配置日志的输出信息(这里是输出到文件),即可记录日志信息。

    2016-10-08 20:25:51:0876 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Listening on port 9876
    2016-10-08 20:25:51:0882 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Waiting to accept a new client.
    2016-10-08 20:25:59:0081 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Connected to client at /127.0.0.1
    2016-10-08 20:25:59:0082 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Starting new socket node.
    2016-10-08 20:25:59:0217 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Waiting to accept a new client.
    2016-10-08 20:25:59:0084 [com.weegee.log.Log4j.Log4jExample]-[DEBUG] DEBUG : null
    2016-10-08 20:25:59:0116 [com.weegee.log.Log4j.Log4jExample]-[INFO] INFO : null
    2016-10-08 20:25:59:0117 [com.weegee.log.Log4j.Log4jExample]-[WARN] WARNING : null
    2016-10-08 20:25:59:0121 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
    2016-10-08 20:25:59:0442 [org.apache.log4j.net.SocketNode]-[INFO] Caught java.net.SocketException closing conneciton.

    5.8 自定义输出

    集成自AppenderSkeleton类就可以自定义日志输出信息,比如输出到某个级别的信息采取一些行动、输出信息达到一定次数采取一些行动等等。这里我们模拟输出信息达到一定次数采取一些行动。

    # 根记录器
    log4j.rootLogger=DEBUG
    # Log4jExample类为DEBUG级别
    log4j.category.com.weegee.log.Log4j.Log4jExample=ERROR, COUNTING
    # 自定义输出的类
    log4j.appender.COUNTING=com.weegee.log.Log4j.CountingConsoleAppender
    # 最大输出次数
    log4j.appender.COUNTING.limit=4
    log4j.appender.COUNTING.layout=org.apache.log4j.PatternLayout
    log4j.appender.COUNTING.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

    com.weegee.log.Log4j.CountingConsoleAppender

    package com.weegee.log.Log4j;
    
    import org.apache.log4j.AppenderSkeleton;
    import org.apache.log4j.spi.ErrorCode;
    import org.apache.log4j.spi.LoggingEvent;
    
    public class CountingConsoleAppender extends AppenderSkeleton {
    
        int counter = 0;
    
        int limit = 16;
    
        public CountingConsoleAppender() {
        }
    
        public void setLimit(int limit) {
            this.limit = limit;
        }
    
        public int getLimit() {
            return limit;
        }
    
        public void append(LoggingEvent event) {
            if (this.layout == null) {
                // WRITING YOUR OWN APPENDER 111
                errorHandler.error("No layout set for the appender named [" + name
                        + "].", null, ErrorCode.MISSING_LAYOUT);
                return;
            }
            if (counter >= limit) {
                errorHandler.error("Counter limit[" + limit + "] reached in ["
                        + getName() + "] appender", null, ErrorCode.WRITE_FAILURE);
                return;
            }
            // output the events as formatted by our layout
            System.out.print(this.layout.format(event));
            // if our layout does not handle exceptions, we have to do it.
            if (layout.ignoresThrowable()) {
                String[] t = event.getThrowableStrRep();
                if (t != null) {
                    int len = t.length;
                    for (int i = 0; i < len; i++) {
                        System.out.println(t[i]);
                    }
                }
            }
            // prepare for next event
            counter++;
        }
    
        public void close() {
            if (this.closed) // closed is defined in AppenderSkeleton
                return;
            this.closed = true;
        }
    
        public boolean requiresLayout() {
            return true;
        }
    }

    测试程序

    package com.weegee.log.Log4j;
    
    import org.apache.log4j.Logger;
    import org.apache.log4j.PropertyConfigurator;
    
    public class Log4jExample {
        public static final Logger LOGGER = Logger.getLogger(Log4jExample.class);
    
    
        public static void main(String[] args) {
        //加载定义的配置文件
            PropertyConfigurator.configure("logProperties/userdefined.properties");
    
            int count = 5;
            while(count > 0) {
                try {
                    String s = null;
                    s.length();
                } catch (Exception e) {
                    LOGGER.trace("TRACE" + " : " + "null");
                    LOGGER.debug("DEBUG" + " : " + "null");
                    LOGGER.info("INFO" + " : " + "null");
                    LOGGER.warn("WARNING" + " : " + "null");
                    LOGGER.error("ERROR" + " : " + "null");
                }
                count--;
                //输出4次后让系统休眠一段时间给自定义的日志处理类一些时间进行代码处理
                try {
                    if(count == 1) {
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    输出结果

    2016-10-08 20:37:12:0618 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
    2016-10-08 20:37:12:0622 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
    2016-10-08 20:37:12:0622 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
    2016-10-08 20:37:12:0627 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
    log4j:ERROR Counter limit[4] reached in [COUNTING] appender

    5.9 PatternLayout布局定义

    如果您希望基于某种模式生成特定格式的日志信息,可使用 org.apache.Log4j.PatternLayout 格式化您的日志信息。PatternLayout 继承自抽象类 org.apache.Log4j.Layout,覆盖了其 format() 方法,通过提供的模式,来格式化日志信息。PatternLayout 是一个简单的 Layout 对象,提供了如下属性,该属性可通过配置文件更改:

    序号属性 & 描述
    1conversionPattern设置转换模式,默认为 %r [%t] %p %c %x - %m%n。

    模式转换字符
    下面的表格解释了上面模式中用到的字符,以及所有定制模式时能用到的字符:

    转换字符含义
    c使用它为输出的日志事件分类,比如对于分类 “a.b.c”,模式 %c{2} 会输出 “b.c” 。
    C使用它输出发起记录日志请求的类的全名。比如对于类 “org.apache.xyz.SomeClass”,模式 %C{1} 会输出 “SomeClass”。
    d使用它输出记录日志的日期,比如 %d{HH:mm:ss,SSS} 或 %d{dd MMM yyyy HH:mm:ss,SSS}。
    F在记录日志时,使用它输出文件名。
    l用它输出生成日志的调用者的地域信息。
    L使用它输出发起日志请求的行号。
    m使用它输出和日志事件关联的,由应用提供的信息。
    M使用它输出发起日志请求的方法名。
    n输出平台相关的换行符。
    p输出日志事件的优先级。
    r使用它输出从构建布局到生成日志事件所花费的时间,以毫秒为单位。
    t输出生成日志事件的线程名。
    x输出和生成日志事件线程相关的 NDC (嵌套诊断上下文)。
    X该字符后跟 MDC 键,比如 X{clientIP} 会输出保存在 MDC 中键 clientIP 对应的值。
    %百分号, %% 会输出一个 %。

    格式修饰符
    缺省情况下,信息保持原样输出。但是借助格式修饰符的帮助,就可调整最小列宽、最大列宽以及对齐。
    下面的表格涵盖了各种修饰符:

    格式修饰符左对齐最小宽度最大宽度注释
    %20c20如果列名少于 20 个字符,左边使用空格补齐。
    %-20c20如果列名少于 20 个字符,右边使用空格补齐。
    %.30c不适用30如果列名长于 30 个字符,从开头剪除。
    %20.30c2030如果列名少于 20 个字符,左边使用空格补齐,如果列名长于 30 个字符,从开头剪除。
    %-20.30c2030如果列名少于 20 个字符,右边使用空格补齐,如果列名长于 30 个字符,从开头剪除。
    %d{yyyy-MM-dd}-%t-%x-%-5p-%-10c:%m%n

    输出的信息则如下

    2016-10-08-main--DEBUG-Log4jExample:Hello this is an debug message

    5.10 HTMLLayout布局

    # 根记录器级别为ERROR,输出到文件
    log4j.rootLogger=DEBUG
    # Log4jExample类为DEBUG级别
    log4j.category.com.weegee.log.Log4j.Log4jExample=DEBUG, f
    # 输出到文件
    log4j.appender.f = org.apache.log4j.FileAppender
    # 输出文件位置
    log4j.appender.f.File = log/log.html
    # 编码方式
    log4j.appender.f.Encoding=UTF-8
    # 是否在文件末尾追加内容
    log4j.appender.f.Append=true
    # 输出格式
    log4j.appender.f.layout=org.apache.log4j.HTMLLayout

    这里写图片描述

    5.11 XMLLayout布局

    # 根记录器级别为ERROR,输出到文件
    log4j.rootLogger=DEBUG
    # Log4jExample类为DEBUG级别
    log4j.category.com.weegee.log.Log4j.Log4jExample=DEBUG, f
    # 输出到文件
    log4j.appender.f = org.apache.log4j.FileAppender
    # 输出文件位置
    log4j.appender.f.File = log/xml.log
    # 编码方式
    log4j.appender.f.Encoding=UTF-8
    # 是否在文件末尾追加内容
    log4j.appender.f.Append=true
    # 输出格式
    log4j.appender.f.layout=org.apache.log4j.xml.XMLLayout

    生成的文件并不能直接通过XML解析,需要一个额外的文件加载输出的内容

    <?xml version="1.0"?>
    <!DOCTYPE log4j:eventSet SYSTEM "log4j.dtd"
            [
                    <!ENTITY data SYSTEM "xml.log">
            ]
    >
    <log4j:eventSet version="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/">
        &data;
    </log4j:eventSet>

    6.持续更新(logback和slf4j)…

    展开全文
  • 系统操作日志实现_JAVA

    千次阅读 2019-04-29 13:18:22
    最近需求需要记录系统日志,在网上查询发现目前有两种主流方式。一种是利用AOP注解实现,一种是利用拦截器实现。 AOP实现的方式更为灵活,但需要为每一个需要记录的方法上加上注解(类似于白名单)。 我这个需求...
  • Log4j——JAVA系统日志

    万次阅读 2011-02-28 16:10:00
    系统日志的概念及作用 日志系统作为一种应用程序服务,对于跟踪调试、程序状态记录、崩溃数据恢复都有着重要的作用 常用Java日志系统 Log4J 最早的Java日志框架之一,由Apache基金会发起,提供灵活而强大的...
  • 一种Java日志系统框架的设计与实现

    热门讨论 2011-03-28 09:18:24
    一种Java日志系统框架的设计与实现( 一种Java日志系统框架的设计与实现( 一种Java日志系统框架的设计与实现( 一种Java日志系统框架的设计与实现
  • JAVA记录操作日志步骤

    万次阅读 多人点赞 2019-08-06 14:01:55
    系统日志不论是在日常的管理还是维护中都会起到很大的作用,但是在日志的记录中通常会存在很多的问题 日志记录的不规范性 日志记录的重复性 日志记录的难分类 目前日志主要记录的有三方面 请求的入参,出参 关于...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 460,123
精华内容 184,049
关键字:

java系统登陆日志

java 订阅