• 日志框架,jdklogging(jdk自带的logging),原生Logger的logging.properties配置文件简单分析


    原生Logger的logging.properties配置文件简单分析_加倍努力中的博客-CSDN博客_logging.properties

    前言

    logging.properties配置文件用于原生的日志记录器进行配置,对该配置文件有一定了解可以更好的使用日志记录器。

    文件路径

    jre/lib/logging.properties

    文件概览

    ############################################################
    # 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
    java.util.logging.FileHandler.append = true
    # 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
    
    内容分析
    # Default Logging Configuration File #
    # You can use a different file by s filename
    # with the java.util.logging.config.file system property. 
    # For example java -Djava.util.logging.config.file=myfile
    java -Djava.util.logging.config.file=myfile
    

    如果想要使用另一个配置文件,就要将java -Djava.util.logging.config.file特性设置成配置文件的存储位置

    除了以上方法,还可以利用原生API进行设置,在main中执行

    System.setProperty("java -Djava.util.logging.config.file",myfile);
    

    该方法会调用readConfiguration()来重新初始化日志管理器

    # "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
    

    由注释可以得知,.level用于全局设置日志处理器处理对象信息的级别的阈值,这里的设置的级别是INFO

    # 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
    java.util.logging.FileHandler.append = true
    

    这里配置了日志文件的名字格式、文件格式(这里是XML格式)等,
    通过注释,我们可以得知日志文件的存储位置位于User文件夹。

    # 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
    

    这里配置的是输出到日志信息输出到控制台的最低级别(这里是INFO,必须要注意该级别不能低于全局阈值)、输出到控制台日志信息的格式

    # For example, set the com.xyz.foo logger to only log SEVERE
    # messages:com.xyz.foo.level = SEVERE
    

    这里是对自定义的日志记录器配置,指定日志记录的记录级别,低于该级别的日志信息不会被处理。

    日志框架1:目前主流的日志框架_Cape_sir-CSDN博客_主流日志框架

    目前的日志框架有jdk自带的logging,log4j1、log4j2、logback

    目前用于实现日志统一的框架apache的commons-logging、slf4j

    为了理清它们的关系,与繁杂的各种集成jar包,如下:

    log4j、log4j-api、log4j-core
    log4j-1.2-api、log4j-jcl、log4j-slf4j-impl、log4j-jul
    logback-core、logback-classic、logback-access commons-logging
    slf4j-api、slf4j-log4j12、slf4j-simple、jcl-over-slf4j、slf4j-jdk14、log4j-over-slf4j、slf4j-jcl

    日志框架2:jdk-logging(jdk自带的logging)_Cape_sir-CSDN博客_logging日志框架

    1.简单使用

    1.1 示例代码

    private static final Logger logger=Logger.getLogger(JdkLoggingTest.class.getName());
    
    public static void main(String[] args){
        logger.info("jdk logging info: a msg");
    }
    

    其中的Logger是:java.util.logging.Logger

    1.2 过程分析

    1)创建一个LogManager,默认是java.util.logging.LogManager,但是也可以自定义,修改系统属性"java.util.logging.manager"即可,源码如下(manager就是LogManager):

    try {
        cname = System.getProperty("java.util.logging.manager");
        if (cname != null) {
            try {
                Class clz = ClassLoader.getSystemClassLoader().loadClass(cname);
                manager = (LogManager) clz.newInstance();
            } catch (ClassNotFoundException ex) {
                Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
                manager = (LogManager) clz.newInstance();
            }
        }
    } catch (Exception ex) {
        System.err.println("Could not load Logmanager \"" + cname + "\"");
        ex.printStackTrace();
    }
    if (manager == null) {
        manager = new LogManager();
    }
    

    2)加载配置文件,默认是jre目录下的lib/logging.properties文件,也可以自定义修改系统属性"java.util.logging.config.file”,源码如下:

    String fname = System.getProperty("java.util.logging.config.file");
    if (fname == null) {
        fname = System.getProperty("java.home");
        if (fname == null) {
            throw new Error("Can't find java.home ??");
        }
        File f = new File(fname, "lib");
        f = new File(f, "logging.properties");
        fname = f.getCanonicalPath();
    }
    InputStream in = new FileInputStream(fname);
    BufferedInputStream bin = new BufferedInputStream(in);
    try {
        readConfiguration(bin);
    }
    

    3)创建Logger,并缓存起来,放置到一个Hashtable中,并把LogManager设置进新创建的logger中

    修改属性"java.util.logging.manager”,自定义LogManager
    修改属性"java.util.logging.config.file”,自定义配置文件

    2.深入浅出

    2.1 提出问题

    JDK Logging的使用很简单,如下代码所示,先使用Logger类的静态方法getLogger就可以获取到一个logger,然后在任何地方都可以通过获取到的logger进行日志输入。比如类似logger.info(“Main running.”)的调用。

    package com.bes.logging;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    public class LoggerTest {
          private static Loggerlogger = Logger.getLogger("com.bes.logging");
          public static void main(String argv[]) {
                   // Log a FINEtracing message
                   logger.info("Main running.");
                   logger.fine("doingstuff");
                   try {
                             Thread.currentThread().sleep(1000);// do some work
                   } catch(Exception ex) {
                             logger.log(Level.WARNING,"trouble sneezing", ex);
                   }
                   logger.fine("done");
          }
    }
    

    不做任何代码修改和JDK配置修改的话,运行上面的例子,你会发现,控制台只会出现【Main running.】这一句日志。如下问题应该呈现在你的大脑里…

    1.【Main running.】以外的日志为什么没有输出?怎么让它们也能够出现?
    2. 日志中出现的时间、类名、方法名等是从哪里输出的?
    3. 为什么日志就会出现在控制台?
    4. 大型的系统可能有很多子模块(可简单理解为有很多包名),如何对这些子模块进行单独的日志级别控制?
    5. apache那个流行的log4j项目和JDK的logging有联系吗,怎么实现自己的LoggerManager?

    2.2 术语解答

    Logger

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

    LogManager

    1.LogManager:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。
    2.LogManager中会有一个Hashtable【private Hashtable<String,WeakReference> loggers】用于存储目前所有的logger,如果需要获取logger的时候,Hashtable已经有存在logger的话就直接返回Hashtable中的,如果hashtable中没有logger,则新建一个同时放入Hashtable进行保存。

    Handler

    1.Handler:用来控制日志输出的,比如JDK自带的ConsoleHanlder把输出流重定向到System.err输出,每次调用Logger的方法进行输出时都会调用Handler的publish方法,每个logger有多个handler。
    2.我们可以利用handler来把日志输入到不同的地方(比如文件系统或者是远程Socket连接)。

    Formatter

    Formatter:日志在真正输出前需要进行一定的格式化,比如是否输出时间?时间格式?是否输入线程名?是否使用国际化信息等都依赖于Formatter。

    Log Level

    Log Level:不必说,这是做容易理解的一个,也是logging为什么能帮助我们适应从开发调试到部署上线等不同阶段对日志输出粒度的不同需求。JDK Log级别从高到低为:
    OFF(2^31-1) —>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-2^31)
    每个级别分别对应一个数字,输出日志时级别的比较就依赖于数字大小的比较。

    但是需要注意的是:不仅是logger具有级别,handler也是有级别,也就是说如果某个logger级别是FINE,客户希望输入FINE级别的日志,如果此时logger对应的handler级别为INFO,那么FINE级别日志仍然是不能输出的。

    对应关系

    LogManager与logger是1对多关系,整个JVM运行时只有一个LogManager,且所有的logger均在LogManager中。
    logger与handler是多对多关系,logger在进行日志输出的时候会调用所有的hanlder进行日志的处理。
    handler与formatter是一对一关系,一个handler有一个formatter进行日志的格式化处理。
    很明显:logger与level是一对一关系,hanlder与level也是一对一关系。

    2.3 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..xxx是配置具体某个handler的属性,比如java.util.logging.ConsoleHandler.formatter便是为ConsoleHandler配置相应的日志Formatter。

    4.logger的配置,所有以[.level]结尾的属性皆被认为是对某个logger的级别的定义,如com.bes.server.level=FINE是给名为[com.bes.server]的logger定义级别为FINE。

    顺便说下,前边提到过logger的继承关系,如果还有com.bes.server.webcontainer这个logger,且在配置文件中没有定义该logger的任何属性,那么其将会从[com.bes.server]这个logger进行属性继承。

    除了级别之外,还可以为logger定义handler和useParentHandlers(默认是为true)属性,如com.bes.server.handler=com.bes.test.ServerFileHandler(需要是一个extends java.util.logging.Handler的类)。
    com.bes.server.useParentHandlers=false(意味着com.bes.server这个logger进行日志输出时,日志仅仅被处理一次,用自己的handler输出,不会传递到父logger的handler)。

    以下是JDK配置文件示例:

    handlers= java.util.logging.FileHandler,java.util.logging.ConsoleHandler
    .level= INFO
    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
    java.util.logging.ConsoleHandler.level = INFO
    java.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormatter
    com.xyz.foo.level = SEVERE
    sun.rmi.transport.tcp.logLevel = FINE;

    2.4 Logging执行原理

    2.4.1 Logger的获取

    首先是调用Logger的如下方法获得一个logger:

    public static synchronized Logger getLogger(String name) {
        LogManager manager =LogManager.getLogManager();
        return manager.demandLogger(name);
    }
    

    上面的调用会触发java.util.logging.LoggerManager的类初始化工作,LoggerManager有一个静态化初始化块(这是会先于LoggerManager的构造函数调用的_):

    static {  
        AccessController.doPrivileged(newPrivilegedAction<Object>() {  
            public Object run() {  
                String cname =null;  
                try {  
                    cname =System.getProperty("java.util.logging.manager");  
                    if (cname !=null) {  
                        try {  
                            Class clz =ClassLoader.getSystemClassLoader().loadClass(cname);  
                            manager= (LogManager) clz.newInstance();  
                        } catch(ClassNotFoundException ex) {  
                            Class clz =Thread.currentThread().getContextClassLoader().loadClass(cname);  
                            manager= (LogManager) clz.newInstance();  
                        }  
                    }  
                } catch (Exceptionex) {  
                    System.err.println("Could not load Logmanager \"" + cname+ "\"");  
                    ex.printStackTrace();  
                }  
                if (manager ==null) {
                    manager = newLogManager();
                }  
                manager.rootLogger= manager.new RootLogger();
                manager.addLogger(manager.rootLogger);
                Logger.global.setLogManager(manager);
                manager.addLogger(Logger.global);
                return null;  
            }  
        });  
    }
    

    从静态初始化块中可以看出LoggerManager是可以使用系统属性java.util.logging.manager指定一个继承自java.util.logging.LoggerManager的类进行替换的,比如Tomcat启动脚本中就使用该机制以使用自己的LoggerManager。

    不管是JDK默认的java.util.logging.LoggerManager还是自定义的LoggerManager,初始化工作中均会给LoggerManager添加两个logger,一个是名称为””的root logger,且logger级别设置为默认的INFO;另一个是名称为global的全局logger,级别仍然为INFO。

    LogManager”类”初始化完成之后就会读取配置文件(默认为$JAVA_HOME/jre/lib/logging.properties),把配置文件的属性名的属性值这样的键值对保存在内存中,方便之后初始化logger的时候使用。

    1步骤中Logger类发起的getLogger操作将会调用java.util.logging.LoggerManager的如下方法:

    Logger demandLogger(String name) {
        Logger result =getLogger(name);
        if (result == null) {
            result = newLogger(name, null);
            addLogger(result);
            result =getLogger(name);
        }
        return result;
    }
    

    可以看出,LoggerManager首先从现有的logger列表中查找,如果找不到的话,会新建一个looger并加入到列表中。当然很重要的是新建looger之后需要对logger进行初始化,这个初始化详见java.util.logging.LoggerManager#addLogger()方法中,改方法会根据配置文件设置logger的级别以及给logger添加handler等操作。

    到此为止logger已经获取到了,你同时也需要知道此时你的logger中已经有级别、handler等重要信息,下面将分析输出日志时的逻辑。

    2.4.2 日志的输出

    首先我们通常会调用Logger类下面的方法,传入日志级别以及日志内容。

    public void log(Levellevel, String msg) {
        if (level.intValue() < levelValue ||levelValue == offValue) {
            return;
        }
        LogRecord lr = new LogRecord(level, msg);
        doLog(lr);
    }
    

    该方法可以看出,Logger类首先是进行级别的校验,如果级别校验通过,则会新建一个LogRecord对象,LogRecord中除了日志级别,日志内容之外还会包含调用线程信息,日志时刻等;之后调用doLog(LogRecord lr)方法。

    private void doLog(LogRecord lr) {
        lr.setLoggerName(name);
        String ebname =getEffectiveResourceBundleName();
        if (ebname != null) {
            lr.setResourceBundleName(ebname);
            lr.setResourceBundle(findResourceBundle(ebname));
        }
        log(lr);
    }
    

    doLog(LogRecord lr)方法中设置了ResourceBundle信息(这个与国际化有关)之后便直接调用log(LogRecord record)方法。

     public void log(LogRecord record) {
            if (record.getLevel().intValue() <levelValue || levelValue == offValue) {
                return;
            }
            synchronized (this) {
                if (filter != null &&!filter.isLoggable(record)) {
                    return;
                }
            }
            Logger logger = this;
            while (logger != null) {
                Handler targets[] = logger.getHandlers();
                if(targets != null) {
                    for (int i = 0; i < targets.length; i++) {
                        targets[i].publish(record);
                    }
                }
                if(!logger.getUseParentHandlers()) {
                    break;
                }
                logger= logger.getParent();
            }
        }
    

    很清晰,while循环是重中之重,首先从logger中获取handler,然后分别调用handler的publish(LogRecordrecord)方法。while循环证明了前面提到的会一直把日志委托给父logger处理的说法,当然也证明了可以使用logger的useParentHandlers属性控制日志不进行往上层logger传递的说法。到此为止logger对日志的控制差不多算是完成,接下来的工作就是看handler的了,这里我们以java.util.logging.ConsoleHandler为例说明日志的输出。

    public class ConsoleHandler extends StreamHandler {
        public ConsoleHandler() {
            sealed = false;
            configure();
            setOutputStream(System.err);
            sealed = true;
        }
    }
    

    ConsoleHandler构造函数中除了需要调用自身的configure()方法进行级别、filter、formatter等的设置之外,最重要的我们最关心的是setOutputStream(System.err)这一句,把系统错误流作为其输出。而ConsoleHandler的publish(LogRecordrecord)是继承自java.util.logging.StreamHandler的,如下所示:

    public synchronized void publish(LogRecord record) {
        if(!isLoggable(record)) {
            return;
        }
        String msg;
        try {
            msg =getFormatter().format(record);
        } catch (Exception ex){
            // We don't want tothrow an exception here, but we
            // report theexception to any registered ErrorManager.
            reportError(null,ex, ErrorManager.FORMAT_FAILURE);
            return;
        }       
        try {
            if (!doneHeader) {
                writer.write(getFormatter().getHead(this));
                doneHeader =true;
            }
            writer.write(msg);
        } catch (Exception ex){
            // We don't want tothrow an exception here, but we
            // report theexception to any registered ErrorManager.
            reportError(null,ex, ErrorManager.WRITE_FAILURE);
        }
    }
    

    方法逻辑也很清晰,首先是调用Formatter对消息进行格式化,说明一下:格式化其实是进行国际化处理的重要契机。然后直接把消息输出到对应的输出流中。需要注意的是handler也会用自己的level和LogRecord中的level进行比较,看是否真正输出日志。

    2.5 解决问题

    1.【Main running.】以外的日志为什么没有输出?怎么让它们也能够出现?
    这就是JDK默认的logging.properties文件中配置的handler级别和跟级别均为info导致的,如果希望看到FINE级别日志,需要修改logging.properties文件,同时进行如下两个修改

    java.util.logging.ConsoleHandler.level= FINE//修改
    com.bes.logging.level=FINE//添加

    2.日志中出现的时间、类名、方法名等是从哪里输出的?
    请参照[java.util.logging.ConsoleHandler.formatter= java.util.logging.SimpleFormatter]配置中指定的java.util.logging.SimpleFormatter类,其public synchronized String format(LogRecord record) 方法说明了一切。

     public synchronized String format(LogRecord record) {
            StringBuffer sb = new StringBuffer();
            // Minimize memory allocations here.
            dat.setTime(record.getMillis());
            args[0] = dat;
            StringBuffer text = new StringBuffer();
            if (formatter == null) {
                formatter = new MessageFormat(format);
            }
            formatter.format(args, text, null);
            sb.append(text);
            sb.append(" ");
            if (record.getSourceClassName() != null) {     
                sb.append(record.getSourceClassName());
            } else {
                sb.append(record.getLoggerName());
            }
            if (record.getSourceMethodName() != null) {
                sb.append(" ");
                sb.append(record.getSourceMethodName());
            }
            sb.append(lineSeparator);
            String message = formatMessage(record);
            sb.append(record.getLevel().getLocalizedName());
            sb.append(": ");
            sb.append(message);
            sb.append(lineSeparator);
            if (record.getThrown() != null) {
                try {
                    StringWriter sw = newStringWriter();
                    PrintWriter pw = newPrintWriter(sw);
                    record.getThrown().printStackTrace(pw);
                    pw.close();
                    sb.append(sw.toString());
                } catch (Exception ex) {
                }
            }
            return sb.toString();
        }
    

    3.为什么日志就会出现在控制台?
    看到java.util.logging.ConsoleHandler 类构造方法中的[setOutputStream(System.err)]语句,相信你已经明白。

    4.大型的系统可能有很多子模块(可简单理解为有很多包名),如何对这些子模块进行单独的日志级别控制?
    在logging.properties文件中分别对各个logger的级别进行定义,且最好使用java.util.logging.config.file属性指定自己的配置文件。

    5.apache那个流行的log4j项目和JDK的logging有联系吗,怎么实现自己的LoggerManager?
    没联系,两个都是日志框架具体实现,两者的LoggerManager实现逻辑大体一致,只是具体细节不同。

    日志框架3:log4j_Cape_sir-CSDN博客

    1.简介

    Apache Log4j是当前在J2EE和J2SE开发中用得最多的日志框架(几乎所有项目都用它),因为它具有出色的性能、灵活的配置以及丰富的功能,并且在业务有特殊的要求时,可以使用自定义组件来代替框架中已有的组件来满足要求。

    基本上所有的大型应用,包括我们常用的框架,比如hibernate;spring;struts等,在其内部都做了一定数量的日志信息。为什么要做这些日志信息,在系统中硬编码日志记录信息是调试系统,观察系统运行状态的一种方式。可能大部分程序员都还记得自己最开始写代码的时候,写一个方法总是出错,就喜欢使用System.out.println(“1111111”)之类的代码来查看程序的运行是否按照自己想要的方式在运行,其实这些sysout就是日志记录信息,但使用System.out.println或者System.err.println在做日志的时候有其非常大的局限性

    1)所有的日志信息输出都是平行的。比如我不能单独的控制只输出某一个或者某几个模块里面的日志调试信息;或者我不能控制只输出某一些日志。

    2)日志信息的输出不能统一控制。比如我不能控制什么时候打印信息,什么时候不打印信息,如果我不想打印任何信息,我只能选择到所有的java文件中去注释这些日志信息。

    3)日志信息的输出方式是固定的。比如,在编码期,就只能输出到eclipse的控制台上,在tomcat环境中就只能输出到tomcat的日志文件中,我不能选择比如输出日志到数据库或者输出日志到Email中等等复杂的控制。

    所以,综上所述,在编码阶段,往往需要一种统一的硬编码日志方式,来记录程序的运行状态,并且能够提供简单的方式,统一控制日志的输出粒度和输出方式。Log4J就是在这种情况下诞生的,而现在Log4J已经被广泛的采用,成为了最流行的日志框架之一。

    Log4J提供了非常简单的使用方式,如果不需要深入去发现Log4J,或者需要自己去扩展Log4J,那么使用起来是非常方便的。而Log4J也非常关注性能的问题,因为在应用中大量的采用日志的方式,会带来一些反面的问题:

    1)会使一段代码的篇幅增大,增加阅读的难度;
    2)增加代码量,会在一定程度上降低系统运行性能;

    而Log4J在性能上不断的优化,使普通的日志输出的性能甚至做到比System.out.println更快。最后,Log4J提供了非常方便的扩展方式,能让我们非常简单的自定义自己的日志输出级别,日志输出方式等。

    2.第一个日志示例:简单使用

    public class Log4jTest {
        private static Logger logger = Logger.getLogger(Log4jTest.class);
     
        public static void main(String[] args) {
            // 从字面意思上看非常简单,我们使用了一个基础配置器,并调用其configure()方法,即配置方法完成了配置。
            BasicConfigurator.configure();
            logger.debug("my first log4j info");
        }
    }
    

    就这么简单,通过这个例子,其实我们已经演示完了Log4j的使用方式,要把Log4J加入你的应用,只需要这么几步:

    1)导入Log4J的包。
    2)完成Log4J的配置。在上面的应用中,我们仅仅是使用了最简单也最缺乏灵活性的BasicConfigurator来完成配置,后面我们会逐步看到更灵活的配置方式,但是配置总是需要的一个步骤。
    3)对于每一个需要日志输出的类来说,通过Logger.getLogger方法得到一个专属于这个类的日志记录器。
    4)使用日志记录器完成日志信息的输出;在上面的例子中,我们仅仅看到了logger的debug方法,后面会看到更多的日志记录方式。

    3.复杂例子1:日志级别的控制

    上面我们看了一个使用Log4J的最简单的示例,在这个示例里面,我们只在一个类中使用标准的配置打印了一行日志信息。接下来,我们简单模拟几个常见的日志情况,来看看Log4J的一些对日志级别的控制能力。

    第一个例子,我们来看看Log4J对日志级别的控制。在Log4J中,日志是可以分成不同级别的,这和我们做日志的想法其实是保持一致的。有的时候,我们记录的信息可能是非常细节的,比如读取了一个配置文件,我们需要日志这个文件的地址,可能我们要日志这个配置文件里面每一个配置项的详细信息;而有的时候,我们不需要查看配置文件里面每一个配置项的具体信息,而只想记录当前已经完成了配置文件的读取。下面我们就来模拟这样一个例子:

    假设我们的代码要处理一个数据库连接的示例,有一个数据库连接配置文件:db.properties

    driverClass=com.mysql.jdbc.Driver
    url=jdbc:mysql:///log4j
    username=log4j
    password=admin
    

    接下来,我们写一个代码来读取和解析这个配置文件:

    package cd.itcast.log4j;
    import java.io.IOException;
    import java.util.Properties;
    public class Configure {
        // 同样,首先得到和这个类绑定的Logger实例
        private static Logger log = Logger.getLogger(Configure.class);
     
        public void config() {
            log.info("using default db.properties");
             config("db.properties");
        }
     
        public void config(String resourceName) {
            log.info("using config file in classpath:" + resourceName);
            try {
                Properties prop = new Properties();
                prop.load(this.getClass().getClassLoader().getResourceAsStream(resourceName));
                log.debug("load properties file success");
                for (String key : prop.stringPropertyNames()) {
                    String value = prop.getProperty(key);
                    // doSomeConfigWorkUseKeyAndValue(key,value)
                    log.debug("[properties] " + key + " : " + value);
                }
            } catch (Exception e) {
                log.error("log properties file failed", e);
            }
        }
    }
    

    在改进后的版本中,我们需要注意几个地方:

    1)同样,我们仍然需要得到一个和该类绑定的Logger实例;
    2)我们在该类中并没有做BasicConfigurator基础设置,因为Log4J的配置是可以放在外部的,我们的类本身只需要做日志,而不需要管怎么去配置。
    3)在该类中,我们一共使用到了三种Logger实例的方法:

    • info:info表示概要信息记录级别,比如代码中的正在读取配置
    • debug:debug表示很详细的信息记录级别,比如代码中的读取的配置文件内容是什么;
    • error:error表示对错误的记录级别,这里在error方法后面,我们把可能抛出的错误作为参数也传给了方法;

    完成该类后,我们写一个测试来使用这个类:

    package cd.itcast.log4j;
    import org.apache.log4j.BasicConfigurator;
    import org.junit.Test;
    public class LogTest2 {
     
        @Test
        public void testLog2(){
            BasicConfigurator.configure();
            Configure conf=new Configure();
            conf.config();
        }
    }
    

    同样,我们先使用BasicConfigurator.config()方法来完成最基本的配置,运行测试,输出:

    0 [main] INFO cd.itcast.log4j.Configure  - using default db.properties
    0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:db.properties
    0 [main] DEBUG cd.itcast.log4j.Configure  - load properties file success
    0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] driverClass : com.mysql.jdbc.Driver
    0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] url : jdbc:mysql:///log4j
    0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] password : admin
    0 [main] DEBUG cd.itcast.log4j.Configure  - [properties] username : log4j
    

    可以通过输出看到,所有的INFO和DEBUG级别的日志信息都输出来了。接下来,我们先修改代码,让代码报个错:

    package cd.itcast.log4j;
    import org.apache.log4j.BasicConfigurator;
    import org.junit.Test;
    public class LogTest2 {
     
        @Test
        public void testLog2(){
            BasicConfigurator.configure();
            Configure conf=new Configure();
            conf.config("aa.properties");
        }
    }
    

    这里,我们故意传一个不存在的配置文件,运行测试:

    0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:aa.properties
    16 [main] ERROR cd.itcast.log4j.Configure  - log properties file failed
    java.lang.NullPointerException
    at java.util.Properties$LineReader.readLine(Properties.java:418)
    at java.util.Properties.load0(Properties.java:337)
    at java.util.Properties.load(Properties.java:325)
    at cd.itcast.log4j.Configure.config(Configure.java:20)
    

    可以看到结果里面即打印出了INFO,也打印出了我们想要的ERROR级别,但下面还有错误的栈信息,这个就看你自己了,如果不想要错误栈信息,只需要打印ERROR级别日志,那么在调用Logger.error方法的时候,不要把错误作为第二个参数传入方法就行了。代码写到这里,演示了Log4J中一个非常重要的概念:日志级别。在Log4J中,内置了几种日志级别,分别是:debug;info;warn;error;和fatal;debug、info和error前面都已经见识过了,下面简单介绍下warn和fatal:

    • warn代表警告级别,表示需要引起注意,可能程序没有按照预期运行等等;
    • fatal代表致命级别,表示非常严重的错误;

    通过这几个级别的解释,大家应该很容易能够感觉到,日志级别是有一定的顺序的,在Log4J中,日志级别的顺序定义为:debug<info<warn<error<fatal;那么这个级别到底有什么用呢?下面接着看这个例子:

    假如现在我的应用已经基本测试完成,在客户那里部署了,我现在不想再看到那么详细的日志输出了,我只想看到粗略的步骤即可,即我不想看到DEBUG信息了,那怎么做呢?很简单,我们只需要在代码中加入一句话:

    package cd.itcast.log4j;
    import org.apache.log4j.BasicConfigurator;
    import org.apache.log4j.Level;
    import org.apache.log4j.Logger;
    import org.junit.Test;
    public class LogTest2 {
     
        @Test
        public void testLog2(){
            BasicConfigurator.configure();
            Logger.getRootLogger().setLevel(Level.INFO);
            Configure conf=new Configure();
            conf.config();
        }
    }
    

    在加入的这一句代码中,包含着非常重要的2个内容,大家要非常注意。在讲解之前,我们先看看输出的结果:

    0 [main] INFO cd.itcast.log4j.Configure  - using default db.properties
    0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:db.properties
    

    可以看到,这次就只打印出了INFO级别的信息,而没有打印DEBUG级别了。下面我们来稍微详细的解释一下这段代码。

    第一个需要注意的就是,我们在调整打印日志级别的时候,并没有修改Configure这个类里面的代码,换句话说,这再次印证了之前我们说了,控制日志级别是应用之外的事情,而不是应用本身代码控制的。

    第二个需要注意的是从字面意义上来看,我们先通过Logger类的getRootLogger方法得到了一个Logger类实例,这个Logger类实例就是根Logger。接着,我们设置了这个根Logger的日志级别为Level.INFO,结合最后输出的日志,我们很容易想到,我们规定了最小的输出日志级别是INFO。

    这句代码反应出了两个非常重要的思想,第一个思想,之前已经提到了,日志级别是有顺序的,这里就能明显看到这个顺序对我们控制应用的中的日志级别的作用,根据刚才的顺序,我们控制了最低打印级别为Level.INFO,那么应用就只会打印出大于或等于这个级别的日志信息,即info<warn<error<fatal这四个。我们可以通过让应用报错,来看看会不会打印出error:

    0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:aa.properties
    0 [main] ERROR cd.itcast.log4j.Configure  - log properties file failed
    

    仍然正常打印出ERROR。第二个思想,代表着Log4J的一个非常重要的体系结构:日志记录器(Logger)是有层次结构的;并且,这个层次结构和类的结构是保持一致的。我们把上面的示例所涉及的两个类的结构画出来:

    输入图片说明

    当我们在LogTest2中使用Logger.getRootLogger()方法的时候,实际上,我们得到了整个应用的根Logger对象,即图中的rootLogger;而我们在Configure类中写的:

    private static Logger log = Logger.getLogger(Configure.class);
    

    代码的真正含义是,我在cd.itcast.log4j这个Logger体系结构下创建了一个cd.itcast.log4j.Configure名字的Logger实例。这个实例的根Logger就是在LogTest2中得到的rootLogger();因为LogTest2和Configure是在同一个classLoader之下的,所以他们共享同一个RootLogger。而当我们在rootLogger上设置了日志级别,即在该rootLogger的体系之上设置了一个默认的日志级别,即Level.INFO;而我们并没有在cd.itcast.log4j.Configure绑定的Logger上设置日志级别,所以他继承了他的祖先的日志级别,即rootLogger的Level.INFO,所以,打印出来就只有大于或等于INFO的日志级别信息了。关于Logger的体系结构,后面会详细讲到,这里大家只要心理面有一个大概的印象即可。

    4.复杂例子2:不同模块的日志打印

    上面我们演示了一个稍微复杂一点的例子,在那个例子中,我们使用了不同的日志打印级别,并且控制了打印级别,但是上面的例子仍然是对于一个类的日志控制,我们在这个例子中,来看看,控制不同模块的日志打印。

    在上面的例子中,我们加入一个新的类,这个类放在一个额外的包中:

    package cd.itcast.core;
    import org.apache.log4j.Logger;
    import cd.itcast.log4j.Configure;
    public class LogicProcessor {
        private static Logger log=Logger.getLogger(LogicProcessor.class);
     
        public void init(Configure conf){
            log.info("init logic processor using conf");
        }
     
        public void process(){
            log.info("process some logic");
            log.debug("process some detail logic");
        }
    }
    

    在这个类中,我们创建了一个模拟某种核心处理器的类,这个类放在了cd.itcast.core包里面,然后在这个类里面使用不同的日志级别打印了一些日志。

    然后我们来完成一个测试:

    package cd.itcast.log4j;
    import org.apache.log4j.BasicConfigurator;
    import org.junit.Test;
    import cd.itcast.core.LogicProcessor;
    public class LogTest3 {
     
        @Test
        public void testLog(){
            BasicConfigurator.configure();
     
            Configure conf=new Configure();
            conf.config();
     
            LogicProcessor processor=new LogicProcessor();
            processor.init(conf);
            processor.process();
        }
    }
    

    注意,这个测试我们仍然是放在cd.itcast.log包中的,这个没有任何关系。在这个测试中,我们综合使用到了之前的Configure类和新的LogicProcessor类来完成一些业务逻辑。同样,我们先简单使用BasicConfigurator.configure()完成基础设置。运行测试,输出:

    0 [main] INFO cd.itcast.log4j.Configure  - using default db.properties
    0 [main] INFO cd.itcast.log4j.Configure  - using config file in classpath:db.properties
    15 [main] DEBUG cd.itcast.log4j.Configure  - load properties file success
    31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] driverClass : com.mysql.jdbc.Driver
    31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] url : jdbc:mysql:///log4j
    31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] password : admin
    31 [main] DEBUG cd.itcast.log4j.Configure  - [properties] username : log4j
    31 [main] INFO cd.itcast.core.LogicProcessor  - init logic processor using conf
    31 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
    31 [main] DEBUG cd.itcast.core.LogicProcessor  - process some detail logic
    

    观察输出,两个类里面的所有的日志都输出了,根据上一个例子,我们基本能够猜到,默认情况下,rootLogger的日志级别被设置为了Level.DEBUG。下面,我们来模拟一个场景。假如现在我们已经完成了cd.itcast.log.Configure类的详细测试,我们只想这个类做WARN级别之上的日志输出,但是现在我们还没有确定LogicProcessor是否正常运行,所以对于LogicProcessor类我们要做DEBUG级别的日志。

    怎么控制这个输出?其实我们根据前一个例子解释的Logger的继承的体系结构,我们能有两种方式来完成这个目的。
    (1)设置rootLogger日志级别为DEBUG;设置cd.itcast.log4j.Configure日志级别为WARN。
    (2)设置rootLogger日志级别为WARN;设置cd.itcast.core.LogicProcessor日志级别为DEBUG。
    我们随意选择一种模式,比如第二种,那么,修改我们的代码:

    import org.junit.Test;
    import cd.itcast.core.LogicProcessor;
    public class LogTest3 {
        @Test
        public void testLog(){
            BasicConfigurator.configure();
            Logger.getRootLogger().setLevel(Level.WARN);
            Logger.getLogger("cd.itcast.core.LogicProcessor").setLevel(Level.DEBUG);
     
            Configure conf=new Configure();
            conf.config();
     
            LogicProcessor processor=new LogicProcessor();
            processor.init(conf);
            processor.process();
        }
    }
    

    再次运行测试,输出:

    0 [main] INFO cd.itcast.core.LogicProcessor  - init logic processor using conf
    0 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
    0 [main] DEBUG cd.itcast.core.LogicProcessor  - process some detail logic
    

    达到我们的目的。我们回过头来看看我们到底做了些什么。加了两行代码,第一行代码大家应该熟悉了,设置rootLogger的日志级别为WARN,那么在该rootLogger体系结构中的cd.itcast.log.Configure和cd.itcast.core.LogicProcessor默认日志级别都变成了WARN。接着第二行代码,我们使用Logger.getLogger()方法,传入cd.itcast.core.LogicProcessor参数,就得到了和LogicProcessor类绑定的那个Logger实例。注意这里,通过Logger.getLogger(“cd.itcast.core.LogicProcessor”)得到的Logger实例和我们在LogicProcessor类中使用Logger.getLogger(LogicProcessor.class)得到的Logger实例是一个实例。换句话说,在一个rootLogger体系结构中,绑定到同一个名字的Logger实例只会有一份。接着,我们再设置这个Logger的日志记录级别为Level.DEBUG,那么LogicProcessor绑定的Logger实例就不再继承rootLogger的日志记录级别了。所以,能正常打印。

    通过这段代码,我们可以再次看到这个rootLogger的继承体系结构和继承的关系。当然,Log4J远远不止如此。

    假如现在我们在cd.itcast.log包中和cd.itcast.core包中,不止一个类,换句话说,我现在想让cd.itcast.log包及其子包中的类的日志级别为WARN,而让cd.itcast.core包及其子包中的所有类的日志级别为DEBUG,又该怎么做呢?可能这种情况才是我们在现实应用当中会遇到的吧。其实非常简单,我们只需要改一句代码:

    @Test
    public void testLog(){
        BasicConfigurator.configure();
        Logger.getRootLogger().setLevel(Level.WARN);
        Logger.getLogger("cd.itcast.core").setLevel(Level.DEBUG);
     
        Configure conf=new Configure();
        conf.config();
     
        LogicProcessor processor=new LogicProcessor();
        processor.init(conf);
        processor.process();
    }
    

    再次运行测试,输出:

    0 [main] INFO cd.itcast.core.LogicProcessor  - init logic processor using conf
    0 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
    0 [main] DEBUG cd.itcast.core.LogicProcessor  - process some detail logic
    

    我们这次不再得到cd.itcast.core.LogicProcessor绑定的Logger,而是得到cd.itcast.core包对应的Logger,是的,就是这样。当我们绑定cd.itcast.core.LogicProcessor对应的Logger的时候,实际上隐式的绑定了cd、cd.itcast、cd.itcast.core这三个Logger。所以,现在的rootLogger体系结构应该是这样:

    输入图片说明

    那么,当我设置rootLogger的日志级别为WARN,并得到cd.itcast.core绑定的Logger,设置其日志级别为DEBUG的时候,rootLogger、cd、cd.itcast、cd.itcast.log、cd.itcast.log.Configure的日志级别都是WARN,而cd.itcast.core、cd.itcast.core.LogicProcessor的日志级别为DEBUG。

    通过这个示例,我们对Logger的继承体系应该有了更深入一些的了解,也可以通过这个例子可以看出,当我们对不同级别的Logger做特定的配置的时候,会非常灵活的控制我整个系统的日志输出,这是Log4J最为强大的地方之一。

    如果你曾经使用过Log4j,你也许会对上面的代码感到非常奇怪,以前不是这样用Log4J的呀,也不要慌,之后就能慢慢看到平时我们使用Log4J的方式和其使用方式后面的真正含义。

    5.Log4j体系结构

    当我们在描述为系统做日志这个动作的时候,实际上描述了3个点;类似于小学语文学语法一样。做日志,其实就是在规定,在什么地方用日志记录器以什么样的格式做日志。把三个最重要的点抽取出来,即什么地方,日志记录器,什么格式。在Log4J中,就使用了三个最重要的组件来描述这三个要素,即:

    Logger:日志记录器

    Appender:什么地方

    Layout:什么格式

    5.1 Logger的结构

    之前的示例中大家对Logger已经有了一定的认识,Logger就是最重要的,我们直接用来完成日志的工具。使用日志框架一个非常重要的目的就是让程序员能够方便的控制特定的日志的输出。要能够控制特定日志的输出,就需要创建一种特殊的结构来划分我们的日志记录器。而对于JAVA程序员来说,按照包的层次关系划分Logger是最容易想到,也最贴近我们的代码结构的。

    之前我们在创建Logger的时候,都是使用Logger.getLogger(Class)方法来得到一个类绑定的日志记录器的。实际上,当我们说把一个日志记录器绑定在一个类上,这种说法是不准确的,正确的说,我们仅仅是使用给定的类的全限定名为Logger取了一个名字。这里请大家注意一下,Logger都是有名字的,假如我们除开rootLogger不谈,我们可以把Logger想象成一张表里面的数据,Logger对应的名字就是其主键,当两个Logger的名字相同,这两个Logger就是同一个Logger实例。我们可以简单的通过一个实例来验证:

    @Test
    public void testLoggerName(){
        BasicConfigurator.configure();
        Logger log1=Logger.getLogger("a");
        Logger log2=Logger.getLogger("a");
        log1.info(log1==log2);
    }
    

    控制台打印:

    0 [main] INFO a  - true;
    

    说明Logger自身维护着每一个名字的Logger实例的引用,保证相同名字的Logger在不同地方获取到的实例是一致的,这样就允许我们在统一的代码中配置不同Logger的特性。

    另外,Logger的层次结构,也是靠Logger的名字来区分的,比如:名称为java的Logger就是java.util的父Logger;java.util是java.util.Collection的父Logger;Logger的体系结构和package的结构划分类似,使用.来区分;所以我们前面才说,使用类的全限定名是最简单,也是最符合logger的体系结构的命名方式。当然,你也可能使用任何的能想到的方式去处理Logger的命名;这也是可以的。

    看到这里,可能有的人会提出疑问,那既然Logger的层次结构是按照Logger的名字来创建的,那在创建Logger的时候,是否必须按照其结构来顺序创建Logger?比如:

    Logger log1=Logger.getLogger(“cd”);
    Logger log2=Logger.getLogger(“cd.itcast”);
    Logger log3=Logger.getLogger(“cd.itcast.log”);
    

    是否必须要按照这个顺序创建呢?不需要。在做前面的示例的时候,我们说到,大家可以认为当我们通过Logger.getLogger(“cd.itcast.log”)的时候,Log4J其实为我们创建了cd;cd.itcast和cd.itcast.log这三个Logger;其实不然,如果是这样的话,Log4J可能会产生非常多不必要的Logger。所以,真正的方式应该是,当我通过Logger.getLogger(“cd.itcast.log”)的时候,Log4J仅仅为我们创建了cd.itcast.log这个Logger;而当我们再次使用Logget.getLogger(“cd.itcast”)的时候,Log4J才会为我们创建cd.itcast这个Logger,并且Log4J会自动的去寻找这个Logger的上下级关系,并自动的把这个新创建的Logger添加到已有的Logger结构体系中。

    上面说到,除了rootLogger之外,其他的Logger都有名字,那么rootLogger呢?rootLogger有下面的规范:

    rootLogger是没有名字的;

    rootLogger是自动存在的;

    rootLogger必须设置Level等级;

    rootLogger没有名字,所以无法像其他的类一样,可以通过Logger.getLogger(String)得到,他只能通过Logger.getRootLogger方法得到;而我们前面使用的Logger.getLogger(Class)方法,其实相当于Logger.getLogger(Class.getName())而已;所以真正赋予Logger的是一个名字,而不是类型;

    在Logger中,非常重要的一个组件,就是Logger的日志级别,即Level。关于Level的划分,使用,我们在前面的例子中已经大概了解到了,下面来看看完整的Level的定义和其在Logger体系中的继承方式,这是一个很简单的概念。

    首先,在Log4J中,为我们默认的定义了7种不同的Level级别,即all<debug<info<warn<error<fatal<off;而这7中Level级别又刚好对应着Level类中的7个默认实例:Level.ALL;Level.DEBUG;Level.INFO;Level.WARN;Level.ERROR;Level.FATAL和Level.OFF。我们在之前也只看到了其中的debug到fatal这5种;这五种日志级别正好对应着Logger中的五个方法,即:

    public class Logger {
        // 输出日志方法:
        public void debug(Object message);
        public void info(Object message);
        public void warn(Object message);
        public void error(Object message);
        public void fatal(Object message);
        // 输出带有错误的日志方法:
        public void debug(Object message, Throwable t);
        public void info(Object message, Throwable t);
        public void warn(Object message, Throwable t);
        public void error(Object message, Throwable t);
        public void fatal(Object message, Throwable t);
        // 更通用的输出日志方法:
        public void log(Level p, Object message);
    }
    

    为什么这里列出了11个方法,其实大家仔细看一下就知道了,最上面5个方法和中间的5个方法其实是对应的,只是中间的5个方法都带有对应的错误信息。而更下面的log方法,是允许我们直接使用Level类型来输出日志,这给了我们直接使用自定义的Level级别提供了输出日志的方式。换句话说,debug(Object message)其实就是log(Level.DEBUG,message)的简写而已。

    要能输出日志,按照常规来说,每一个Logger实例都应该设置其对应的Level;但是如果这样做,会让我们控制Logger的Level非常麻烦,而Log4J借助Logger的层级关系,使用继承的方式来最大限度的减少Level的设置。这个规律我们在之前的代码中已经讲过。

    一种不太常用的方式是使用Threshold方式来限制日志输出。Threshold我们可以理解为门槛,它和Level的概念完全一致,只是使用方式有一些区别,先来看代码:

    @Test
    public void testLogLevel2() {
        BasicConfigurator.configure();
     
        Logger logger = Logger.getLogger("cd.itcast");
        logger.getLoggerRepository().setThreshold(Level.WARN);
        Logger barLogger = Logger.getLogger("cd.itcast.log");
     
        logger.warn("logger warn");
        logger.debug("logger debug");
        barLogger.info("bar logger info");
        barLogger.debug("bar logger debug");
    }
    

    我们先创建了一个名字为cd.itcast的Logger,这次我们并没有设置其Level,而是使用logger.getLoggerRepository()方法得到了一个LoggerRepository对象。这个LoggerRepository可以理解为Logger栈,简单说,就是从当前Logger开始其下的所有的子Logger。然后,我们设置了这个栈的日志门槛,Threshold就是门槛的意思,换句话说就是最低日志输出级别为WARN,那么其下的所有的Logger的最低日志输出级别就变为了WARN。所以,这段代码执行的结果是:

    0 [main] WARN cd.itcast  - logger warn
    

    Threshold优先级>Level优先级,这里需要注意一点,Threshold优先级大于Level优先级,不等于Level就没有了意义。假如Threshold设置的级别为DEBUG,而Level设置的等级为INFO,那么最终,logger.debug还是不会输出日志。

    Threshold的方式和Level一样,如果子Logger设置了自己的Threshold,则会使用自己的Threshold,如果没有设置,则继续向上查询,直到找到一个父类的Threshold或者rootLogger的level。只要父Logger的Threshold设置好了,子Logger的Level也失效了。

    上面简单的介绍了Logger和Level的意义和使用方式,其实,在真正使用Log4J的时候,我们一般都不需要使用代码的方式去配置Level或者Threshold,这些配置更多的时候是使用配置文件来完成的,这个后面会详细介绍。但是,不管使用什么样的配置文件,最终也会解释成这样的配置代码,所以,理解了这些代码,再去使用配置文件,会更加清楚到底配置文件在干什么,同样,为我们自己去扩展Log4J的配置,想要自己去实现自定义的配置文件格式,提供了可能。

    5.2 Appender的结构

    使用Logger的日志记录方法,仅仅是发出了日志记录的事件,具体日志要记录到什么地方,需要Appender的支持。在Log4J中,Appender定义了日志输出的目的地。在上面所有的示例当中,我们日志输出的目的地都是控制台,在Log4j中,还有非常多的Appender可供选择,可以将日志输出到文件,网络,数据库等等,这个后面再介绍。说到这里,可能有人就已经会思考,既然Logger对象的info()等方法仅仅是发出了日志记录的事件,还需要指定输出目的地;那么我们之前的示例代码也并没有为任何一个Logger设置Appender啊?其实这很好理解,我们回顾一下之前的Level,按道理,也应该为每一个Logger指定对应的日志输出级别,但是我们也并没有这样做,正是因为Logger本身存在一个完整的体系结构,而Level能够在这个结构中自下而上的继承。同理,Appender也具有这种继承的特性。下面给出一段代码,先看看怎么使用代码的方式指定Appender:

    @Test
    public void testLogAppender1(){
        Logger log1=Logger.getLogger("cd");
        log1.setLevel(Level.DEBUG);
        log1.addAppender(new ConsoleAppender(new SimpleLayout()));
        Logger log2=Logger.getLogger("cd.itcast.log");
        log2.info("log2 info");
        log2.debug("log2 debug");
    }
    

    注意在这段代码中的加粗的代码。第一句代码设置了日志级别为DEBUG;第二条代码,调用了Logger的addAppender 方法添加了一个ConsoleAppender;从类的名字上看就知道这是一个把日志输出到控制台上的Appender,在创建ConsoleAppender的时候,又传入了一个SimpleLayout的实例;关于Layout下面再介绍,现在只需要关注Appender的继承特性。接下来,又创建了一个cd.itcast.log的子Logger;并且使用这个Logger输出了两条日志信息。运行测试,输出:

    INFO - log2 info
    DEBUG - log2 debug
    

    请注意这个输出,很明显已经和之前的输出信息的格式完全不一样了,这里的输出格式就是由我们在cd这个Logger上面设置的ConsoleAppender+SimpleLayout所规定的。从这个例子中,我们可以看到,我们改变了cd这个Logger的Appender;他下面的子Logger自然就继承了这个Appender,输出了另外一种格式的信息。从这段代码中,我们能看出Logger的继承性,假如我们把代码修改为以下这样:

    @Test
    public void testLogAppender1(){
        BasicConfigurator.configure();
        Logger log1=Logger.getLogger("cd");
        log1.setLevel(Level.DEBUG);
        log1.addAppender(new ConsoleAppender(new SimpleLayout()));
        Logger log2=Logger.getLogger("cd.itcast.log");
        log2.info("log2 info");
        log2.debug("log2 debug");
    }
    

    在这段代码中,我们仅仅只是添加了BasicConfigurator来完成一个基本的配置。我们先来分析下这段代码。首先我们完成了基本的配置,从前面的测试代码中,我们可以知道,这条代码为rootLogger设置了一个DEBUG的Level;另外,现在我们知道了,这条代码肯定还为rootLogger添加了一个ConsoleAppender。然后我们创建了名字为cd的Logger,并且另外添加了一个Appender;接着又创建了名字为cd.itcast.log的Logger,最后使用这个Logger输出了两条日志信息。根据前面的Level的表现,我们猜想,当使用cd.itcast.log这个Logger做日志的时候,因为这个Logger本身是没有添加任何Appender,所以他会向上查询任何一个添加了Appender的父Logger,即找到了cd这个Logger,最后使用cd这个Logger完成日志,那么我们预计的结果是这段代码和上一段代码输出相同。

    我们来运行一下这段代码,输出:

    INFO - log2 info
    0 [main] INFO cd.itcast.log  - log2 info
    DEBUG - log2 debug
    0 [main] DEBUG cd.itcast.log  - log2 debug
    

    和我们预测的结果不一样。log2 info和log2 debug分别被输出了两次。我们观察结果,两条日志的输出都是先有一条ConsoleAppender+SimpleLayout的方式输出的然后紧跟一条使用BasicConfigurator的输出方式。那我们就能大胆的猜测了,Logger上的Appender不光能继承其父Logger上的Appender,更重要的是,他不光只继承一个,而是只要是其父Logger,其上指定的Appender都会追加到这个子Logger之上。所以,这个例子中,cd.itcast.log这个Logger不光继承了cd这个Logger上的Appender,还得到了rootLogger上的Appender;所以输出了这样的结果。在Log4J中,这个特性叫做Appender的追加性。默认情况下,所有的Logger都自动具有追加性,通过一个表来说明:

    输入图片说明

    但是,在某些情况下,这样做反而会引起日志输出的混乱。有些时候,我们并不希望Logger具有追加性。比如在上面这张表中,我们想让cd.itcast.log只需要继承A2和自己的A3Appender,而不想使用root上面的A1 Appender,又该怎么做呢?

    其实很简单,在Logger上,都有一个setAdditivity方法,如果设置setAdditivity为false,则该logger的子类停止追加该logger之上的Appender;如果设置为true,则具有追加性。修改一下上表:

    输入图片说明

    再来一段代码看看是否如此:

    @Test
    public void testLogAppender2() throws Exception{
        BasicConfigurator.configure();
        Logger log1=Logger.getLogger("cd");
        log1.setAdditivity(false);
        log1.addAppender(new ConsoleAppender(new SimpleLayout()));
     
        Logger log2=Logger.getLogger("cd.itcast");
        log2.addAppender(new FileAppender(new SimpleLayout(),"a0.log"));
     
        Logger log3=Logger.getLogger("cd.itcast.log");
        log3.info("log2 info");
    }
    

    先来分析这段代码,在这段代码中有一些新的知识,简单理解即可。首先,我们使用BasicConfigurator.configure()方法配置了rootLogger;接着定义了名称为cd的Logger;并为其添加了一个ConsoleAppender,但是这里,我们这里设置了additivity为false,即cd和cd之后的logger都不会再添加rootLogger的Appender了。接下来,我们创建了cd.itcast这个Logger,并且为这个Logger指定了一个FileAppender。FileAppender很简单,除了同样要指定一个Layout,这个在后面介绍,第二个参数还需要指定输出日志的名称;最后,我们创建了cd.itcast.log,并使用这个Logger输出日志。按照上面的表所展示的规律,因为cd.itcast.log没有指定任何的Appender,所以向上查询。找到cd.itcast,cd.itcast.log得到其上的FileAppender,因为cd.itcast没有设置additivity,默认为true,继续向上查找,cd.itcast.log会得到cd的ConsoleAppender;但是因为cd设置了additivity为false,所以不再向上查询,最后,cd.itcast.log会向FileAppender和ConsoleAppender输出日志。

    运行测试,结果:

    INFO - log2 info
    

    并且在应用下增加一个a0.log文件,内容为INFO - log2 info。符合我们的预期。

    在Log4J中,一个Logger可以添加多个Appender,不管是通过继承的方式还是通过调用Logger.addAppender方法添加。只要添加到了某一个Logger之上,在这个Logger之上的任何一个可以被输出的日志都会分别输出到所有的Appender之上。

    5.3 docLayout的结构

    Logger规定了输出什么日志,Appender规定了日志输出到哪里,当然,我们还会奢望,以什么样的方式输出日志。这就涉及到之前我们在观察Appender的时候创建ConsoleAppender和FileAppender都需要传入的Layout。在Log4J中,Layout对象提供了以什么样的方式格式化日志。这个对象是绑定在Appender之上的,一般在Appender创建的时候指定。

    下面就简单看一个最常使用的Layout:PatternLayout。PatternLayout允许使用标准的输出格式来指定格式化日志消息的样式。举个简单的例子,可能之前大家看到的使用BasicConfigurator配置的rootLogger输出的日志样式和我们使用ConsoleAppender(new SimpleLayout)创建的输出样式完全不一样。那大抵类似:

    0 [main] INFO cd.itcast.core.LogicProcessor  - process some logic
    

    那这样的日志输出样式是什么样子的呢?一个代码:

    @Test
    public void testPatternLayout(){
        Logger log=Logger.getLogger("cd");
        String pattern="%r [%t] %-5p %c - %m%n";
        log.addAppender(new ConsoleAppender(new PatternLayout(pattern)));
        Logger log2=Logger.getLogger("cd.itcast.log");
        log2.info("log2 info");
    }
    

    运行测试输出:

    0 [main] INFO  cd.itcast.log - log2 info
    

    符合我们的预期。注意粗体字。首先定义了一个格式化日志的模式,在这个模式中,有很多以%开头的参数,每一个特定的参数代表着一种日志的内容。比如%r代表从Log4j启动到运行这条日志的时间差,单位为毫秒;第二个参数%t代表运行该日志的线程名称;第三个参数%-5p,首先-5代表这个字符总占用5个位置,p代表日志输出级别;%c代表输出日志的Logger的名字;%m代表输出的日志内容;%n代表分行。可以看到,其实PatternLayout的使用是非常简单的,只需要了解所有内置的参数和其表示的含义,再按照想要的方式输出即可。具体日志格式化参数,请参见Properties配置文件。

    5.4 三个组件的使用

    前面简单的了解了Log4J中最重要的3个组件,下面我们来看看Log4j是怎么使用这3个组件完成当我们调用logger.debug()方法能在控制台上打印出日志信息的。

    第一步,继承数体系上的门槛检查:首先当调用info()方法后,Log4J会立刻使用该Logger所在的体系结构中设置的门槛去检查当前日志的级别。如果级别不够,立刻作废当前日志请求。
    第二步,Level级别检查:使用当前Logger上设置的或者继承的Level级别来检查当前的日志级别。如果当前日志级别不够,立刻作废当前日志请求。
    第三步,创建LoggingEvent对象:当日志审核通过,Log4J就会创建一个LoggingEvent对象(即日志事件对象)。在该对象中,会保存和本次日志相关的所有参数信息,包括日志内容,日志时间等。
    第四步,执行Appender:当创建完成LoggingEvent对象时候,会该对象交给当前logger上起作用的所有的Appender对象,并调用这些对象的doAppend方法来处理日志消息。
    第五步,格式化日志消息:接下来,会使用每一个Appender绑定的Layout对象(如果有)来格式化日志消息。Layout对象会把LoggingEvent格式化成最终准备输出的String。
    第六步,输出日志消息:当得到最终要输出的String对象之后,appender会把字符输出到最终的目标上,比如控制台或者文件。

    三个主要组件的组成结构:
    输入图片说明

    日志执行流程图:
    输入图片说明

    日志类结构图:
    输入图片说明

    5.5 Log4j的配置文件

    通过前面的示例,我们已经对Log4j的使用方式有了一定的了解。我们再返回去看我们第二个稍微复杂的例子。单看Configure和LogicProcessor两个类,从代码的角度来看,在这两个类中硬编码日志,是没有问题的,也是没法优化的,比如在代码中添加log.info(string),这种代码是必要的;在这种情况下,我应用中所有的类必要引入的也就只是一个Logger类,引入这个复杂性也是我们能够控制的。但是最重要的注意力,我们来思考,在真正一个应用当中,我们的测试类又该怎么表现呢?通过最开始的示例代码,我们已经知道,要正常的运行Log4J的日志功能,必须要至少对rootLogger进行相关的配,之前随着我们的代码的复杂度越来越高,我们发现,要能够控制我们的日志的输出级别,各个模块的日志输出控制,我们必须要在测试代码中加入大量的Log4J的代码;比如设定Level,Threshold,Appender,Layout,Pattern等等代码,换句话说,如果这些代码都必须硬编码到程序中,我们必须要在我们的应用启动的时候就执行这些代码,比如在WEB环境中,就要使用ServletContextListener等来初始化Log4J环境,或者在我们的桌面应用的启动过程中,使用这些代码来控制Log4J的初始化。这样做的后果是,虽然我们也能达到统一控制日志输出的目的,但是我们仍然需要使用大量的代码来控制这些内容;也没法达到一种灵活统一配置的方式,因为我们知道,在大部分的情况下,使用配置文件的方式肯定优于代码的配置方式。

    庆幸的是,Log4J远远不止能使用代码来完成配置,他还提供了更多更灵活的配置方式,比如我们接下来要介绍的properties和XML的配置。

    6 Log4j架构分析

    6.1 组件介绍

    1)Logger:负责供客户端代码调用,执行debug(Object msg)、info(Object msg)、warn(Object msg)、error(Object msg)等方法。
    输入图片说明

    Logger 继承自 Category,Logger有三个子类:

    • org.apache.log4j.spi.RootLogger。这是默认的Logger类。
    • org.apache.log4j.spi.NOPLogger。这个类针对日志的输出不做任何操作,直接丢弃。
    • org.apache.log4j.spi.RootCategory。此类已经不推荐使用,用RootLogger代替。

    Logger 一共有如下日志级别:

    • DEBUG
    • INFO
    • WARN
    • ERROR
    • FATAL

    另外,还有两个特殊的日志级别:

    • ALL 所有的日志输出,不论其级别。
    • OFF 所有日志一律不输出,与ALL相反。

    2)Appender:负责日志的输出,Log4j已经实现了多种不同目标的输出方式,可以向文件输出日志、向控制台输出日志、向Socket输出日志等。当前Log4j中Appender的子类实现及其层次结构如下:
    输入图片说明
    在这里插入图片描述
    3)Layout:负责日志信息的格式化。
    在这里插入图片描述

    已经实现的类有:

    输入图片说明输入图片说明在这里插入图片描述

    6.2 执行顺序及关系

    调用Log4j输出日志时,调用各个组件的顺序如下图所示:

    1)日志信息传入 Logger。
    2)将日志信息封装成 LoggingEvent 对象并传入 Appender。
    3)在 Appender 中调用 Filter 对日志信息进行过滤,调用 Layout 对日志信息进行格式化,然后输出。
    输入图片说明

    6.3 Log4j初始化

    下面分三个步骤来介绍Log4j的初始化:

    getLogger(String):在代码中我们以如下的代码来使用Log4j,如下:

    private Logger _logger = Logger.getLogger(Hello.class);
    

    1)在执行Logger.getLogger(Class)方法,在Log4j内部会执行一系列方法。其执行序列图如下所示: 输入图片说明

    2)LogManager初始化:如果是第一次调用LogManager,这时Log4j会读取配置文件并对自身初始化。其执行序列图如下: 输入图片说明

    其中:

    • Hierarchy 实现了 LoggerRepository 接口。
    • RootLogger 实现了 Logger 接口。
    • DefaultRepositorySelector 实现了 RepositorySelector 接口。
    • getSystemProperty(String key, String def)) 这一步骤中获取系统变量log4j.defaultInitOverride、log4j.configuration、log4j.configuratorClass的值。log4j已经不推荐设置这些系统变
    • getResource(String) 这一步骤中获取log4j.xml、log4j.properties配置文件。首先获取log4j.xml,然后再获取log4j.properties,如果在log4j.xml 和log4j.properties 都获取失败的情况下会用 log4j.configuration来代替(如果log4j.configration存在的话)。
    • selectAndConfigure 这一步骤中,如果class 为空,并且url 指向的文件名后缀为xml,则将class设置为“org.apache.log4j.xml.DOMConfigurator”,否则新建一个“org.apache.log4j.PropertyConfigurator”的实例。

    3)解析配置文件:获得一个Configurator实例后,调用它的 doConfigure(Url url, LoggerRepository repository)方法传入配置文件完事路径,然后解析配置文件内容。下面以 log4j.xml 为例来介绍配置文件的解析过程:
    在这里插入图片描述

    4)在执行 doConfigure(ParseAction action, LoggerRepository repository) 方法时,会通过获取系统的环境变量 javax.xml.parsers.DocumentBuilderFactory 获得XML的Document解析器对配置文件log4j.xml进行解析。

    5)parse(Element element)的关键代码如下。

    String tagName = null;
    Element currentElement = null;
    Node currentNode = null;
    NodeList children = element.getChildNodes();
    final int length = children.getLength();
    for (int loop = 0; loop < length; loop++) {
        currentNode = children.item(loop);
        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
            currentElement = (Element) currentNode;
            tagName = currentElement.getTagName();
            if (tagName.equals(CATEGORY_FACTORY_TAG)
                || tagName.equals(LOGGER_FACTORY_TAG)) {
                parseCategoryFactory(currentElement);
            }
        }
    }
    for (int loop = 0; loop < length; loop++) {
        currentNode = children.item(loop);
        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
            currentElement = (Element) currentNode;
            tagName = currentElement.getTagName();
            if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
                parseCategory(currentElement);
            } else if (tagName.equals(ROOT_TAG)) {
                parseRoot(currentElement);
            } else if (tagName.equals(RENDERER_TAG)) {
                parseRenderer(currentElement);
            } else if (tagName.equals(THROWABLE_RENDERER_TAG)) {
            if (repository instanceof ThrowableRendererSupport) {
                ThrowableRenderer tr = parseThrowableRenderer(currentElement);
                if (tr != null) {
                    ((ThrowableRendererSupport) repository)
                        .setThrowableRenderer(tr);
                }
            }
        } else if (!(tagName.equals(APPENDER_TAG)
            || tagName.equals(CATEGORY_FACTORY_TAG) || tagName
            .equals(LOGGER_FACTORY_TAG))) {
            quietParseUnrecognizedElement(repository, currentElement,
              props);
        }
    }
    
    • 1.获取根元素的 tagName。
      1)如果 tagName 不是 log4j:configuration 并且 不是已经不推荐的 configuration,输出错误信息。
      2)如果 tagName 不是 log4j:configuration 是已经不推荐的 configuration,输出警告信息。

    • 2.获取根元素的 debug 属性。
      1)如果 debug 属性的值不等于"“且不等于"null”,调用 LogLog.setInternalDebugging(boolean),否则输出调试信息。

    • 3.获取根元素的 reset 属性。
      1)如果 reset 属性的值 不等于"“且等于"true”,调用 repository.resetConfiguration()。

    • 4.获取根元素的 threshold 属性。
      1)如果 threshold 属性不等于"“且不等于"null”,调用 repository.setThreshold(String)。

    • 5.循环处理根元素的所有子元素。
      1)如果子元素是 categoryFactory 或 loggerFactory,则调用内部方法 parseCategoryFactory(Element factoryElement) 处理;

    • 6.再次循环处理根元素的所有子元素。
      1)如果子元素是 category 或 logger,调用内部方法 parseCategory(Element loggerElement) 处理。
      2)如果子元素是 root,调用内部方法 parseRoot(Element rootElement) 处理。
      3)如果子元素是 renderer,调用内部方法 parseRenderer(Element rootElement) 处理。
      4)如果子元素是 throwableRenderer 并且repository instanceof ThrowableRendererSupport,调用内部方法 parseThrowableRenderer(Element rootElement) 处理返回一个ThrowableRenderer,如果没有返回null,调用((ThrowableRendererSupport) repository).setThrowableRenderer(ThrowableRenderer)。
      5)如果子元素是 appender 或者 categoryFactory 或者 loggerFactory,调用内部方法 quietParseUnrecognizedElement(Object instance, Element element, Properties props) 处理。

    6.4 Log4j输出日志

    Log4j输出日志分为六个步骤:全局开关控制、日志等级过滤、封装日志信息、过滤器处理、日志信息格式化、输出至文件。下面分两个环节来介绍这六个步骤是如何实现的:

    第一环节:预处理,当调用Log4j的方法(如:debug(String, Throwable)、info(String, Throwable))输出日志时,首先对日志信息进行预处理,其序列图如下; 输入图片说明

    说明:

    1)isDisabled(int level):根据全局日志等级threshold进行判断,如果日志等级低于threshold,不输出日志。
    2)isGreaterOrEquals(Priority r):根据当前logger配置的日志等级level进行判断,如果日志等级低于当前logger配置的日志等级,不输出日志。
    3)foredLog(String fqcn Priority level, Object message, Throwable t):将日志信息封装成LoggingEvent对象。
    4)callAppenders(LoggingEvent event):将LoggingEvent对象分发给所有的Appender。其实现代码如下:

    public void callAppenders(LoggingEvent event) {
        int writes = 0;
     
        for (Category c = this; c != null; c = c.parent) {
            // Protected against simultaneous call to addAppender,
            // removeAppender,...
            synchronized (c) {
                if (c.aai != null) {
                    writes += c.aai.appendLoopOnAppenders(event);
                }
                if (!c.additive) {
                    break;
                }
            }
        }
     
        if (writes == 0) {
            repository.emitNoAppenderWarning(this);
        }
    }
        public int appendLoopOnAppenders(LoggingEvent event) {
        int size = 0;
        Appender appender;
     
        if (appenderList != null) {
            size = appenderList.size();
            for (int i = 0; i < size; i++) {
                appender = (Appender) appenderList.elementAt(i);
                appender.doAppend(event);
            }
        }
        return size;
    }
    

    第二环节:输出日志。输出日志前还有两道工序需要处理:Filter处理和日志信息格式化。其执行序列图如下: 输入图片说明

    相应的代码如下:

    public synchronized void doAppend(LoggingEvent event) {
        if (closed) {
            LogLog.error("Attempted to append to closed appender named ["
                    + name + "].");
            return;
        }
     
        if (!isAsSevereAsThreshold(event.getLevel())) {
            return;
        }
     
        Filter f = this.headFilter;
     
        FILTER_LOOP: while (f != null) {
            switch (f.decide(event)) {
     
            case Filter.DENY:
                return;
            case Filter.ACCEPT:
                break FILTER_LOOP;
            case Filter.NEUTRAL:
                f = f.getNext();
            }
        }
     
        this.append(event);
    }
     
    protected void subAppend(LoggingEvent event) {
        this.qw.write(this.layout.format(event));
     
        if (layout.ignoresThrowable()) {
            String[] s = event.getThrowableStrRep();
            if (s != null) {
                int len = s.length;
                for (int i = 0; i < len; i++) {
                    this.qw.write(s[i]);
                    this.qw.write(Layout.LINE_SEP);
                }
            }
        }
     
        if (shouldFlush(event)) {
            this.qw.flush();
        }
    }
    

    说明:

    1)decide(LoggingEvent event):有三种返回值 DENY、ACCEPT、NEUTRAL,DENY表示丢弃当前日志信息,ACCEPT表示输出当前日志信息,NEUTRAL表示继续下一个Filter。Filter只能在XML配置文件中使用,Properties文件中不支持。
    2)format(LoggingEvent event):对日志进行格式化处理。
    3)write(String string):将日志信息输出至目的地(文件、数据库或网格)。

    7.Log4j性能优化

    1)log4j已成为大型系统必不可少的一部分,log4j可以很方便的帮助我们在程序的任何位置输出所要打印的信息,便于我们对系统在调试阶段和正式运行阶段对问题分析和定位。由于日志级别的不同,对系统的性能影响也是有很大的差距,日志级别越高,性能越高。

    2)log4j对系统性能的影响程度主要体现在以下几方面:

    3)日志输出的目的地,输出到控制台的速度比输出到文件系统的速度要慢。

    4)日志输出格式不一样对性能也会有影响,如简单输出布局(SimpleLayout)比格式化输出布局(PatternLayout)输出速度要快。可以根据需要尽量采用简单输出布局格式输出日志信息。

    5)日志级别越低输出的日志内容就越多,对系统系能影响很大。

    6)日志输出方式的不同,对系统系能也是有一定影响的,采用异步输出方式比同步输出方式性能要高。

    7)每次接收到日志输出事件就打印一条日志内容比当日志内容达到一定大小时打印系能要低。

    1.针对以上几点对系能的影响中的第4,5点,对日志配置文件做如下配置:

    设置日志缓存,以及缓存大小
    log4j.appender.A3.BufferedIO=true
    #Buffer单位为字节,默认是8K,IO BLOCK大小默认也是8K
    log4j.appender.A3.BufferSize=8192
    

    以上配置说明,当日志内容达到8k时,才会将日志输出到日志输出目的地。

    2.设置日志输出为异步方式

     <appender name="DRFOUT" class="org.apache.log4j.DailyRollingFileAppender">
            <param name="File" value="logs/brws.log" />
            <param name="Append" value="true" />
            <param name="DatePattern" value="yyyy_MM_dd'.'" />
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%d [%t] %-5p %l %x - %m%n" />
            </layout>
        </appender>
        <appender name="ASYNCOUT" class="org.apache.log4j.AsyncAppender">
            <param name="BufferSize" value="512" />
            <appender-ref ref="DRFOUT" />
        </appender>
    

    同步情况:各线程直接获得输出流进行输出(线程间不需要同步)。

    异步情况:

    1)各线程将日志写到缓存,继续执行下面的任务(这里是异步的)。
    2)日志线程发现需要记日志时独占缓存(与此同时各线程等待,此时各线程是被阻塞住的),从缓存中取出日志信息,获得输出流进行输出,将缓存解锁(各线程收到提醒,可以接着写日志了)。

    众所周知,磁盘IO操作、网络IO操作、JDBC操作等都是非常耗时的,日志输出的主要性能瓶颈也就是在写文件、写网络、写JDBC的时候。日志是肯定要记的,而要采用异步方式记,也就只有将这些耗时操作从主线程当中分离出去才真正的实现性能提升,也只有在线程间同步开销小于耗时操作时使用异步方式才真正有效!

    现在我们接着分别来看看这几种记录日志的方式:

    1)将日志记录到本地文件 同样都是写本地文件Log4j本身有一个buffer处理入库,采用异步方式并不一定能提高性能(主要是如何配置好缓存大小);而线程间的同步开销则是非常大的!因此在使用本地文件记录日志时不建议使用异步方式。

    2)将日志记录到JMS JMS本身是支持异步消息的,如果不考虑JMS消息创建的开销,也不建议使用异步方式。

    3)将日子记录到SOCKET 将日志通过Socket发送,纯网络IO操作不需要反馈,因此也不会耗时。

    4)将日志记录到数据库 众所周知JDBC是几种方式中最耗时的:网络、磁盘、数据库事务,都使JDBC操作异常的耗时,在这里采用异步方式入库倒是一个不错的选择。

    5)将日志记录到SMTP 同JDBC。

    异步输出日志工作原理:AsyncAppender采用的是生产者消费者的模型进行异步地将Logging Event送到对应的Appender中。

    生产者:外部应用了Log4j的系统的实时线程,实时将Logging Event传送进AsyncAppender里。

    中转:Buffer和DiscardSummary。

    消费者:Dispatcher线程和appenders。

    工作原理:

    1) Logging Event进入AsyncAppender,AsyncAppender会调用append方法,在append方法中会去把logging Event填入Buffer中,当消费能力不如生产能力时,AsyncAppender会把超出Buffer容量的Logging Event放到DiscardSummary中,作为消费速度一旦跟不上生成速度,中转buffer的溢出处理的一种方案。

    2) AsyncAppender有个线程类Dispatcher,它是一个简单的线程类,实现了Runnable接口。它是AsyncAppender的后台线程。

    Dispatcher所要做的工作是:

    ① 锁定Buffer,让其他要对Buffer进行操作的线程阻塞。

    ② 看Buffer的容量是否满了,如果满了就将Buffer中的Logging Event全部取出,并清空Buffer和DiscardSummary;如果没满则等待Buffer填满Logging Event,然后notify Disaptcher线程。

    ③ 将取出的所有Logging Event交给对应appender进行后面的日志信息推送。

    以上是AsyncAppender类的两个关键点:append方法和Dispatcher类,通过这两个关键点实现了异步推送日志信息的功能,这样如果大量的Logging Event进入AsyncAppender,就可以游刃有余地处理这些日志信息了。

    日志框架4:log4j配置_Cape_sir-CSDN博客

    1.日志级别

    • 一般日志级别包括:ALL,DEBUG, INFO, WARN, ERROR,FATAL,OFF
    • Log4J推荐使用:DEBUG, INFO,WARN, ERROR

    OFF: 为最高等级 关闭了日志信息
    FATAL: 为可能导致应用中止的严重事件错误
    ERROR:为严重错误 主要是程序的错误
    WARN: 为一般警告,比如session丢失
    INFO: 为一般要显示的信息,比如登录登出 DEBUG 为程序的调试信息
    TRACE: 为比DEBUG更细粒度的事件信息 ALL 为最低等级,将打开所有级别的日志

    2.Appender

    Appender:日志输出器,配置日志的输出级别、输出位置等,包括以下几类:

    • ConsoleAppender: 日志输出到控制台;
    • FileAppender:输出到文件;
    • RollingFileAppender:输出到文件,文件达到一定阈值时,自动备份日志文件;
    • DailyRollingFileAppender:可定期备份日志文件,默认一天一个文件,也可设置为每分钟一个、每小时一个;
    • WriterAppender:可自定义日志输出位置。

    配置日志信息输出目的地

    1.org.apache.log4j.ConsoleAppender(控制台)
    2.org.apache.log4j.FileAppender(文件)
    3.org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
    4.org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
    5.org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

    3.输出格式

    1.org.apache.log4j.HTMLLayout(以HTML表格形式布局),
    2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
    3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
    4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

    Log4J最常用的日志输出格式为:org.apache.log4j.PatternLayOut,其主要参数为:

    %n - 换行
    %m - 日志内容
    %p - 日志级别(FATAL, ERROR,WARN, INFO,DEBUG or custom)
    %r - 程序启动到现在的毫秒数
    %t - 当前线程名
    %d - 日期和时间, 一般使用格式 %d{yyyy-MM-dd HH:mm:ss,SSS}
    %l - 输出日志事件的发生位置, 同 %F%L%C%M
    %F - java 源文件名
    %L - java 源码行数
    %C - java 类名,%C{1} 输出最后一个元素
    %M - java 方法名

    详细配置参数如下表:

    参数说明例子
    %c 列出logger名字空间的全称,如果加上{<层数>}表示列出从最内层算起的指定层数的名字空间 log4j配置文件参数举例 输出显示媒介
    假设当前logger名字空间是"a.b.c"
    %c a.b.c
    %c{2} b.c
    %20c (若名字空间长度小于20,则左边用空格填充)
    %-20c (若名字空间长度小于20,则右边用空格填充)
    %.30c (若名字空间长度超过30,截去多余字符)
    %20.30c (若名字空间长度小于20,则左边用空格填充;若名字空间长度超过30,截去多余字符)
    %-20.30c (若名字空间长度小于20,则右边用空格填充;若名字空间长度超过30,截去多余字符)
    %C 列出调用logger的类的全名(包含包路径) 假设当前类是"org.apache.xyz.SomeClass"
    %C org.apache.xyz.SomeClass
    %C{1} SomeClass
    %d 显示日志记录时间,{<日期格式>}使用ISO8601定义的日期格式 %d{yyyy/MM/dd HH:mm:ss,SSS} 2005/10/12 22:23:30,117
    %d{ABSOLUTE} 22:23:30,117
    %d{DATE} 12 Oct 2005 22:23:30,117
    %d{ISO8601} 2005-10-12 22:23:30,117
    %F 显示调用logger的源文件名 %F MyClass.java
    %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 %l MyClass.main(MyClass.java:129)
    %L 显示调用logger的代码行 %L 129
    %m 显示输出消息 %m This is a message for debug.
    %M 显示调用logger的方法名 %M main
    %n 当前平台下的换行符 %n Windows平台下表示rn
    UNIX平台下表示n
    %p 显示该条日志的优先级 %p INFO
    %r 显示从程序启动时到记录该条日志时已经经过的毫秒数 %r 1215
    %t 输出产生该日志事件的线程名 %t MyClass
    %x 按NDC(Nested Diagnostic Context,线程堆栈)顺序输出日志 假设某程序调用顺序是MyApp调用com.foo.Bar
    %c %x - %m%n MyApp - Call com.foo.Bar.
    com.foo.Bar - Log in Bar
    MyApp - Return to MyApp.
    %X 按MDC(Mapped Diagnostic Context,线程映射表)输出日志。通常用于多个客户端连接同一台服务器,方便服务器区分是那个客户端访问留下来的日志。 %X{5} (记录代号为5的客户端的日志)
    %% 显示一个百分号 %% %

    4.举例说明

    4.1 log4j.xml举例

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "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="[%p][%d{yyyy-MM-dd HH:mm:ss SSS}][%c]-[%m]%n"/>
            </layout>
     
            <!--过滤器设置输出的级别-->
            <filter class="org.apache.log4j.varia.LevelRangeFilter">
                <!-- 设置日志输出的最小级别 -->
                <param name="levelMin" value="INFO"/>
                <!-- 设置日志输出的最大级别 -->
                <param name="levelMax" value="ERROR"/>
            </filter>
        </appender>
     
     
        <!-- 输出日志到文件 -->
        <appender name="fileAppender" class="org.apache.log4j.FileAppender">
            <!-- 输出文件全路径名-->
            <param name="File" value="/data/applogs/own/fileAppender.log"/>
            <!--是否在已存在的文件追加写:默认时true,若为false则每次启动都会删除并重新新建文件-->
            <param name="Append" value="false"/>
            <param name="Threshold" value="INFO"/>
            <!--是否启用缓存,默认false-->
            <param name="BufferedIO" value="false"/>
            <!--缓存大小,依赖上一个参数(bufferedIO), 默认缓存大小8K  -->
            <param name="BufferSize" value="512"/>
            <!-- 日志输出格式 -->
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="[%p][%d{yyyy-MM-dd HH:mm:ss SSS}][%c]-[%m]%n"/>
            </layout>
        </appender>
     
     
        <!-- 输出日志到文件,当文件大小达到一定阈值时,自动备份 -->
        <!-- FileAppender子类 -->
        <appender name="rollingAppender" class="org.apache.log4j.RollingFileAppender">
            <!-- 日志文件全路径名 -->
            <param name="File" value="/data/applogs/RollingFileAppender.log" />
            <!--是否在已存在的文件追加写:默认时true,若为false则每次启动都会删除并重新新建文件-->
            <param name="Append" value="true" />
            <!-- 保存备份日志的最大个数,默认值是:1  -->
            <param name="MaxBackupIndex" value="10" />
            <!-- 设置当日志文件达到此阈值的时候自动回滚,单位可以是KB,MB,GB,默认单位是KB,默认值是:10MB -->
            <param name="MaxFileSize" value="10KB" />
            <!-- 设置日志输出的样式 -->`
            <layout class="org.apache.log4j.PatternLayout">
                <!-- 日志输出格式 -->
                <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5p] [method:%l]%n%m%n%n" />
            </layout>
        </appender>
     
     
        <!-- 日志输出到文件,可以配置多久产生一个新的日志信息文件 -->
        <appender name="dailyRollingAppender" class="org.apache.log4j.DailyRollingFileAppender">
            <!-- 文件文件全路径名 -->
            <param name="File" value="/data/applogs/own/dailyRollingAppender.log"/>
            <param name="Append" value="true" />
            <!-- 设置日志备份频率,默认:为每天一个日志文件 -->
            <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
     
            <!--每分钟一个备份-->
            <!--<param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm'.log'" />-->
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="[%p][%d{HH:mm:ss SSS}][%c]-[%m]%n"/>
            </layout>
        </appender>
     
     
     
        <!--
            1. 指定logger的设置,additivity是否遵循缺省的继承机制
            2. 当additivity="false"时,root中的配置就失灵了,不遵循缺省的继承机制
            3. 代码中使用Logger.getLogger("logTest")获得此输出器,且不会使用根输出器
        -->
        <logger name="logTest" additivity="false">
            <level value ="INFO"/>
            <appender-ref ref="dailyRollingAppender"/>
        </logger>
     
     
        <!-- 根logger的设置,若代码中未找到指定的logger,则会根据继承机制,使用根logger-->
        <root>
            <appender-ref ref="console"/>
            <appender-ref ref="fileAppender"/>
            <appender-ref ref="rollingAppender"/>
            <appender-ref ref="dailyRollingAppender"/>
        </root>
     
    </log4j:configuration>
    

    4.2 log4j.properties举例

    #############
    # 输出到控制台
    #############
    
    # log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
    # WARN:日志级别     CONSOLE:输出位置自己定义的一个名字       logfile:输出位置自己定义的一个名字
    log4j.rootLogger=WARN,CONSOLE,logfile
    # 配置CONSOLE输出到控制台
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 
    # 配置CONSOLE设置为自定义布局模式
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 
    # 配置CONSOLE日志的输出格式  [frame] 2019-08-22 22:52:12,000  %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
    log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n
    
    ################
    # 输出到日志文件中
    ################
    
    # 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
    log4j.appender.logfile=org.apache.log4j.RollingFileAppender
    # 保存编码格式
    log4j.appender.logfile.Encoding=UTF-8
    # 输出文件位置此为项目根目录下的logs文件夹中
    log4j.appender.logfile.File=logs/root.log
    # 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
    log4j.appender.logfile.MaxFileSize=10MB
    # 设置滚定文件的最大值3 指可以产生root.log.1、root.log.2、root.log.3和root.log四个日志文件
    log4j.appender.logfile.MaxBackupIndex=3  
    # 配置logfile为自定义布局模式
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
    
    ##########################
    # 对不同的类输出不同的日志文件
    ##########################
    
    # club.bagedate包下的日志单独输出
    log4j.logger.club.bagedate=DEBUG,bagedate
    # 设置为false该日志信息就不会加入到rootLogger中了
    log4j.additivity.club.bagedate=false
    # 下面就和上面配置一样了
    log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
    log4j.appender.bagedate.Encoding=UTF-8
    log4j.appender.bagedate.File=logs/bagedate.log
    log4j.appender.bagedate.MaxFileSize=10MB
    log4j.appender.bagedate.MaxBackupIndex=3
    log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
    log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

    日志框架5:logback_Cape_sir-CSDN博客

    1.Logback介绍

    Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块:logback-core,logback-classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。

    本文章用到的组件如下:请自行到Maven仓库下载!

    logback-access-1.0.0.jar
    logback-classic-1.0.0.jar
    logback-core-1.0.0.jar
    slf4j-api-1.6.0.jar

    maven配置: 这样依赖包全部自动下载了!

    <dependency>  
        <groupId>ch.qos.logback</groupId>  
        <artifactId>logback-classic</artifactId>  
        <version>1.0.11</version>  
    </dependency>
    

    2.Logback配置

    2.1 Logger、Appender及Layout

    Logback建立于三个主要类之上:Logger、Appender 和 Layout。Logger类是logback-classic模块的一部分,而Appender和Layout接口来自logback-core。作为一个多用途模块,logback-core不包含任何logger。

    Logger作为日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。

    Appender主要用于指定日志输出的目的地,目的地可以是控制台、文件、远程套接字服务器、 MySQL、 PostreSQL、 Oracle和其他数据库、 JMS和远程UNIX Syslog守护进程等。

    Layout负责把事件转换成字符串,格式化的日志信息的输出。

    2.2 LoggerContext

    各个logger 都被关联到一个 LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各 logger。如果 logger的名称带上一个点号后是另外一个 logger的名称的前缀,那么,前者就被称为后者的祖先。如果logger与其后代 logger之间没有其他祖先,那么,前者就被称为子logger 之父。比如,名为"com.foo""的 logger 是名为"com.foo.Bar"之父。root logger 位于 logger 等级的最顶端,root logger 可以通过其名称取得,如下所示:

    Logger rootLogger =LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
    

    其他所有logger也通过org.slf4j.LoggerFactory 类的静态方法getLogger取得。 getLogger方法以 logger 名称为参数。用同一名字调用LoggerFactory.getLogger 方法所得到的永远都是同一个logger对象的引用。

    2.3 有效级别及级别的继承

    Logger 可以被分配级别。级别包括:TRACE、DEBUG、INFO、WARN 和 ERROR,定义于 ch.qos.logback.classic.Level类。如果 logger没有被分配级别,那么它将从有被分配级别的最近的祖先那里继承级别。root logger 默认级别是 DEBUG。

    2.4 打印方法与基本的选择规则

    打印方法决定记录请求的级别。例如,如果 L 是一个 logger 实例,那么,语句 L.info("…")是一条级别为 INFO 的记录语句。记录请求的级别在高于或等于其 logger 的有效级别时被称为被启用,否则,称为被禁用。记录请求级别为 p,其 logger的有效级别为 q,只有则当 p>=q时,该请求才会被执行。

    该规则是 logback 的核心。级别排序为: TRACE < DEBUG < INFO < WARN < ERROR。

    2.5 Logback默认配置

    如果配置文件 logback-test.xml 和 logback.xml 都不存在,那么 logback 默认地会调用BasicConfigurator ,创建一个最小化配置。最小化配置由一个关联到根 logger 的ConsoleAppender 组成。输出用模式为%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 的 PatternLayoutEncoder 进行格式化。root logger 默认级别是 DEBUG。

    1)Logback的配置文件
    Logback 配置文件的语法非常灵活。正因为灵活,所以无法用 DTD 或 XML schema 进行定义。尽管如此,可以这样描述配置文件的基本结构:以开头,后面有零个或多个元素,有零个或多个元素,有最多一个元素。

    2)Logback默认配置的步骤

    • 尝试在 classpath 下查找文件 logback-test.xml;
    • 如果文件不存在,则查找文件 logback.xml;
    • 如果两个文件都不存在,logback 用 BasicConfigurator 自动对自己进行配置,这会导致记录输出到控制台。

    3)Logback.xml 文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
        Copyright 2010-2011 The myBatis Team
        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at
            http://www.apache.org/licenses/LICENSE-2.0
        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.
    -->
    <configuration debug="false">
        <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->  
        <property name="LOG_HOME" value="/home" />  
        <!-- 控制台输出 -->   
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 
                 <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
            </encoder> 
        </appender>
        <!-- 按照每天生成日志文件 -->   
        <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">   
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern> 
                <!--日志文件保留天数-->
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>   
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
            </encoder> 
            <!--日志文件最大的大小-->
           <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
             <MaxFileSize>10MB</MaxFileSize>
           </triggeringPolicy>
        </appender> 
       <!-- show parameters for hibernate sql 专为 Hibernate 定制 --> 
        <logger name="org.hibernate.type.descriptor.sql.BasicBinder"  level="TRACE" />  
        <logger name="org.hibernate.type.descriptor.sql.BasicExtractor"  level="DEBUG" />  
        <logger name="org.hibernate.SQL" level="DEBUG" />  
        <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
        <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />  
        
        <!--myibatis log configure--> 
        <logger name="com.apache.ibatis" level="TRACE"/>
        <logger name="java.sql.Connection" level="DEBUG"/>
        <logger name="java.sql.Statement" level="DEBUG"/>
        <logger name="java.sql.PreparedStatement" level="DEBUG"/>
        
        <!-- 日志输出级别 -->
        <root level="INFO">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="FILE" />
        </root> 
         <!--日志异步到数据库 -->  
        <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
            <!--日志异步到数据库 --> 
            <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
               <!--连接池 --> 
               <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
                  <driverClass>com.mysql.jdbc.Driver</driverClass>
                  <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
                  <user>root</user>
                  <password>root</password>
                </dataSource>
            </connectionSource>
      </appender>
    </configuration>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!--
        scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
        scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
        debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
    -->
    <configuration scan="true" scanPeriod="60 seconds" debug="false">
        <property name="APP_NAME" value="base-mvc-web" />
        <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
        <property name="LOG_HOME" value="/home/taomk" />
        <!--
            每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用<contextName>设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。
         -->
        <contextName>${APP_Name}</contextName>
        <!--
            key:标识此<timestamp> 的名字;datePattern:设置将当前时间(解析配置文件的时间)转换为字符串的模式,遵循java.txt.SimpleDateFormat的格式。
        -->
        <timestamp key="BOOT_SECOND" datePattern="yyyyMMdd'T'HHmmss"/>
        <!-- 控制台输出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <!--
                encoder:对日志进行格式化,未配置class属性时,默认配置为PatternLayoutEncoder
            -->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
            <!-- 只输出level级别的日志 -->
            <filter class = "ch.qos.logback.classic.filter.LevelFilter">
                <level>INFO</level>
                <!--
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
                -->
            </filter>
        </appender>
        <!-- 按照每天生成日志文件 -->
        <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--日志文件保留天数-->
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>
            <!--
                encoder:对日志进行格式化,未配置class属性时,默认配置为PatternLayoutEncoder
            -->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
            <!--日志文件最大的大小-->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <MaxFileSize>10MB</MaxFileSize>
            </triggeringPolicy>
            <!-- 只输出level级别以上的日志 -->
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
        </appender>
        <!-- 日志异步到数据库
        <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
            <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
                <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
                    <driverClass>com.mysql.jdbc.Driver</driverClass>
                    <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
                    <user>root</user>
                    <password>root</password>
                </dataSource>
            </connectionSource>
        </appender>
        -->
        <!--
            name:用来指定受此logger约束的某一个包或者具体的某一个类。
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。
            additivity:是否向上级logger传递打印信息。默认是true。
            <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
        -->
        <logger name="net.paoding" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.RoseFilter" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.web.controllers.roseInfo" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.web.controllers.roseInfo.AccessControlInterceptor" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.web.controllers.roseInfo.TreeController" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.web.impl.thread.Rose" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.web.impl.mapping.MappingNode" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.controllers.ToolsController" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="net.paoding.rose.jade" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="org.springframework" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <logger name="org.apache" level="DEBUG">
            <appender-ref ref="STDOUT" />
        </logger>
        <!--
            <root>:也是<logger>元素,但是它是根logger。只有一个level属性,应为已经被命名为"root".
        -->
        <root level="DEBUG">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="FILE" />
        </root>
    </configuration>
    

    4)LogbackDemo.java

     package logback;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        public class LogbackDemo {
            private static Logger log = LoggerFactory.getLogger(LogbackDemo.class);
            public static void main(String[] args) {
                log.trace("======trace");
                log.debug("======debug");
                log.info("======info");
                log.warn("======warn");
                log.error("======error");	 
                String name = "Aub";
                String message = "3Q";
                String[] fruits = { "apple", "banana" };	
                // logback提供的可以使用变量的打印方式,结果为"Hello,Aub!"
                log.info("Hello,{}!", name);	
                // 可以有多个参数,结果为“Hello,Aub! 3Q!”
                log.info("Hello,{}!   {}!", name, message);    		
                // 可以传入一个数组,结果为"Fruit:  apple,banana"
                log.info("Fruit:  {},{}", fruits); 
            }
        }
    

    3.Spring中LogbackConfigListener使用

    在Web.xml中,添加如下配置:

     <!-- logback 日志配置 start -->
        <context-param>
            <param-name>logbackConfigLocation</param-name>
            <param-value>classpath:logback.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
        </listener>
        <!-- logback 日志配置 end -->


    日志框架6:logback相较于log4j的优势_Cape_sir-CSDN博客_logback和log4j哪个好

    无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进。不过尽管难以一一细数,这里还是列举部分理由为什么选择logback而不是log4j。牢记logback与log4j在概念上面是很相似的,它们都是有同一群开发者建立。所以如果你已经对log4j很熟悉,你也可以很快上手logback。如果你喜欢使用log4j,你也许会迷上使用logback。

    
    

    1.更快的执行速度

    
    

    基于我们先前在log4j上的工作,logback 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上10倍。在保证logback的组件更加快速的同时,同时所需的内存更加少。

    
    

    2.充分的测试

    
    

    Logback 历经了几年,数不清小时数的测试。尽管log4j也是测试过的,但是Logback的测试更加充分,跟log4j不在同一个级别。我们认为,这正是人们选择Logback而不是log4j的最重要的原因。人们都希望即使在恶劣的条件下,你的日记框架依然稳定而可靠。

    
    

    3.logback-classic 非常自然的实现了SLF4J

    
    

    logback-classic中的登陆类自然的实现了SLF4J。当你使用 logback-classic作为底层实现时,涉及到LF4J日记系统的问题你完全不需要考虑。更进一步来说,由于 logback-classic强烈建议使用SLF4J作为客户端日记系统实现,如果需要切换到log4j或者其他,你只需要替换一个jar包即可,不需要去改变那些通过SLF4J API 实现的代码。这可以大大减少更换日记系统的工作量。

    
    

    4.使用XML配置文件或者Groovy

    
    

    配置logback的传统方法是通过XML文件。在文档中,大部分例子都是是用XML语法。但是,对于logback版本0.9.22,通过Groovy编写的配置文件也得到支持。相比于XML,Groovy风格的配置文件更加直观,连贯和简短的语法。现在,已经有一个工具自动把logback.xml文件迁移至logback.groovy。

    
    

    5.自动重新载入配置文件

    
    

    Logback-classic可以在配置文件被修改后,自动重新载入。这个扫描过程很快,无资源争用,并且可以动态扩展支持在上百个线程之间每秒上百万个调用。它和应用服务器结合良好,并且在J2EE环境通用,因为它不会调用创建一个单独的线程来做扫描。

    
    

    6.优雅地从I/O错误中恢复

    
    

    FileAppender和它的子类,包括RollingFileAppender,可以优雅的从I/O错误中恢复。所以,如果一个文件服务器临时宕机,你再也不需要重启你的应用,而日志功能就能正常工作。当文件服务器恢复工作,logback相关的appender就会透明地和快速的从上一个错误中恢复。

    
    

    7.自动清除旧的日志归档文件

    
    

    通过设置TimeBasedRollingPolicy 或者 SizeAndTimeBasedFNATP的 maxHistory 属性,你就可以控制日志归档文件的最大数量。如果你的回滚策略是每月回滚的,并且你希望保存一年的日志,那么只需简单的设置maxHistory属性为12。对于12个月之前的归档日志文件将被自动清除。

    
    

    8.自动压缩归档日志文件

    
    

    RollingFileAppender可以在回滚操作中,自动压缩归档日志文件。压缩通常是异步执行的,所以即使是很大的日志文件,你的应用都不会因此而被阻塞。

    
    

    9.谨慎模式

    
    

    在谨慎模式中,在多个JVM中运行的多个FileAppender实例,可以安全的写入统一个日志文件。谨慎模式可以在一定的限制条件下应用于RollingFileAppender。

    
    

    10.Lilith

    
    

    Lilith是logback的一个记录和访问事件查看器。它相当于log4j的 chainsaw,但是Lilith设计的目的是处理大量的日志记录。

    
    

    11.配置文件中的条件处理

    
    

    开发者通常需要在不同的目标环境中变换logback的配置文件,例如开发环境,测试环境和生产环境。这些配置文件大体是一样的,除了某部分会有不同。为了避免重复,logback支持配置文件中的条件处理,只需使用,和,那么同一个配置文件就可以在不同的环境中使用了。

    
    

    12.过滤

    
    

    Logback拥有远比log4j更丰富的过滤能力。例如,让我们假设,有一个相当重要的商业应用部署在生产环境。考虑到大量的交易数据需要处理,记录级别被设置为WARN,那么只有警告和错误信息才会被记录。现在,想象一下,你在开发环境遇到了一个臭虫,但是在测试平台中却很难发现,因为一些环境之间(生产环境/测试环境)的未知差异。

    
    

    使用log4j,你只能选择在生产系统中降低记录的级别到DEBUG,来尝试发现问题。但是很不幸,这会生成大量的日志记录,让分析变得困难。更重要的是,多余的日志记录会影响到生产环境的性能。

    
    

    使用logback,你可以选择保留只所有用户的WARN级别的日志,而除了某个用户,例如Alice,而她就是问题的相关用户。当Alice登录系统,她就会以DEBUG级别被记录,而其他用户仍然是以WARN级别来记录日志。这个功能,可以通过在配置文件的XML中添加4行。

    
    

    13.SiftingAppender

    
    

    SiftingAppender是一个全能的追加器。它可以基于任何给定的实时属性分开(或者筛选)日志。例如,SiftingAppender可以基于用户会话分开日志事件,这样,可以为每一个用户建立一个独立的日志文件。

    
    

    14.堆栈轨迹信息包含包的数据

    
    

    当logback打印一个异常,堆栈轨迹信息将包含包的相关数据。下面是一个通过 logback-demo 生成的堆栈信息:

    
    
    14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
    java.lang.Exception: 99 is invalid
      at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
      at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
      at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
      at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
      at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
      at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
      at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
      at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
      at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
    
    
    

    从上面的信息,你可以发现这个应用使用Struts 1.2.9 而且是使用 jetty 6.1.12部署的。所以,堆栈轨迹信息将快速的告诉读者,关于异常发生的类还有包和包的版本。当你的客户发送一个堆栈轨迹信息给你,作为一个开发人员,你就不需要让他们告诉你他们正在使用的包的版本。这项信息已经包括在堆栈轨迹信息中。详细请参考 “%xThrowable” conversion word.

    
    

    15.Logback-access模块,提供了通过HTTP访问日志的能力,是logback不可或缺的组成部分

    
    

    最后但绝非最不重要的是,作为logback发布包的一部分,logback-access模块可与Jetty或者Tomcat进行集成,提供了非常丰富而强大的通过HTTP访问日志的功能。因为logback-access模块是logback初期设计方案中的一部分,因此,所有你所喜欢的logback-classic模块所提供的全部特性logback-access同样也具备。

    日志框架7:log4j2_Cape_sir-CSDN博客_log4j2日志框架

    1.Log4j2介绍

    Log4j2是Log4j的升级版,与之前的版本Log4j 1.x相比、有重大的改进,在修正了Logback固有的架构问题的同时,改进了许多Logback所具有的功能。

    1.1 Log4j2的特性及改进

    • API分离:Log4j2将API与实现分离开来。开发人员现在可以很清楚地知道能够使用哪些没有兼容问题的类和方法,同时又允许通过自己实现来增强功能。
    • 改进的性能:Log4j2的性能在某些关键领域比Log4j 1.x更快,而且大多数情况下与Logback相当。
      多个API支持:Log4j2提供最棒的性能的同时,还支持SLF4J和公共日志记录API。
    • 自动配置加载:像Logback一样,一旦配置发生改变,Log4j2可以自动载入这些更改后的配置信息,又与Logback不同,配置发生改变时不会丢失任何日志事件。
    • 高级过滤功能:与Logback类似,Log4j2可以支持基于上下文数据、标记,正则表达式以及日志事件中的其他组件的过滤。Log4j2能够专门指定适用于所有的事件,无论这些事件在传入Loggers之前还是正在传给appenders。另外,过滤器还可以与Loggers关联起来。与Logback不同的是,Filter公共类可以用于任何情况。
    • 插件架构:所有可以配置的组件都以Log4j插件的形式来定义。同样地,不需要修改任何Log4j代码就可以创建新的Appender、Layout、Pattern Convert 等等。Log4j自动识别预定义的插件,如果在配置中引用到这些插件,Log4j就自动载入使用。
    • 属性支持:属性可以在配置文件中引用,也可以直接替代或传入潜在的组件,属性在这些组件中能够动态解析。属性可以是配置文件,系统属性,环境变量,线程上下文映射以及事件中的数据中定义的值。用户可以通过增加自己的Lookup插件来定制自己的属性。

    1)更为先进的API(Modern API) 在这之前,程序员们以如下方式进行日志记录:

    if(logger.isDebugEnabled()) {
        logger.debug("Hi, " + u.getA() + “ “ + u.getB());
    }
    

    许多人都会抱怨上述代码的可读性太差了。如果有人忘记写if语句,程序输出中会多出很多不必要的字符串。现在,Java虚拟机(JVM)也许对字符串的打印和输出进行了很多优化,但是难道我们仅仅依靠JVM优化来解决上述问题?log4j 2.0开发团队鉴于以上考虑对API进行了完善。现在你可以这样写代码:

    logger.debug("Hi, {} {}", u.getA(), u.getB());
    

    和其它一些流行的日志框架一样, 新的API也支持变量参数的占位符功能。

    log4j 2.0还支持其它一些很棒的功能,像Markers和flow tracing:

    private Logger logger = LogManager.getLogger(MyApp.class.getName());
    private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL");
    ...
    public String doQuery(String table) {
        logger.entry(param);
        logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);
        return logger.exit();
    }
    

    Markers可以帮助你很快地找到具体的日志项(Log Entries)。而在某个方法的开头和结尾调用Flow Traces中的一些方法,你可以在日志文件中看到很多新的跟踪层次的日志项,也就是说,你的程序工作流(Program Flow)被记录下来了。下面是Flow Traces的一些例子:

    19:08:07.056 TRACE com.test.TestService 19 retrieveMessage - entry
    19:08:07.060 TRACE com.test.TestService 46 getKey - entry
    

    插件式的架构:log4j 2.0支持插件式的架构。你可以根据需要自行扩展log4j 2.0,这非常简单。首先,你要为你的扩展建立好命名空间,然后告诉log4j 2.0在哪能够找到它。

    <configuration … packages="de.grobmeier.examples.log4j2.plugins">
    

    根据上述配置,log4j 2将会在de.grobmeier.examples.log4j2.plugins包中找寻你的扩展插件。如果你建立了多个命名空间,没关系,用逗号分隔就可以了。

    下面是一个简单的扩展插件:

    @Plugin(name = "Sandbox", type = "Core", elementType = "appender")
    public class SandboxAppender extends AppenderBase {
    
        private SandboxAppender(String name, Filter filter) {
            super(name, filter, null);
        }
     
        public void append(LogEvent event) {
            System.out.println(event.getMessage().getFormattedMessage());
        }
     
        @PluginFactory
        public static SandboxAppender createAppender(
             @PluginAttr("name") String name,
             @PluginElement("filters") Filter filter) {
            return new SandboxAppender(name, filter);
        }
    }
    

    上面标有@PluginFactory注解的方法是一个工厂,它的两个参数直接从配置文件读取。我用@PluginAttr和@PluginElement进行了实现。

    剩下的就非常简单了。由于我写的是一个Appender,因此得继承AppenderBase这个类。该类必须实现append()方法,从而进行实际的逻辑处理。除了Appender,你甚至可以实现自己的Logger和Filter。

    2)强大的配置功能(Powerful Configuration) log4j 2的配置变得非常简单。如果你习惯了之前的配置方式,也不用担心,你只要花很少的时间就可以从之前的方式转换到新的方式。请看下面的配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration status="OFF">
        <appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </Console>
        </appenders>
        <loggers>
            <logger name="com.foo.Bar" level="trace" additivity="false">
                <appender-ref ref="Console"/>
            </logger>
            <root level="error">
                <appender-ref ref="Console"/>
            </root>
        </loggers>
    </configuration>
    

    上面说的只是一部分改进,你还可以自动重新加载配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration monitorInterval="30">
        ...
    </configuration>
    

    监控的时间间隔单位为秒,最小值是5。这意味着,log4j 2在配置改变的情况下可以重新配置日志记录行为。如果值设置为0或负数,log4j 2不会对配置变更进行监测。最为称道的一点是:不像其它日志框架, log4j 2.0在重新配置的时候不会丢失之前的日志记录。

    还有一个非常不错的改进,那就是:同XML相比,如果你更加喜欢JSON,你可以自由地进行基于JSON的配置了:

    {
        "configuration": {
            "appenders": {
                "Console": {
                    "name": "STDOUT",
                    "PatternLayout": {
                        "pattern": "%m%n"
                    }
                }
            },
            "loggers": {
                "logger": {
                    "name": "EventLogger",
                    "level": "info",
                    "additivity": "false",
                    "appender-ref": {
                        "ref": "Routing"
                    }
                },
                "root": {
                    "level": "error",
                    "appender-ref": {
                        "ref": "STDOUT"
                    }
                }
            }
        }
    }
    

    3)Java 5 并发性(Concurrency)
    有一段文档是这样描述的:“log4j 2利用Java 5中的并发特性支持,尽可能地执行最低层次的加锁…”。Apache log4j 2.0解决了许多在log4j 1.x中仍然存留的死锁问题。如果你的程序仍然饱受内存泄漏的折磨,请毫不犹豫地试一下log4j 2.0。

    1.2 程序中如何使用

    在你的程序中使用Log4j之前必须确保API和Core jars 在程序的classpath中。使用Maven将下面的依赖加入pom.xml。

    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.0-beta3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.0-beta3</version>
        </dependency>
    </dependecies>
    

    与日志门面接口commons-logging,slf4j集成使用,具体详情参见slf4j与log4j2集成,commons-logging与log4j2集成。

    这里随便写个类,调用就是这么简单,log4j的核心在配置文件上,配置文件详解,请参见Log4j2配置文件详解。

     public class Log4j2Hello {
            private static Logger logger = LogManager.getLogger(Hello.class.getName());
            public boolean hello() {
                logger.entry();   //trace级别的信息,单独列出来是希望你在某个方法或者程序逻辑开始的时候调用,和logger.trace("entry")基本一个意思
                logger.error("Did it again!");   //error级别的信息,参数就是你输出的信息
                logger.info("我是info信息");    //info级别的信息
                logger.debug("我是debug信息");
                logger.warn("我是warn信息");
                logger.fatal("我是fatal信息");
                logger.log(Level.DEBUG, "我是debug信息");   //这个就是制定Level类型的调用:谁闲着没事调用这个,也不一定哦!
                logger.exit();    //和entry()对应的结束方法,和logger.trace("exit");一个意思
                return false;
            }
        }
    

    2.Log4j2日志级别

    在log4j2中,一共有五种log level,分别为TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。详细描述如下:

    • FATAL:用在极端的情形中,即必须马上获得注意的情况。这个程度的错误通常需要触发运维工程师的寻呼机。
    • ERROR:显示一个错误,或一个通用的错误情况,但还不至于会将系统挂起。这种程度的错误一般会触发邮件的发送,将消息发送到alert list中,运维人员可以在文档中记录这个bug并提交。
    • WARN:不一定是一个bug,但是有人可能会想要知道这一情况。如果有人在读log文件,他们通常会希望读到系统出现的任何警告。
    • INFO:用于基本的、高层次的诊断信息。在长时间运行的代码段开始运行及结束运行时应该产生消息,以便知道现在系统在干什么。但是这样的信息不宜太过频繁。
    • DEBUG:用于协助低层次的调试。
    • TRACE:用于展现程序执行的轨迹。

    3.Log4j2类图

    在这里插入图片描述
    通过类图可以看到:

    每一个log上下文对应一个configuration,configuration中详细描述了log系统的各个LoggerConfig、Appender(输出目的地)、EventLog过滤器等。每一个Logger又与一个LoggerConfig相关联。另外,可以看到Filter的种类很多,有聚合在Configuration中的filter、有聚合在LoggerConfig中的filter也有聚合在Appender中的filter。不同的filter在过滤LogEvent时的行为和判断依据是不同的,具体可参加本文后面给出的文档。

    应用程序通过调用log4j2的API并传入一个特定的名称来向LogManager请求一个Logger实例。LogManager会定位到适当的 LoggerContext 然后通过它获得一个Logger。如果LogManager不得不新建一个Logger,那么这个被新建的Logger将与LoggerConfig相关联,这个LoggerConfig的名称中包含如下信息中的一种:①与Logger名称相同的②父logger的名称③ root 。

    当一个LoggerConfig的名称与一个Logger的名称可以完全匹配时,Logger将会选择这个LoggerConfig作为自己的配置。如果不能完全匹配,那么Logger将按照最长匹配串来选择自己所对应的LoggerConfig。LoggerConfig对象是根据配置文件来创建的。LoggerConfig会与Appenders相关联,Appenders用来决定一个log request将被打印到那个目的地中,可选的打印目的地很多,如console、文件、远程socket server等。LogEvent是由Appenders来实际传递到最终输出目的地的,而在LogEvent到达最终被处理之前,还需要经过若干filter的过滤,用来判断该EventLog应该在何处被转发、何处被驳回、何处被执行。

    4.Log4j2概念介绍

    4.1 Logger层次关系

    相比于纯粹的System.out.println方式,使用logging API的最首要以及最重要的优势是可以在禁用一些log语句块的同时允许其他的语句块的输出。这一能力建立在一种假设之上,即所有在应用中可能出现的logging语句可以按照开发者定义的标准分成不同的类型。

    在Log4j 1.x版本时,Logger的层次是靠Logger类之间的关系来维护的。但在Log4j2中, Logger的层次则是靠LoggerConfig对象之间的关系来维护的。

    Logger和LoggerConfig均是有名称的实体。Logger的命名是大小写敏感的,并且服从如下的分层命名规则。(与java包的层级关系类似)。例如:com.foo是com.foo.Bar的父级;java是java.util的父级,是java.util.vector的祖先。

    root LoggerConfig位于LoggerConfig层级关系的最顶层。它将永远存在与任何LoggerConfig层次中。任何一个希望与root LoggerConfig相关联的Logger可以通过如下方式获得:

    Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
    

    其他的Logger实例可以调用LogManager.getLogger 静态方法并传入想要得到的Logger的名称来获得。

    4.2 LoggerContext

    LoggerContext在Logging System中扮演了锚点的角色。根据情况的不同,一个应用可能同时存在于多个有效的LoggerContext中。在同一LoggerContext下,log system是互通的。如:Standalone Application、Web Applications、Java EE Applications、“Shared” Web Applications 和REST Service Containers,就是不同广度范围的log上下文环境。

    4.3 Configuration

    每一个LoggerContext都有一个有效的Configuration。Configuration包含了所有的Appenders、上下文范围内的过滤器、LoggerConfigs以及StrSubstitutor.的引用。在重配置期间,新与旧的Configuration将同时存在。当所有的Logger对象都被重定向到新的Configuration对象后,旧的Configuration对象将被停用和丢弃。

    4.4 Logger

    如前面所述, Loggers 是通过调用LogManager.getLogger方法获得的。Logger对象本身并不实行任何实际的动作。它只是拥有一个name 以及与一个LoggerConfig相关联。它继承了AbstractLogger类并实现了所需的方法。当Configuration改变时,Logger将会与另外的LoggerConfig相关联,从而改变这个Logger的行为。

    获得Logger,使用相同的名称参数来调用getLogger方法将获得来自同一个Logger的引用。如:

    Logger x = Logger.getLogger("wombat");
    
    Logger y = Logger.getLogger("wombat"); // x和y指向的是同一个Logger对象。
    

    log4j环境的配置是在应用的启动阶段完成的。优先进行的方式是通过读取配置文件来完成。

    log4j使采用类名(包括完整路径)来定义Logger 名变得很容易。这是一个很有用且很直接的Logger命名方式。使用这种方式命名可以很容易的定位这个log message产生的类的位置。当然,log4j也支持任意string的命名方式以满足开发者的需要。不过,使用类名来定义Logger名仍然是最为推崇的一种Logger命名方式。

    4.5 LoggerConfig

    当Logger在configuration中被描述时,LoggerConfig对象将被创建。LoggerConfig包含了一组过滤器。LogEvent在被传往Appender之前将先经过这些过滤器。过滤器中包含了一组Appender的引用。Appender则是用来处理这些LogEvent的。

    每一个LoggerConfig会被指定一个Log级别。可用的Log级别包括TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。需要注意的是,在log4j2中,Log的级别是一个Enum型变量,是不能继承或者修改的。如果希望获得更多的分割粒度,可用考虑使用Markers来替代。

    在Log4j 1.x 和Logback 中都有“层次继承”这么个概念。但是在log4j2中,由于Logger和LoggerConfig是两种不同的对象,因此“层次继承”的概念实现起来跟Log4j 1.x 和Logback不同。具体情况下面的五个例子:

    例子一:
    输入图片说明

    可用看到,应用中的LoggerConfig只有root这一种。因此,对于所有的Logger而言,都只能与该LoggerConfig相关联而没有别的选择。

    例子二:
    输入图片说明

    在例子二中可以看到,有5种不同的LoggerConfig存在于应用中,而每一个Logger都被与最匹配的LoggerConfig相关联着,并且拥有不同的Log Level。

    例子三:
    输入图片说明

    可以看到Logger root、X、X.Y.Z都找到了与各种名称相同的LoggerConfig。而LoggerX.Y没有与其名称相完全相同的LoggerConfig。怎么办呢?它最后选择了X作为它的LoggerConfig,因为X LoggerConfig拥有与其最长的匹配度。

    例子四:
    输入图片说明

    可以看到,现在应用中有两个配置好的LoggerConfig:root和X。而Logger有四个:root、X、X.Y、X.Y.Z。其中,root和X都能找到完全匹配的LoggerConfig,而X.Y和X.Y.Z则没有完全匹配的LoggerConfig,那么它们将选择哪个LoggerConfig作为自己的LoggerConfig呢?由图上可知,它们都选择了X而不是root作为自己的LoggerConfig,因为在名称上,X拥有最长的匹配度。

    例子五:
    输入图片说明

    可以看到,现在应用中有三个配置好的LoggerConfig,分别为:root、X、X.Y。同时,有四个Logger,分别为:root、X、X.Y以及X.YZ。其中,名字能完全匹配的是root、X、X.Y。那么剩下的X.YZ应该匹配X还是匹配X.Y呢?答案是X。因为匹配是按照标记点(即“.”)来进行的,只有两个标记点之间的字串完全匹配才算,否则将取上一段完全匹配的字串的长度作为最终匹配长度。

    4.6 Filter

    与防火墙过滤的规则相似,log4j2的过滤器也将返回三类状态:Accept(接受), Deny(拒绝) 或Neutral(中立)。其中,Accept意味着不用再调用其他过滤器了,这个LogEvent将被执行;Deny意味着马上忽略这个event,并将此event的控制权交还给过滤器的调用者;Neutral则意味着这个event应该传递给别的过滤器,如果再没有别的过滤器可以传递了,那么就由现在这个过滤器来处理。

    4.7 Appender

    由logger的不同来决定一个logging request是被禁用还是启用只是log4j2的情景之一。log4j2还允许将logging request中log信息打印到不同的目的地中。在log4j2的世界里,不同的输出位置被称为Appender。目前,Appender可以是console、文件、远程socket服务器、Apache Flume、JMS以及远程 UNIX 系统日志守护进程。一个Logger可以绑定多个不同的Appender。

    可以调用当前Configuration的addLoggerAppender函数来为一个Logger增加。如果不存在一个与Logger名称相对应的LoggerConfig,那么相应的LoggerConfig将被创建,并且新增加的Appender将被添加到此新建的LoggerConfig中。尔后,所有的Loggers将会被通知更新自己的LoggerConfig引用(PS:一个Logger的LoggerConfig引用是根据名称的匹配长度来决定的,当新的LoggerConfig被创建后,会引发一轮配对洗牌)。

    在某一个Logger中被启用的logging request将被转发到该Logger相关联的的所有Appenders上,并且还会被转发到LoggerConfig的父级的Appenders上。

    这样会产生一连串的遗传效应。例如,对LoggerConfig B来说,它的父级为A,A的父级为root。如果在root中定义了一个Appender为console,那么所有启用了的logging request都会在console中打印出来。另外,如果LoggerConfig A定义了一个文件作为Appender,那么使用LoggerConfig A和LoggerConfig B的logger 的logging request都会在该文件中打印,并且同时在console中打印。

    如果想避免这种遗传效应的话,可以在configuration文件中做如下设置:

    additivity="false" // 默认为true
    

    这样,就可以关闭Appender的遗传效应了。

    4.8 Layout

    通常,用户不止希望能定义log输出的位置,还希望可以定义输出的格式。这就可以通过将Appender与一个layout相关联来实现。Log4j中定义了一种类似C语言printf函数的打印格式,如"%r [%t] %-5p %c - %m%n" 格式在真实环境下会打印类似如下的信息:

    176 [main] INFO  org.foo.Bar - Located nearest gas station.
    

    其中,各个字段的含义分别是:

    %r 指的是程序运行至输出这句话所经过的时间(以毫秒为单位);
    %t 指的是发起这一log request的线程;
    %c 指的是log的level;
    %m 指的是log request语句携带的message;
    %n 为换行符;

    5.Log4j2的LogEvent分析

    5.1 如何产生LogEvent

    在调用Logger对象的info、error、trace等函数时,就会产生LogEvent。LogEvent跟LoggerConfig一样,也是由Level的。LogEvent的Level主要是用在Event传递时,判断在哪里停下。

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    public class Test {
        private static Logger logger = LogManager.getLogger("HelloWorld");
        public static void main(String[] args){
            Test.logger.info("hello,world");
            Test.logger.error("There is a error here");
        }
    
    }
    

    如代码中所示,这样就产生了两个LogEvent。

    5.2 LogEvent的传递是怎样的

    我们在IDE中运行一下这个程序,看看会有什么输出。如下:

    输入图片说明

    发现,只有ERROR的语句输出了,那么INFO的语句呢?不着急,先来看看工程目录结构:

    在这里插入图片描述

    可以看到,工程中没有写入任何的配置文件。所以,application应该是使用了默认的LoggerConfig Level。那么默认的Level是多少呢?默认的输出地是console,默认的级别是ERROR级别。

    那么,为什么默认ERROR级别会导致INFO级别的信息被拦截呢?看如下表格:

    输入图片说明

    左边竖栏是Event的Level,右边横栏是LoggerConfig的Level。Yes的意思就是这个event可以通过filter,no的意思就是不能通过filter。

    可以看到,INFO级别的Event是无法被ERROR级别的LoggerConfig的filter接受的。所以,INFO信息不会被输出。

    6.Log4j2的配置文件重定位

    如果想要改变默认的配置,那么就需要configuration file。Log4j的配置是写在log4j.properties文件里面,但是Log4j2就可以写在XML和JSON文件里了。

    (1)放在classpath(src)下,以log4j2.xml命名:使用Log4j2的一般都约定俗成的写一个log4j2.xml放在src目录下使用。这一点没有争议。

    (2)将配置文件放到别处:在系统工程里面,将log4j2的配置文件放到src目录底下很不方便。如果能把工程中用到的所有配置文件都放在一个文件夹里面,当然就更整齐更好管理了。但是想要实现这一点,前提就是Log4j2的配置文件能重新定位到别处去,而不是放在classpath底下。

    如果没有设置"log4j.configurationFile" system property的话,application将在classpath中按照如下查找顺序来找配置文件:

    log4j2-test.json 或log4j2-test.jsn文件

    log4j2-test.xml文件

    log4j2.json 或log4j2.jsn文件

    log4j2.xml文件

    这就是为什么在src目录底下放log4j2.xml文件可以被识别的原因了。如果想将配置文件重命名并放到别处,就需要设置系统属性log4j.configurationFile。设置的方式是在VM arguments中写入该属性的key和value:

    -Dlog4j.configurationFile="D:\learning\blog\20130115\config\LogConfig.xml"
    

    测试的java程序如上文,在此不再重复。运行,console输出如下:

    输入图片说明

    期待已久的INFO语句出现了。

     日志框架8:log4j2配置_Cape_sir-CSDN博客

    1.默认配置

    本来以为Log4J2应该有一个默认的配置文件的,不过好像没有找到(通过DefaultConfiguration,初始化一个最小化配置),下面这个配置文件等同于缺省配置:

    <?xml version="1.0" encoding="UTF-8"?>  
    <configuration status="OFF">  
        <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">  
                <appender-ref ref="Console"/>  
            </root>  
        </loggers>  
    </configuration>
    

    2.第一个配置例子

    配置Log4j 2可以有四种方法(其中任何一种都可以):

    • 通过一个格式为XML或JSON的配置文件。
    • 以编程方式,通过创建一个ConfigurationFactory工厂和Configuration实现。
    • 以编程方式,通过调用api暴露在配置界面添加组件的默认配置。
    • 以编程方式,通过调用Logger内部类上的方法。

    注意,与Log4j 1.x不一样的地方,公开的Log4j 2 API没有提供任何添加、修改或删除 appender和过滤器或者操作配置文件的方法。

    Log4j能够自动配置本身在初始化期间。当Log4j启动它将定位所有的ConfigurationFactory插件和安排然后在加权从最高到最低。Log4j包含两个ConfigurationFactory实现,一个用于JSON和XML。加载配置文件流程如下:

    1)Log4j将检查“Log4j的配置文件“系统属性,如果设置,将尝试加载配置使用 ConfigurationFactory 匹配的文件扩展。
    2)如果没有系统属性设置JSON ConfigurationFactory log4j2-test将寻找。 json或 log4j2-test。json在类路径中。
    3)如果没有这样的文件发现XML ConfigurationFactory log4j2-test将寻找。 xml在 类路径。
    4)如果一个测试文件无法找到JSON ConfigurationFactory log4j2将寻找。 log4j2.jsn json或 在类路径中。
    5)如果一个JSON文件无法找到XML ConfigurationFactory将试图定位 log4j2。 xml在类路径中。
    6)如果没有配置文件可以找到了 DefaultConfiguration 将被使用。 这将导致日志输出到控制台。

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration status="OFF">
        <appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </Console>
        </appenders>
        <loggers>
            <!--我们只让这个logger输出trace信息,其他的都是error级别-->
            <!--
            additivity开启的话,由于这个logger也是满足root的,所以会被打印两遍。
            不过root logger 的level是error,为什么Bar 里面的trace信息也被打印两遍呢
            -->
            <logger name="cn.lsw.base.log4j2.Hello" level="trace" additivity="false">
                <appender-ref ref="Console"/>
            </logger>
            <root level="error">
                <appender-ref ref="Console"/>
            </root>
        </loggers>
    </configuration>
    

    我们这里看到了配置文件里面是name很重要,没错,这个name可不能随便起(其实可以随便起)。这个机制意思很简单。就是类似于java package一样,比如我们的一个包:cn.lsw.base.log4j2。而且,可以发现我们前面生成Logger对象的时候,命名都是通过 Hello.class.getName(); 这样的方法,为什么要这样呢? 很简单,因为有所谓的Logger 继承的问题。比如 如果你给cn.lsw.base定义了一个logger,那么他也适用于cn.lsw.base.lgo4j2这个logger。名称的继承是通过点(.)分隔的。然后你可以猜测上面loggers里面有一个子节点不是logger而是root,而且这个root没有name属性。这个root相当于根节点。你所有的logger都适用与这个logger,所以,即使你在很多类里面通过类名.class.getName() 得到很多的logger,而且没有在配置文件的loggers下面做配置,他们也都能够输出,因为他们都继承了root的log配置。

    我们上面的这个配置文件里面还定义了一个logger,他的名称是 cn.lsw.base.log4j2.Hello ,这个名称其实就是通过前面的Hello.class.getName(); 得到的,我们为了给他单独做配置,这里就生成对于这个类的logger,上面的配置基本的意思是只有cn.lsw.base.log4j2.Hello 这个logger输出trace信息,也就是他的日志级别是trace,其他的logger则继承root的日志配置,日志级别是error,只能打印出ERROR及以上级别的日志。如果这里logger 的name属性改成cn.lsw.base,则这个包下面的所有logger都会继承这个log配置(这里的包是log4j的logger name的“包”的含义,不是java的包,你非要给Hello生成一个名称为“myhello”的logger,他也就没法继承cn.lsw.base这个配置了。

    那有人就要问了,他不是也应该继承了root的配置了么,那么会不会输出两遍呢?我们在配置文件中给了解释,如果你设置了additivity=“false”,就不会输出两遍。

    3.复杂一点的配置

     <?xml version="1.0" encoding="UTF-8"?>
        <!--
            Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出。 
        -->
        <!--
            monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数。
        -->
        <configuration status="error" monitorInterval=”30″>
            <!--先定义所有的appender-->
            <appenders>
                <!--这个输出控制台的配置-->
                <Console name="Console" target="SYSTEM_OUT">
                    <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
                    <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
                    <!--这个都知道是输出日志的格式-->
                    <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
                </Console>
                <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
                <File name="log" fileName="log/test.log" append="false">
                    <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
                </File> 
                <!-- 这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
                <RollingFile name="RollingFile" fileName="logs/app.log"
                         filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
                    <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
                    <SizeBasedTriggeringPolicy size="50MB"/>
                    <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
                    <DefaultRolloverStrategy max="20"/>
                </RollingFile>
            </appenders>
            <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
            <loggers>
                <!--建立一个默认的root的logger-->
                <root level="trace">
                    <appender-ref ref="RollingFile"/>
                    <appender-ref ref="Console"/>
                </root> 
            </loggers>
        </configuration> 
    
    • 扩展组件
      1)ConsoleAppender:输出结果到System.out或是System.err。
      2)FileAppender:输出结果到指定文件,同时可以指定输出数据的格式。append=“false”指定不追加到文件末尾
      3)RollingFileAppender:自动追加日志信息到文件中,直至文件达到预定的大小,然后自动重新生成另外一个文件来记录之后的日志。
    • 过滤标签
      1)ThresholdFilter:用来过滤指定优先级的事件。
      2)TimeFilter:设置start和end,来指定接收日志信息的时间区间。

    3.1 Appender之Syslog配置

    log4j2中对syslog的简单配置,这里就不重复展示log4j2.xml了:

    <Syslog name="SYSLOG" host="localhost" port="514" protocol="UDP" facility="LOCAL3"/>
    

    host是指你将要把日志写到的目标机器,可以是ip(本地ip或远程ip,远程ip在实际项目中很常见,有专门的日志服务器来存储日志),也可以使用主机名,如果是本地,还可以使用localhost或127.0.0.1。

    Port指定端口,默认514,参见/etc/rsyslog.conf(以Fedora系统为例,下同)。protocol指定传输协议,这里是UDP,facility是可选项,后面可以看到用法。

    3.2 Syslog及Syslog-ng相关配置(Fedora)

    在运行程序之前,需要修改:/etc/rsyslog.conf。

    把这两行前的#去掉,即取消注释:

    #$ModLoad imudp
    #$UDPServerRun 514
    

    这里启用udp监听,514是默认监听端口,重启syslog:

    service syslog restart
    

    大部分日志会默认写到/var/log/messages中,如果不想写到这个文件里,可以按下面修改,这样local3的日志就会写到app.log中。这里的local3即 log4j2.xml中facility的配置。

    *.info;mail.none;authpriv.none;cron.none;local3.none                /var/log/messages
    

    新增一行:

    local3.*                                                            /var/log/app.log
    

    除了使用自带的syslog,我们也可以使用syslog的替代品,比如syslog-ng,这对于log4j2.xml配置没有影响。 安装:

    yum install syslog-ng
    

    启动:

    service syslog-ng start
    

    其配置文件为:

    /etc/syslog-ng/syslog-ng.conf
    

    启动前把source一节中这一行取消注释即可:

    #udp(ip(0.0.0.0) port(514));
    

    这个端口会和syslog冲突,可以使用别的端口比如50014,同时修改log4j2.xml中的port属性。另外提一下,使用非默认端口,要求log4j版本在1.2.15或以上。

    syslog-ng本身也可以设置把日志送到远程机器上,在源机器上的syslog-ng.conf中添加:

    destination d_remote1 {udp(153.65.171.73 port(514));};
    

    这表示源机器上的syslog-ng会把接收到的日志送到远程主机153.65.171.73的514端口上,只要保证153.65.171.73上的syslog-ng正常运行并监听对应的端口即可。

    4.Log4j2与Spring集成

    web.xml配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/root-context.xml</param-value>
        </context-param>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <!-- log4j2-begin -->
        <listener>
            <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
        </listener>
        <filter>
            <filter-name>log4jServletFilter</filter-name>
            <filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>log4jServletFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>FORWARD</dispatcher>
            <dispatcher>INCLUDE</dispatcher>
            <dispatcher>ERROR</dispatcher>
        </filter-mapping>
        <!-- log4j2-end -->
        
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <servlet>
            <servlet-name>appServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>appServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
     
  • 相关阅读:
    她又在这个星期联系我了 20110422
    用心去做 20110307
    2011年随笔记
    让我有勇气坚持下去 20110427
    2011年随笔记 5月30号以后的日志薄
    因为迷失,所以 201103015
    迷失只是暂时 20110313
    我们做一对好哥们吧 20101228
    3.20号,她恢复了联系 20110320
    FW: Deploy reporting services web parts (RSWebParts) to SharePoint 2010
  • 原文地址:https://www.cnblogs.com/Chary/p/15846360.html
Copyright © 2020-2023  润新知