圆圆网络 手游攻略 新游动态 日志系统架构,日志框架是什么

日志系统架构,日志框架是什么

时间:2024-05-01 23:53:11 来源:头条 浏览:0

1. JUL 代表Java Util Logging 包。 Java 的本机日志记录框架。您不需要引用任何第三方类库即可使用它。与其他框架相比,它易于使用和学习。主要用于小型应用。

1.1. JUL 配置结构Logger:称为记录器,应用程序获取Logger 对象并使用其API 公开日志信息。记录器通常被认为是访问日志系统的入口点程序。处理者:处理器。每个记录器都与一个或一组处理程序相关联。 logger将日志传递给关联的handler进行处理,handler负责记录日志。 handler具体实现了日志输出位置。例如,您可以输出到控制台、文件等。过滤器:应用过滤器来根据需要自定义要记录的信息和要跳过的信息。 Formatter:格式化组件。负责对日志中的数据和信息进行转换和格式化,并确定输出日志的最终格式。级别:日志输出级别。每条日志消息都有一个关联的级别。使用输出级别设置来显示最终的日志信息。根据不同的需求设置不同的级别。

1.2、首先直接进入JDK文档,查看java.util.logging包。

日志系统架构,日志框架是什么

public class JULTest { @Test public void test01(){ /* 日志入口程序java.util.logging.Logger *///创建Logger 对象的方法不允许直接创建新对象//对象方法To获取引入当前类所需的参数。完整路径字符串(目前这是我们使用的第一个;根据包结构,Logger 之间存在父子关系,稍后详细介绍) Logger logger=Logger.getLogger(' cn.zixieqing. jul.test.JULTest '); /* 对于日志的输出方式有两种,第一种是直接调用与日志级别相关的方法,将日志输出信息传递给该方法。我要输出info级别的日志信息*///logger.info('输出info信息1') ; /* 第二种方法:调用通用日志方法,然后使用Level类型指定日志级别,定义参数并匹配日志输出信息*///logger.log(Level.INFO,'输出信息信息2'); /* 输出学生信息姓名和年龄*//*String name=' zs'; int Age=23; logger.log(Level.INFO,'学生姓名是:'+姓名+';年龄是:'+年龄);*//* 对于输出消息,字符串拼接使用有很多缺点:1.麻烦2.程序效率低3. 可读性差4. 维护成本高生成日志需要采用动态生成数据的方式进行操作,使用占位符*/String name='zs'; int Age=23; logger.log(Level.INFO, '学生姓名: {0},年龄:{1}',新对象[]{ name,age}); }}

1.3. 日志级别@Test public void test02(){ /* 日志级别(通过源码可见,很简单) SEVERE : Error --- 最高日志级别WARNING : warning INFO :(默认级别) 消息源码: Logger . getLogger () ---demandLogger ----- getLogManager() ----- EnsureLogManagerInitialized()---在第350行左右有一个默认级别属性CONFIG :设置FINE : 详细(少) FINER : 详细(中) FINEST : 详细(多) --- 使用最低日志记录级别和两个特殊级别OFF 来关闭所有日志记录,并且可以启用所有消息日志记录。关于日志级别,请注意创建新对象时的第二级。该参数是一个数字(源代码中包含) OFF Integer.MAX_VALUE 最大整数值SEVERE 1000 WARNING 900 . FINEST 300 ALL Integer.MIN_VALUE 最小整数值该值的重要性由设置的日志级别决定。 它一直。这是信息——800。此时,最终显示的日志信息必须是所有大于800的日志信息。最终显示的日志信息为SEVERE WARNING INFO */Logger logger=Logger.getLogger('cn.zixieqing .jul.test.JULTest'); /* 输出结果会显示info 级别及以上级别的日志信息可以看到该日志不输出低于信息级别的信息。 information level日志信息是系统默认的日志级别,在默认的日志级别信息的基础上,输出更高级别的信息*//* 单纯按照如下格式设置日志级别是不会体现出来的,以后一定要使用处理器处理程序设置生效*/logger.setLevel(Level.CONFIG); logger.severe('严重信息'); logger.warning('警告信息') ; logger.info('信息信息'); logger .config('配置信息'); logger.fine('精细信息'); logger.finer('精细信息'); logger.finest('最详细信息') ; }

1.4. 自定义日志级别1.4.1. 向控制台输出信息@Test public void test03(){ /* 自定义日志级别*///Logger Logger logger=Logger.getLogger('cn.zixieqing.jul .test . JULTest'); /* 关闭默认的日志记录方法并将参数设置为false。日志记录方法不按照父记录器的默认方法执行*/logger.setUseParentHandlers(false); /* 处理处理程序这里使用控制台日志处理器来获取处理器对象*/ConsoleHandler handler=new ConsoleHandler(); //创建日志格式组件对象SimpleFormatter formatter=new SimpleFormatter(); //设置输出到处理器Format handler.setFormatter( formatter); //将处理器添加到记录器logger.addHandler(handler); /* 设置日志输出电平。记录器和处理器级别应统一设置。达到显示相应日志的效果。 level logger.setLevel(Level.CONFIG); handler.setLevel(Level.CONFIG); *///设置下面所有的级别,如果需要的话,会打印下面所有的输出信息到控制台以及相应的日志当改变级别时,logger .setLevel(Level.ALL); handler.setLevel(Level.ALL); logger.severe('严重信息'); logger.warning('警告信息'); logger.info('信息logger.config('配置信息'); logger.fine('精细信息'); logger.finer('精细信息'); logger.finest('精细信息');

1.4.2. 输出磁盘信息@Test public void test04() throws IOException { /* 输出日志到特定磁盘文件,相当于执行日志持久化操作*/Logger logger=Logger.getLogger( 'cn.zixieqing .jul .test.JULTest'); logger.setUseParentHandlers(false); //文件日志处理程序FileHandler handler=new FileHandler('D:\\test\\' + this.getClass(). getSimpleName() + '.log' ); SimpleFormatter formatter=new SimpleFormatter(); handler.setFormatter(formatter); logger.addHandler(handler); //也可以同时打印到控制台和文件ConsoleHandler handler2=new ConsoleHandler(); handler2.setFormatter(formatter) ; //日志记录器可以同时添加多个处理器,允许相应级别的日志信息同时输出到磁盘和控制台。 logger.addHandler(handler2); logger.setLevel(Level.ALL); handler.setLevel(Level.ALL); handler2.setLevel(Level.CONFIG); logger.severe('严重信息'); logger.warning('警告logger.info('信息'); logger.config('配置信息'); logger.fine('精细信息'); logger.finer('精细信息'); logger.finest('最详细信息') ; /* 总结:用户使用Logger进行日志记录。Logger可以持有多个处理器的Handler(日志记录使用Logger,日志记录使用Handler)应该添加哪些handler对象?相当于根据添加的handler将日志输出到指定位置。控制台、文件等/}

1.5. logger 之间的父子关系@Test public void test05(){ /* logger 之间的父子关系。 JUL里,伐木工之间存在着“父子”关系。这种亲子关系值得关注。孩子的关系并不是我们通常想象的那样。通过树形结构保存它们之间的继承关系*//* 从下面创建的两个logger对象可以认为logger1是logger2的父亲*//* 父亲是RootLogger,默认名称是空字符串。 RootLogger 可以称为所有logger 对象的顶级logger *///这是父logger logger1=Logger.getLogger('cn.zixieqing.jul.test'); //这是子和cn.zixieqing。 jul也是这个logger2的父级Logger logger2=Logger.getLogger('cn.zixieqing.jul.test.JULTest'); //System.out.println(logger2.getParent) ()==logger1); //true System.out.println('logger1的父Logger引用是:'+logger1.getParent()+';名称是'+logger1.getName()+';父名称是'+logger1.getParent().getName()) ; System.out.println('logger2 的父记录器引用是:' +logger2.getParent()+'; 名称是'+logger2.getName()+'; 父亲的名字是'+logger2.getParent ().getName() ); /* 父亲所做的设置也可能会影响儿子在logger1上执行的日志相关设置,因此使用logger2来输出日志*///父亲所做的设置logger1.setUseParentHandlers(false); ConsoleHandler handler=new ConsoleHandler(); SimpleFormatter formatter=new SimpleFormatter(); handler.setFormatter(formatter); logger1.addHandler(handler); handler.setLevel( Level) .ALL); logger1.setLevel(Level.ALL); //儿子做打印结果是:儿子的logger2没有设置,而父亲的logger1设置了,所以儿子的logger2的输出级别就是父亲的logger2的级别。severe('严重信息'); logger2.warning('警告信息'); logger2.info('信息信息'); logger2.config('配置信息'); logger2.fine('详细信息'); logger2 .finer('更详细的信息'); logger2.finest('更详细的信息'); }

摘要:查看源代码中的结果。

//JUL 在初始化期间创建一个顶级RootLogger 作为所有记录器的父记录器java.util.logging.LogManager$RootLogger. Default name is an empty string //显示源代码Logger.getLogger() ---demandLogger -- - -- getLogManager() ----- EnsureLogManagerInitialized() --- 第350 行左右:owner.rootLogger=Owner.new RootLogger(); RootLogger 是LogManager 的内部类/* 上面的RootLogger 对象的工作原理如下:树结构的根节点以后自定义的现有父子关系通过路径与父子关系关联起来,这也是节点之间的一种实现关系*///350行owner.addLogger(owner .rootLogger); addLogger ---- LoggerContext cx=getUserContext (); /* LoggerContext是一个Map关系,用于存储节点。 WeakHashMap contextsMap点击WeakHashMap的LoggerContext。内部结构为: HashtablenamedLoggers *///那么当点击hashtablenamedLoggers中的LoggerWeakRef时,得到的信息为:private String name;//对于namedLoggers清理来说,这是node,父子关系意味着它是还有节点的挂载关系private LogNode node; //loggerRef 用于清理private WeakReferenceparentRef; //用于子清理

1.6. 使用配置文件1.6.1. 查看默认配置文件位置的源代码Logger.getLogger() ---demandLogger ----- getLogManager() ----- EnsureLogManagerInitialized() -- - 每行345: 有是这样的行代码:owner.readPrimordialConfiguration();单击readPrimordialConfiguration()并且在第340行周围有一个语句readConfiguration();单击readConfiguration();第1290行我在String fname=System.getProperty('周围有以下代码java.util.logging .config.file'); if (fname==null) { fname=System.getProperty('java.home'); if (fname==null) { throw new Error('找不到java.home '); } 文件f=new File(fname, 'lib'); f=new File(f, 'logging.properties'); fname=f.getCanonicalPath(); }

结论:默认配置文件位置

java.home(即JDK安装目录)--找到jre文件夹--lib --logging.properties

分析位于上述目录中的logging.properties 文件并找到要在此目录中打开的文件。

# RootManager默认使用的处理器# 如果要配置多个处理器,使用逗号指定处理器:可以分隔。 ConsoleHandler# RootManager 默认日志记录级别(这是全局日志记录级别) # 如果不手动设置级别,则默认使用INFO 或更高级别进行输出level=INFO # 文件处理器设置# 日志文件的路径# %h /java%u.log h 指的是用户目录,无论是Windows还是Linux。 # java%u.log 是生成的文件名。这里u相当于从0开始自动递增#例子: java0 .log, java1.log, java2.log. 这里生成的日志文件数量是通过设置java.util.logging.FileHandler .count 来确定的=1 java.util.logging.FileHandler.pattern=%h/java. %u.log# 限制- 默认50000 字节java.util.logging.FileHandler.limit=50000# 日志文件数量- 默认1 java.util.logging.FileHandler.count=1# 日志文件格式,默认XML 格式,SimpleFormatterjava .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

1.6.2.修改配置文件更加人性化#自定义Loggercn.zixieqing.handlers=java.util.logging.FileHandler#自定义logger的日志级别cn.zixieqing.level=SERVER#父logger的Shields日志设置,相当Logger.setUseParentHandlers(false);cn.zixieqing.useParentHandlers=false#RootManager默认使用的处理器#如果要配置多个处理器,可以像java.util.logging中那样使用逗号来分隔处理器。 ConsoleHandler ,java.util.logging.FileHandlerhandlers=java.util.logging.ConsoleHandler# RootManager 的默认日志记录级别(全局日志记录级别)。 level=SERVER# 文件处理程序设置# 日志文件路径# %h/java% u .log h 指的是用户目录,无论是Windows 还是Linux#。 java%u.log 是生成的文件名。这里的u相当于从0开始自增,如java0.log、java1等。 log, java2 .log. 此处生成的副本数由设置java.util.logging.FileHandler.count=1 决定。 java.util.logging.FileHandler.pattern=%h/java%u.log#日志文件限制- 50000 字节java.util.logging.FileHandler.limit=50000# 日志文件数量java.util.logging.FileHandler.count=1# 日志文件格式java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter# 控制台处理器配置# 控制台默认级别java.util.logging.ConsoleHandler.level=SERVER# 控制台默认输出格式java.util .logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter# Next By 默认情况下,一旦生成日志文件,它就会覆盖前一个日志文件。相反,java.util.logging.FileHandler.append=true 会附加到原始日志中。

1.6.3. 使用自定义配置文件@Test public void test06() throws Exception { //加载自定义配置文件InputStream input=new FileInputStream('D:\\test\\logging.properties'); //日志管理器Get object LogManager logManager=LogManager.getLogManager(); //读取自定义配置文件logManager.readConfiguration(input); Logger logger=Logger.getLogger('cn.zixieqing.jul.test.JULTest'); logger .severe('severe information'); logger.warning('警告信息'); logger.info('信息信息'); logger.config('配置信息'); l

ogger.fine("fine信息"); logger.finer("finer信息"); logger.finest("finest信息"); } 1.7、总结:JUL原理1、初始化LogManagerLogManager加载logging.properties配置文件添加Logger到LogManager2、从单例的LogManager获取Logger,即:LogManager.getLogManager();3、设置日志级别Level,在打印的过程中使用到了日志记录的LogRecord类,源码找寻如下:1、点击logger.severe("severe信息");中的severe,当然点击其他warning、info、config也是可以进去的,之后会看到如下的代码 public void severe(String msg) { log(Level.SEVERE, msg); } 2、点击上述的log(),看到如下的代码 public void log(Level level, String msg) { if (!isLoggable(level)) { return; } // 这里就是目的地 LogRecord lr = new LogRecord(level, msg); doLog(lr); }4、Filter作为过滤器提供了日志级别之外更细粒度的控制5、Handler日志处理器,决定日志的输出位置,例如控制台、文件...6、Formatter是用来格式化输出的 2、LOG4J全称:log for java 2.1、LOG4J的组成Loggers (日志记录器):控制日志的输出以及输出级别(JUL做日志级别Level)Appenders(输出控制器):指定日志的输出方式(输出到控制台、文件等)Layout(日志格式化器):控制日志信息的输出格式 2.1.1、Loggers日志记录器负责收集处理日志记录,实例的命名就是类的全限定名,如:cn.zixieqing.test,同时:Logger的名字大小写敏感,其命名有继承机制( 和JUL中的父子关系一样,以包路径来区分的 );另外:root logger是所有logger的根 - 上辈所做的日志属性设置,会直接的影响到子辈 // root logger的获取 Logger.getRootLogger(); 日志级别 DEBUGINFOWARNERROR........ 大小关系:ERROR > WARN > INFO > DEBUG 输出规则:输出日志的规则是:只输出级别不低于设定级别的日志信息如:假设Loggers级别设定为INFO,则INFO、WARN、ERROR级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出 2.1.2、Appenders输出控制器允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据时间或者文件大小产生新的文件,可以以流的形式发送到其它地方等等 常用Appenders输出控制器类型:ConsoleAppender 将日志输出到控制台FileAppender 将日志输出到文件中DailyRollingFileAppender 根据指定时间输出到一个新的文件, 将日志输出到一个日志文件RollingFileAppender 根据指定文件大小,当文件大小达到指定大小时,会自动把文件改名,产生一个新的文件,将日志信息输出到一个日志文件JDBCAppender 把日志信息保存到数据库中 2.2、玩LOG4J2.2.1、入门依赖 log4j log4j 1.2.17 public class Log4jTest01 { @Test public void test01(){ // 加载初始化配置 BasicConfigurator.configure(); // 注意:这个logger是apache下的,前面玩的JUL中的是java。util.logging包下的 Logger logger = Logger.getLogger(Log4jTest01.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); }}注意加载初始化信息:BasicConfigurator.configure();,不加这一句代码就报错 - 没有添加Appenders输出控制器,加上不报错是因为:源码中有这样一句代码rootManager.addAppenders( XxxxAppender( PatternLayout layout ) ),configure源码如下: public static void configure() { Logger root = Logger.getRootLogger(); root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n"))); } LOG4J日志级别 Log4j提供了8个级别的日志输出,分别为如下级别:ALL 最低等级 用于打开所有级别的日志记录TRACE 程序推进下的追踪信息,这个追踪信息的日志级别非常低,一般情况下是不会使用的DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要是配合开发,在开发过程中打印一些重要的运行信息,在没有进行设置的情况下,默认的日志输出级别INFO 消息的粗粒度级别运行信息WARN 表示警告,程序在运行过程中会出现的有可能会发生的隐形的错误注意,有些信息不是错误,但是这个级别的输出目的就是为了给程序员以提示ERROR 系统的错误信息,发生的错误不影响系统的运行一般情况下,如果不想输出太多的日志,则使用该级别即可FATAL 表示严重错误,它是那种一旦发生系统就不可能继续运行的严重错误如果这种级别的错误出现了,表示程序可以停止运行了OFF 最高等级的级别,用户关闭所有的日志记录 2.2.2、分析源码 BasicConfigurator.configure(); Logger logger = Logger.getLogger(Log4jTest01.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); 分析 BasicConfigurator.configure();点击configure() 得到的代码如下 public static void configure() { Logger root = Logger.getRootLogger(); root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n"))); } 从中可以得到几个信息:1、创建根节点的对象Logger root = Logger.getRootLogger();2、根节点添加了ConsoleAppender对象(表示默认打印到控制台,自定义的格式化输出PatternLayout)那么想要自定义配置文件来实现上述源代码的功能呢?通过上面这个源代码分析,我们需要具备如下的条件:们的配置文件需要提供Logger、Appender、Layout这3个组件信息 分析Logger logger = Logger.getLogger(Log4jTest01.class);点击getLogger() public static Logger getLogger(Class clazz) { return LogManager.getLogger(clazz.getName()); } 发现:LogManager.getLogger(clazz.getName());,其中:LogManagerj就是日志管理器 查看LogManager 首先看到如下信息,一堆常量 public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"; /** @deprecated */ public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration"; /** @deprecated */ public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass"; /** @deprecated */ public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride"; private static Object guard = null; private static RepositorySelector repositorySelector; 这些东西代表的就是不同形式(不同后缀名)的配置文件,其中log4j.properties属性使我们最常使用的,因为它语法简单、使用方便 log4j.properties的加载时机加载 - 那就是static观察LogManager中的代码,找到其中的静态代码块static 发现如下的一堆源码 static { Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG)); repositorySelector = new DefaultRepositorySelector(h); String override = OptionConverter.getSystemProperty("log4j.defaultInitOverride", (String)null); if (override != null && !"false".equalsIgnoreCase(override)) { LogLog.debug("Default initialization of overridden by log4j.defaultInitOverrideproperty."); } else { String configurationOptionStr = OptionConverter.getSystemProperty("log4j.configuration", (String)null); String configuratorClassName = OptionConverter.getSystemProperty("log4j.configuratorClass", (String)null); URL url = null; if (configurationOptionStr == null) { url = Loader.getResource("log4j.xml"); if (url == null) { // 前面就是文件格式的一堆判断,这里才是log4j.propertie格式做的事情 url = Loader.getResource("log4j.properties"); } } else { try { url = new URL(configurationOptionStr); } catch (MalformedURLException var7) { url = Loader.getResource(configurationOptionStr); } } if (url != null) { LogLog.debug("Using URL [" + url + "] for automatic log4j configuration."); try { // 这里又是一个信息:selectAndConfigure()翻译就是选择配置文件 OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository()); } catch (NoClassDefFoundError var6) { LogLog.warn("Error during default initialization", var6); } } else { LogLog.debug("Could not find resource: [" + configurationOptionStr + "]."); } } } 从源码中,发现url = Loader.getResource("log4j.properties");从这句代码得到的信息:系统默认是从当前的类路径下找到log4j.properties,而若是maven工程,那么:就应该在resources路径下去找同时在上面的源码中发现OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository()); 查看selectAndConfigure() 发现如下源码 public static void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { Configurator configurator = null; String filename = url.getFile(); if (clazz == null && filename != null && filename.endsWith(".xml")) { clazz = "org.apache.log4j.xml.DOMConfigurator"; } if (clazz != null) { LogLog.debug("Preferred configurator class: " + clazz); configurator = (Configurator)instantiateByClassName(clazz, Configurator.class, (Object)null); if (configurator == null) { LogLog.error("Could not instantiate configurator [" + clazz + "]."); return; } } else { // 有用信息在这里,即 new PropertyConfigurator();创建了一个properties配置对象 configurator = new PropertyConfigurator(); } ((Configurator)configurator).doConfigure(url, hierarchy); } 查看PropertyConfigurator类 首先看到的就是如下的常量信息 static final String CATEGORY_PREFIX = "log4j.category."; static final String LOGGER_PREFIX = "log4j.logger."; static final String FACTORY_PREFIX = "log4j.factory"; static final String ADDITIVITY_PREFIX = "log4j.additivity."; static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory"; // 这是一个重要信息 static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"; // 这也是一个重要信息 static final String APPENDER_PREFIX = "log4j.appender."; static final String RENDERER_PREFIX = "log4j.renderer."; static final String THRESHOLD_PREFIX = "log4j.threshold"; private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer"; private static final String LOGGER_REF = "logger-ref"; private static final String ROOT_REF = "root-ref"; private static final String APPENDER_REF_TAG = "appender-ref"; public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory"; private static final String RESET_KEY = "log4j.reset"; private static final String INTERNAL_ROOT_NAME = "root"; 通过前面的基础,从这源码中,发现有两个信息是要进行配置的static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";static final String APPENDER_PREFIX = "log4j.appender."; 那么这二者是怎么进行配置的? 找寻static final String APPENDER_PREFIX = "log4j.appender."中的appender配置方式直接在当前源码页面搜索appender 发现如下的源代码 Appender parseAppender(Properties props, String appenderName) { Appender appender = this.registryGet(appenderName); if (appender != null) { LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); return appender; } else { // 重要信息就在这里,这里告知了一件事:上面找到的log4j.appender.配置方式为如下的方式 String prefix = "log4j.appender." + appenderName; // 这也是重要信息,layout日志格式化的配置方式 String layoutPrefix = prefix + ".layout"; appender = (Appender)OptionConverter.instantiateByKey(props, prefix, Appender.class, (Object)null); if (appender == null) { LogLog.error("Could not instantiate appender named \"" + appenderName + "\"."); return null; } else { appender.setName(appenderName); if (appender instanceof OptionHandler) { if (appender.requiresLayout()) { Layout layout = (Layout)OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, (Object)null); if (layout != null) {appender.setLayout(layout);LogLog.debug("Parsing layout options for \"" + appenderName + "\".");PropertySetter.setProperties(layout, props, layoutPrefix + ".");LogLog.debug("End of parsing for \"" + appenderName + "\"."); } }String errorHandlerPrefix = prefix + ".errorhandler"; String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props); if (errorHandlerClass != null) { ErrorHandler eh = (ErrorHandler)OptionConverter.instantiateByKey(props, errorHandlerPrefix, ErrorHandler.class, (Object)null); if (eh != null) {appender.setErrorHandler(eh);LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");this.parseErrorHandler(eh, errorHandlerPrefix, props, this.repository);Properties edited = new Properties();String[] keys = new String[]{errorHandlerPrefix + "." + "root-ref", errorHandlerPrefix + "." + "logger-ref", errorHandlerPrefix + "." + "appender-ref"};Iterator iter = props.entrySet().iterator(); while(true) { if (!iter.hasNext()) { PropertySetter.setProperties(eh, edited, errorHandlerPrefix + "."); LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\"."); break; } Entry entry = (Entry)iter.next(); int i; for(i = 0; i < keys.length && !keys[i].equals(entry.getKey()); ++i) { } if (i == keys.length) { edited.put(entry.getKey(), entry.getValue()); }} } }PropertySetter.setProperties(appender, props, prefix + "."); LogLog.debug("Parsed \"" + appenderName + "\" options."); } this.parseAppenderFilters(props, appenderName, appender); this.registryPut(appender); return appender; } } } 通过上述的源码,发现配置log4j.appender.的方式:log4j.appender.+appenderName其中:appenderName就是输出控制器名字继而:推导出log4j.properties配置文件中的一个配置项appender输出方式为:log4j.appender.+appenderName=某一种输出控制器名字其中:输出控制器名字在前面一开始就接触过了因此Log4j.properties的appender输出方式配置方式举例就是log4j.appender.console=org.apache.log4j.ConsoleAppender同样道理,通过第二句代码String layoutPrefix = prefix + ".layout";,也就知道了layout输出格式的配置方式layout日志输出格式配置举例:log4j.appender.console.layout=org.log4j.SimpleLayout 小小总结一波:log4j.properties配置文件中的appender输出控制器 和 layout日志输出格式的配置方式 log4j.appender,console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.log4j.SimpleLayout 继续找第二个配置static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"中的rootLogger配置方式通过log4j.rootLogge进行搜索 发现如下的方法 void configureRootCategory(Properties props, LoggerRepository hierarchy) { String effectiveFrefix = "log4j.rootLogger"; String value = OptionConverter.findAndSubst("log4j.rootLogger", props); if (value == null) { value = OptionConverter.findAndSubst("log4j.rootCategory", props); effectiveFrefix = "log4j.rootCategory"; } if (value == null) { LogLog.debug("Could not find root logger information. Is this OK "); } else { Logger root = hierarchy.getRootLogger(); synchronized(root) { // 这里面执行了这个方式 this.parseCategory(props, root, effectiveFrefix, "root", value); } } } 查看parseCategory() 在这里找到了想要的配置方式 void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) { LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "]."); // 配置方式就在这里,这个操作的意思就是:表示要以逗号的方式来切割字符串,证明了log4j.rootLogger的取值,可以有多个值,使用逗号进行分隔 StringTokenizer st = new StringTokenizer(value, ","); if (!value.startsWith(",") && !value.equals("")) { if (!st.hasMoreTokens()) { return; } // 把字符串通过逗号切割之后,第一个值的用途就在这里 - levelStr、level,即:切割后的第一个值是日志的级别 String levelStr = st.nextToken(); LogLog.debug("Level token is [" + levelStr + "]."); if (!"inherited".equalsIgnoreCase(levelStr) && !"null".equalsIgnoreCase(levelStr)) { logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG)); } else if (loggerName.equals("root")) { LogLog.warn("The root logger cannot be set to null."); } else { logger.setLevel((Level)null); } LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); } logger.removeAllAppenders(); // 字符串切割之后的第一个值是level日志级别,而剩下的值的用途就在这里 while(st.hasMoreTokens()) { // 通过这句代码得知:第2 - 第n个值,就是我们配置的其他信息,这个信息就是appenderName String appenderName = st.nextToken().trim(); if (appenderName != null && !appenderName.equals(",")) { LogLog.debug("Parsing appender named \"" + appenderName + "\"."); Appender appender = this.parseAppender(props, appenderName); if (appender != null) { logger.addAppender(appender); } } } } 通过上述的代码分析,得知log4j.rootLogger的配置方式为:log4j.rootLogger=日志级别,appenderName1,appenderName2,appenderName3....表示可以同时在根节点上配置多个日志输出的途径 2,2,3、最基本的log4j.properties配置通过前面的源码分析之后,得出BasicConfigurator.configure();替代品的properties配置如下:# rootLogger所有logger的根配置 - log4j.rootLogger=日志级别,appenderName1,appenderName2,appenderName3....# 这里的例子没用日志输出路径,这个日志输出路径后续再加log4j.rootLogger=debug,console# appender输出控制器配置 log4j.appender.+appenderName=某一种输出类型 - 采用Console控制台的方式举例log4j.appender.console=org.apache.log4j.ConsoleAppender# 输出的格式配置log4j.appender.+appenderName+layout=某种layout格式类型log4j.appender.console.layout=org.apache.log4j.SimpleLayout 测试 // 注掉这一句就可以了,这句代码的配置由上面的自定义配置进行代替了 //BasicConfigurator.configure(); Logger logger = Logger.getLogger(Log4jTest01.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); 2.2.3、打开日志输出的详细信息 @Test public void test03(){ /* 通过Logger中的开关 打开日志输出的详细信息 查看LogManager类中的方法getLoggerRepository() 找到代码LogLog.debug(msg, ex); LogLog会使用debug级别的输出为我们展现日志输出详细信息 Logger是记录系统的日志,那么LogLog就是用来记录Logger的日志 进入到LogLog.debug(msg, ex);方法中 通过代码:if (debugEnabled && !quietMode) 观察到if判断中的这两个开关都必须开启才行 !quietMode是已经启动的状态,不需要我们去管 debugEnabled默认是关闭的 所以我们只需要设置debugEnabled为true就可以了 */ // 开启 debugEnabled LogLog.setInternalDebugging(true); Logger logger = Logger.getLogger(Log4jTest01.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); } 若开启debugEnabled,那么输出信息就是如下的样子( 如下是举的一个例子而已)0 [main] FATAL cn.zixieqing.HotelJavaApplicationTests - fatal信息1 [main] ERROR cn.zixieqing.HotelJavaApplicationTests - error信息1 [main] WARN cn.zixieqing.HotelJavaApplicationTests - warn信息1 [main] INFO cn.zixieqing.HotelJavaApplicationTests - info信息1 [main] DEBUG cn.zixieqing.HotelJavaApplicationTests - debug信息 开启之后,信息就会更全log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@18b4aac2.log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@18b4aac2 class loader.log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@18b4aac2.log4j: Trying to find [log4j.properties] using sun.misc.Launcher$AppClassLoader@18b4aac2 class loader.log4j: Trying to find [log4j.properties] using ClassLoader.getSystemResource().log4j: Could not find resource: [null].0 [main] FATAL cn.zixieqing.HotelJavaApplicationTests - fatal信息0 [main] ERROR cn.zixieqing.HotelJavaApplicationTests - error信息1 [main] WARN cn.zixieqing.HotelJavaApplicationTests - warn信息1 [main] INFO cn.zixieqing.HotelJavaApplicationTests - info信息1 [main] DEBUG cn.zixieqing.HotelJavaApplicationTests - debug信息 2.2.4、自定义输出格式 patternLayout自定义配置的也就是Layout而已,而自定义就是玩的PatternLayout,这个类有一个setConversionPattern()方法,查看这个方法的源码 public void setConversionPattern(String conversionPattern) { this.pattern = conversionPattern; this.head = this.createPatternParser(conversionPattern).parse(); } 从中发现,需要配置的就是String conversionPattern,因此:在log4j.properties配置文件中添加上conversionPattern属性配置即可,当然:这个属性配置遵循一定的写法,写法如下: %m 输出代码中指定的日志信息 %p 输出优先级,及 DEBUG、INFO 等 %n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n") %r 输出自应用启动到输出该 log 信息耗费的毫秒数 %c 输出打印语句所属的类的全名 %t 输出产生该日志的线程全名 %d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss} %l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10) %F 输出日志消息产生时所在的文件名称 %L 输出代码中的行号 %% 输出一个 "%" 字符 可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式 [%10p]:[]中必须有10个字符,由空格来进行补齐,信息右对齐 [%-10p]:[]中必须有10个字符,由空格来进行补齐,信息左对齐,应用较广泛 上述举例:[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n 修改了log4j.properties的配置如下( 做的修改就是最后两个配置项 )# rootLogger所有logger的根配置 - log4j.rootLogger=日志级别,appenderName1,appenderName2,appenderName3....log4j.rootLogger=debug,console# appender输出控制器配置 log4j.appender.+appenderName=某一种输出类型 - 采用Console控制台的方式举例log4j.appender.console=org.apache.log4j.ConsoleAppender# 输出的格式配置log4j.appender.+appenderName+layout=某种layout格式类型 - 注意:这里类型改了,是PatternLayout,即自定义log4j.appender.console.layout=org.apache.log4j.PatternLayout# 编写自定义输出格式( 直接把上面举例的拿过来 ) - 注意:加上了刚刚说的conversionPattern属性log4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n 测试代码 public void test04(){ LogLog.setInternalDebugging(true); Logger logger = Logger.getLogger(Log4jTest01.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); } 2.2.5、将日志输出到文件中2.2.5.1、改造log4j.properties文件# rootLogger所有logger的根配置 - 这里再加一个filelog4j.rootLogger=debug,console,file# 控制台的appender输出控制器配置log4j.appender.console=org.apache.log4j.ConsoleAppender# 采用自定义控制台输出的格式log4j.appender.console.layout=org.apache.log4j.PatternLayout# 编写控制台自定义输出格式log4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n# 再来一份,变为file的配置 - 把console改为file即可log4j.appender.file=org.apache.log4j.FileAppenderlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n 2.2.5.2、指定文件输出位置 及 字符编码设置查看FileAppender源码,首先看到的是四个属性 // 表示日志文件是否采用内容追加的方式 - 源码中有一个构造方法,这个的默认值是true protected boolean fileAppend; protected String fileName; protected boolean bufferedIO; // 日志文件的大小 - 源码的构造方法中默认值是8192 protected int bufferSize; // 构造方法源码 public FileAppender() { this.fileAppend = true; this.fileName = null; this.bufferedIO = false; this.bufferSize = 8192; } 在FlieAppender中还有一个setFile()的方法,得知这个就是设置文件的方法 ,也就是文件日志文件存放路径位置 public void setFile(String file) { String val = file.trim(); this.fileName = val; } 这里面需要传一个file参数进去,因此:通过MyBatis的知识就知道log4j.properties配置中的这个对应属性就是file了( 截掉set,得到属性名 )# rootLogger所有logger的根配置 - 这里再加一个filelog4j.rootLogger=debug,console,file# 控制台的appender输出控制器配置log4j.appender.console=org.apache.log4j.ConsoleAppender# 采用自定义控制台输出的格式log4j.appender.console.layout=org.apache.log4j.PatternLayout# 编写控制台自定义输出格式log4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n# 再来一份,变为file的配置 - 把console改为file即可log4j.appender.file=org.apache.log4j.FileAppenderlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n# 日志文件保存路径 - 注:前一个file为自定义的appenderName;后一个file是日志文件输出路径的属性,要是怕看错眼,可以把后者换为File大写也没错log4j.appender.file.file=D:\log4j\zixieqing\log4j.log现在看FileAppender的父类WriterAppender,去看字符编码设置 protected boolean immediateFlush; // 这个属性就是编码设置 protected String encoding; protected QuietWriter qw; 因此:加上日志文件输出路径和字符编码之后的log4.properties配置为:# 控制台日志输出设置log4j.rootLogger=debug,console,filelog4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n# 日志文件输出设置log4j.appender.file=org.apache.log4j.FileAppenderlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%nlog4j.appender.file.file=D:\log4j\zixieqing\log4j.loglog4j.appender.file.encoding=UTF-8 2.2.5.3、拆分日志文件一份日志不可能用于一直记录下去,那样的话,日志文件体积就太大了继续查看FileAppender源码,看实现类 2.2.5.3.1、RollingFileAppender实现类这个玩意儿就是利用文件大小来进行日志拆分源码中有两个属性需要关注 // 达到多大文件时进行日志拆分 protected long maxFileSize = 10485760L; // 一共能够拆分出多少份日志文件 protected int maxBackupIndex = 1; 因此:现在将log4j.properties配置文件修改一下即可实现日志根据文件大小进行拆分# 注意:记得在log4j.rootLoger=debug,console,file这一句中加上下面的这个文件大小进行拆分的配置# 如:log4j.rootLoger=debug,console,rollingFilelog4j.appender.rollingFile=org.apache.log4j.RollingFileAppenderlog4j.appender.rollingFile.layout=org.apache.log4j.PatternLayoutlog4j.appender.rollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%nlog4j.appender.rollingFile.file=D:\log4j\zixieqing\log4j.log# 文件多大时进行日志拆分log4j.appender.rollingFile.maxFileSize=10MB# 最多可以拆分多少份log4j.appender.rollingFile.maxBackupIndex=50 2.2.5.3.2、DailyRollingFileAppender实现类 - 建议用这个东西就是根据时间来进行日志拆分,看源码,有这么一个属性 // 时间格式,默认值就是如下的天 private String datePattern = "'.'yyyy-MM-dd"; 在log4j.properties配置文件中修改成如下的配置即可使用# 一样的,要使用这种方式:记得在log4j.rootLogger=debug,console,file这一句中加上下面的这个文件大小进行拆分的配置# 如:log4j.rootLogger=debug,console,dateRollingFilelog4j.appender.dateRollingFile=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.dateRollingFile.layout=org.apache.log4j.PatternLayoutlog4j.appender.dateRollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%nlog4j.appender.dateRollingFile.file=D:\log4j\zixieqing\log4j.log# 加上时间格式 - 下面的值根据实际情况即可log4j.appender.dateRollingFile.datePattern='.'yyyy-MM-dd 2.2.6、日志持久化到数据库表基础字段 CREATE TABLE tbl_log( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) DEFAULT NULL COMMENT '项目名称', createTime varchar(100) DEFAULT NULL COMMENT '创建时间', level varchar(10) DEFAULT NULL COMMENT '日志级别', category varchar(100) DEFAULT NULL COMMENT '所在类的全路径', fileName varchar(100) DEFAULT NULL COMMENT '文件名称', message varchar(255) DEFAULT NULL COMMENT '日志消息', PRIMARY KEY(id)) 项目MySQL驱动 mysql mysql-connector-java ${mysql.version} runtime log4.properties配置 # 配置appender输出方式 输出到数据库表 - 注意:在rootManager中加上logDB,如:log4j.rootLogger=debug,console,logDBlog4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppenderlog4j.appender.logDB.layout=org.apache.log4j.PatternLayoutlog4j.appender.logDB.Driver=com.mysql.jdbc.Driverlog4j.appender.logDB.URL=jdbc:mysql://localhost:3306/testlog4j.appender.logDB.User=rootlog4j.appender.logDB.Password=072413log4j.appender.logDB.Sql=INSERT INTO tbl_log(name,createTime,level,category,fileName,message) values('project_log','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%m') 测试代码 @Test public void test07(){ Logger logger = Logger.getLogger(Log4jTest01.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); } 2.2.7、自定义logger前面配置文件中使用的都是rootManager的logger,接下来就自定义一个logger,看PropertyConfigurator类的源码,它里面有一个属性 // 自定义logger配置的写法 - 这后面拼接的就是自定义的logger名字 static final String LOGGER_PREFIX = "log4j.logger."; 其中:上述说的自定义logger名字遵循父子关系,也就是包关系如:cn.zixieqing.log4j.test.Log4jTest01 它的父logger就是上层的路径或者是更上层的路径 例如: cn.zixieqing.log4j.test cn.zixieqing.log4j ... cn 修改log4j.properties # 根logger,输出级别是trace,在console控制台进行输出log4j.rootLogger=trace,console# 自定义logger,级别为info,在file文件中输出log4j.logger.cn.zixieqing.log4j.test=info,file# 自定义logger,是apache的,级别为errorlog4j.logger.org.apache=error 自定义logger的注意点 如果根节点的logge( 即:rootManager ) 和 自定义logger配置的输出位置是不同的,则取二者的并集,配置的位置都会进行输出操作如果二者配置的日志级别不同,以我们自定义logger的输出级别为主 2.2.8、一份简单的log4j.properties配置 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./1og/zixieqing.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]‰m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sq1=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG 3、JCL全称为Jakarta Commons Logging,是Apache提供的一个通用日志AP注意:这个玩意儿本身没有记录日志的实现功能,而是相当于一个门面。我们可以自由选择第三方的日志组件( log4j、JUL )作为具体实现,common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库 JCL的组成 JCL 有两个基本的抽象类Log:日志记录器LogFactory:日志工厂(负责创建Log实例) 3.1、玩JCL依赖 commons-logging commons-logging 1.2 测试 @Test void jclQuickStartTest() { // LogFactory是org.apache.commons.logging.LogFactory Log log = LogFactory.getLog(JCLTest01.class); log.info("info信息"); } 运行之后看效果会发现:输出格式是JUL格式即:如果没有任何第三方日志框架的时候,默认使用的就是JUL 3.2、引入log4j log4j log4j 1.2.17 然后使用log4j.properties配置文件当再次进行测试时就会变成log4j的输出配置 JCL源码分析 Log接口的4个实现类 JDk13 JDK14 正常java.util.logging Log4j 我们集成的log4j Simple JCL自带实现类 (1)查看Jdk14Logger证明里面使用的是JUL日志框架 - 看import引入的就可以得知 (2)查看Log4JLogger证明里面使用的是Log4j日志框架 - 看import引入的就可以得知 (3)观察LogFactory,看看如何加载的Logger对象 这是一个抽象类,无法实例化 需要观察其实现类LogFactoryImpl (4)观察LogFactoryImpl 真正加载日志实现使用的就是这个实现类LogFactoryImpl (5)进入getLog - 采用打断点debug能够更清晰地看清楚 进入getInstance 找到instance = this.newInstance(name);,继续进入 找到instance = this.discoverLogImplementation(name); 表示发现一个日志的实现 for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); } 遍历我们拥有的日志实现框架 遍历的是一个数组,这个数组是按照 log4j jdk14 jdk13 SimpleLogger 的顺序依次遍历 表示的是,第一个要遍历的就是log4j,如果有log4j则执行该日志框架 如果没有,则遍历出来第二个,使用jdk14的JUL日志框架 以此类推 result = this.createLogFromClass(classesToDiscover[i], logCategory, true); 表示帮我们创建Logger对象 在这个方法中,我们看到了 c = Class.forName(logAdapterClassName, true, currentCL); 是取得该类型的反射类型对象 使用反射的形式帮我们创建logger对象 constructor = c.getConstructor(this.logConstructorSignature); 4、SLF4J这也是一个日志门面( 门面模式 / 外观模式 ),其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用 常见的日志门面和日志实现 常见的日志实现:JUL、log4j、logback、log4j2常见的日志门面 :JCL、slf4j出现顺序 :log4j -->JUL-->JCL--> slf4j --> logback --> log4j2 了解SLF4J 全称:Simple Logging Facade For Java,简单日志门面主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接 4.1、玩SLF4J4.1.1、快速上手准备知识 SLF4J对日志的级别划分trace、debug、info、warn、error五个级别 trace:日志追踪信息 debug:日志详细信息 info:日志的关键信息 默认打印级别 warn:日志警告信息 error:日志错误信息 在没有任何其他日志实现框架集成的基础之上,slf4j使用的就是自带的框架slf4j-simple注意点:slf4j-simple也必须以单独依赖的形式导入进来 org.slf4j slf4j-simple 1.7.25 入门 依赖 org.slf4j slf4j-simple 1.7.25 测试代码 @Test public void test01(){ // 是slf4j包下的 Logger logger = LoggerFactory.getLogger(SLF4JTest01.class); logger.trace("trace信息"); logger.debug("debug信息"); logger.info("info信息"); logger.warn("warn信息"); logger.error("error信息"); } 4.1.2、动态信息输出本质:使用占位符有些时候输出的日志信息,需要我们搭配动态的数据,这些数据有可能是信息,有可能是数据库表中的数据 @Test public void test02(){ Logger logger = LoggerFactory.getLogger(SLF4JTest01.class); String name = "zs"; int age = 23; /* 字符串拼接的形式:logger.info("学生信息-姓名:"+name+";年龄:"+age); 使用JCL的形式:logger.info("学生信息-姓名:{},年龄:{}",new Object[]{name,age}); 这上面两者虽然都可以做到相应的输出,但是:麻烦 */ // 使用SLF4J的形式 logger.info("学生信息-姓名:{},年龄:{}",name,age); } 注意点:如果后面拼接的字符串是一个对象,那么{}并不能充当占位,进行字符串拼接 4.2.3、输出异常信息 @Test public void test03(){ /* 日志对于异常信息的处理 一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式) 我们会根据异常信息提取出有用的线索,来调试bug 但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题 在控制台上其实也会提供相应的异常或者错误信息的输出 但是这种错误输出方式(输出的时间,位置,格式...)都是服务器系统默认的 我们可以通过日志技术,选择将异常以日志打印的方式,进行输出查看 输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义 */ Logger logger = LoggerFactory.getLogger(SLF4JTest01.class); try { Class.forName("aaa"); } catch (ClassNotFoundException e) { // e.printStackTrace(); logger.info("XXX类中的XXX方法出现了异常,请及时关注信息"); // e是引用类型对象,不能根前面的{}做有效的字符串拼接 // logger.info("具体错误是:{}",e); // 我们不用加{},直接后面加上异常对象e即可 - 这是利用了重载方法info(String message, Throwable throwable ) logger.info("具体错误是:",e); } } 4.2.4、SLF4J与日志绑定官网中有一张图,官网地址:https://www.slf4j.org/图中分为了三部分1、在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的slf4j-simple是slf4j官方提供的使用的时候,也是需要导入依赖,自动绑定到slf4j门面上如果不导入,slf4j 核心依赖是不提供任何实现的2、logback和simple(包括nop)都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接注意:nop虽然也划分到实现中了,但是它是指不实现日志记录3、log4j和JUL都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计通过适配桥接的技术,完成的与日志门面的衔接 4.2.5、绑定logback依赖 org.slf4j slf4j-api 1.7.25 ch.qos.logback logback-classic 1.2.11 测试 public class SLF4JTest { @Test public void bingingLogTest() { Logger logger = LoggerFactory.getLogger(SLF4JTest.class); try { Class.forName("aaa"); } catch (ClassNotFoundException e) { logger.info("具体错误是:",e); } }} 结果 10:50:15.391 [main] INFO com.zixieqing.SLF4JTest - 具体错误是:java.lang.ClassNotFoundException: aaa at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) .......这种就看起来很清爽,如果加上slf4j-simple那输出结果又是另一回事,测试跳过上面这种就是不用去管底层到底采用的是哪一种日志实现,可是上面的源代码完全没有改变,照常写,这就是日志门面的好处 4.2.6、slf4j-nop禁止日志打印依赖 junit junit 4.13.2 test org.slf4j slf4j-api 1.7.25 org.slf4j slf4j-nop 1.7.30 ch.qos.logback logback-classic 1.2.11 测试 @Test public void slf4jNopTest() { Logger logger = LoggerFactory.getLogger(SLF4JTest.class); try { Class.forName("aaa"); } catch (ClassNotFoundException e) { logger.info("具体错误是:",e); } } 结果 SLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar:file:/D:/install/maven/apache-maven-3.6.1/maven-repo/org/slf4j/slf4j-nop/1.7.30/slf4j-nop-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: Found binding in [jar:file:/D:/install/maven/apache-maven-3.6.1/maven-repo/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.SLF4J: Actual binding is of type [org.slf4j.helpers.NOPLoggerFactory] 日志内容的相关打印就没了注意:如果想要让nop发挥效果,禁止所有日志的打印,那么就必须要将slf4j-nop的依赖放在所有日志实现依赖的上方 4.2.7、绑定log4j玩这个就需要注意日志框架时间线的问题了出现顺序 :log4j -->JUL-->JCL--> slf4j --> logback --> log4j2 也就是在slf4j之后出现的( 如:logback、log4j2 ),这些都准许slf4j的规范,所以直接导入对应的依赖之后就可以使用,但是在slf4j之前的,并没有预料到会出现后续这些规范嘛,而slf4j想要绑定log4j就需要一个slf4j-log4j12的适配器 玩玩绑定log4j 依赖 junit junit 4.13.2 test org.slf4j slf4j-api 1.7.25 log4j log4j 1.2.17 ${filePath}/logback.log ${pattern} ${logDir}/roll_logback.log ${logDir}/roll.%d{yyyy-MM-dd}.log%i.gz 1KB System.out ${pattern} ERROR ACCEPT DENY 测试代码和前面一样,跳过 6、LOG4J2这玩意儿虽然叫log4j2,也就是对log4j做了增强,但是更多的其实是对logback不足做了优化 log4j2的特征 性能提升 Log4j2包含基于LMAX Disruptor库的下一代异步记录器。在多线程场景中,异步记录器的吞吐量比Log4j 1.x和Logback高18倍,延迟低自动重新加载配置 与Logback一样,Log4j2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件高级过滤 与Logback一样,Log4j2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤此外,过滤器还可以与记录器关联。与Logback不同,Log4j2可以在任何这些情况下使用通用的Filter类插件架构 Log4j使用插件模式配置组件。因此,无需编写代码来创建和配置Appender,Layout,Pattern Converter等。在配置了的情况下,Log4j自动识别插件并使用它们无垃圾机制 在稳态日志记录期间,Log4j2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应性能 log4j2的最佳搭配 采用log4j2 + slf4j的方式 6.1、玩log4j26.1.1、快速上手依赖 org.apache.logging.log4j log4j-api 2.17.1 org.apache.logging.log4j log4j-core 2.17.1 junit junit 4.13.2 test 测试 public class Log4j2Test { @Test public void quickStartTest() { // 是org.apache.logging.log4j包下的 Logger logger = LogManager.getLogger(Log4j2Test.class); logger.fatal("fatal信息"); logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); }} 结果 21:18:43.909 [main] FATAL com.zixieqing.Log4j2Test - fatal信息21:18:43.911 [main] ERROR com.zixieqing.Log4j2Test - error信息 得出结论:log4j2的默认级别是error级别 6.1.2、简单了解log4j2的配置文件log4j2是参考logback创作出来的,所以配置文件也是使用xmllog4j2同样是默认加载类路径(resources)下的log4j2.xml文件中的配置但是:log4j2.xml和logback.xml有区别,第一点就是log4j2.xml中的标签名字是首字母大写;第二点就是多个单词采用的是驼峰命名法,还有其他的区别 简单的log4j2.xml配置文件 < xml version="1.0" encoding="UTF-8" > 3、rootlogger引用Async 测试 @Test public void blendAsync() { Logger logger = LoggerFactory.getLogger(Log4j2Test.class); for (int i = 0; i < 1000; i++) { logger.error("error信息"); logger.warn("warn信息"); logger.info("info信息"); logger.debug("debug信息"); logger.trace("trace信息"); } System.out.println("其他要执行的异步任务1"); System.out.println("其他要执行的异步任务2"); System.out.println("其他要执行的异步任务3"); System.out.println("其他要执行的异步任务4"); System.out.println("其他要执行的异步任务5"); } 6.1.6.2.3、AsyncAppender、AsyncLogge两种方式的建议如果使用异步日志,AsyncAppender、AsyncLogger不要同时出现,不会有这种奇葩需求,同时效果也不会叠加,如果同时出现,那么效率会以AsyncAppender为主同样的,AsyncLogger的全局异步和混合异步也不要同时出现,也不会有这种奇葩,效果也不会叠加文章来自https://www.cnblogs.com/xiegongzi/p/16293103.html
标题:日志系统架构,日志框架是什么
链接:https://yyuanw.com/news/xydt/13348.html
版权:文章转载自网络,如有侵权,请联系删除!
资讯推荐
更多
阴阳师4月22日更新内容:帝释天上线技能调整,红莲华冕活动来袭

阴阳师4月22日更新内容:帝释天上线技能调整,红莲华冕活动来袭[多图],阴阳师4月22日更新的内容有哪些?版本更新

2024-05-01
四川电视台经济频道如何培养孩子的学习习惯与方法直播在哪看?直播视频回放地址

四川电视台经济频道如何培养孩子的学习习惯与方法直播在哪看?直播视频回放地址[多图],2021四川电视台经济频

2024-05-01
湖北电视台生活频道如何培养孩子的学习兴趣直播回放在哪看?直播视频回放地址入口

湖北电视台生活频道如何培养孩子的学习兴趣直播回放在哪看?直播视频回放地址入口[多图],湖北电视台生活频道

2024-05-01
小森生活金币不够用怎么办?金币没了不够用解决方法

小森生活金币不够用怎么办?金币没了不够用解决方法[多图],小森生活金币突然就不够用的情况很多人都有,金币没

2024-05-01