走进JavaWeb技术世界9:Java日志系统的诞生与发展

SoC低功耗设计实战:28nm工艺下四核A7的Hierarchical Flow全流程解析 本文深入解析了在TSMC 28nm工艺上,采用Hierarchical Flow(层次化流程)实现四核Cortex-A7 SoC低功耗设计的全流程。文章从项目背景切入,详细探讨了基于UPF的电源规划、时钟树综合、接口时序收敛以及物理验证等关键环节,分享了使用Cadence Innovus等工具进行低功耗物理实现的实战经验与挑战。 阅读详情

一个著名的日志系统是怎么设计出来的?、

转载自:码农翻身

1前言

Java帝国在诞生之初就提供了集合、线程、IO、网络等常用功能,从C和C++领地那里吸引了大量程序员过来加盟,但是却有意无意地忽略了一个重要的功能: 输出日志。

 

对于这一点,IO大臣其实非常清楚, 日志是个很重要的东西, 因为程序运行起来以后, 基本上就是一个黑盒子,如果程序的行为和预料的不一致,那就是出现Bug了,如何去定位这个Bug 呢?   

 

臣民们能用的工具有两个,第一个就是单步调试,一步步地跟踪,查看代码中变量的值, 这种办法费时费力, 并且只能在程序员的机器上才能用。 

 

第二种就是在特定的地方打印日志, 通过日志的输出,帮助快速定位。尤其是当代码在生产环境上跑起来以后, 日志信息更是必不可少,要不然出了状况两眼一抹黑,上哪儿找问题去? 总不能让臣民们把自己变成一个线程进入系统来执行吧?

 

但是IO大臣也有自己的小算盘: 日志嘛, 用我的System.out.println(.....) 不就可以了?!   我还提供了System.err.println不是?  

 

在IO大臣的阻挠下, 从帝国的第一代国王到第三代国王, 都没有在JDK中提供日志相关的工具包, 臣民们只好忍受着去使用System.out.println去输出日志,把所有的信息都输出到控制台, 让那里变成一堆垃圾。

 

2张家村

 张家村的电子商务系统也不能幸免,自然也遇到了日志的问题。经验丰富的老村长已经烦透了System.out.println所输出的大量难于理解的无用信息,看着村民民整天苦逼地和这些System.out做斗争,他找来了小张,命令他设计一个通用的处理日志的系统。

 

小张在消息队列和JMS的设计上花了不少功夫, 积累了丰富的经验,从那以后一直都是实现业务代码,一直都是CRUD, 张二妮整天笑话自己是HTML填空人员,这一回一定要让她看看自己的设计功力!

 

(传送门:《Java帝国之消息队列》,《Java帝国之JMS的诞生》)

老村长给小张下达的需求是这样的:

 

1.  日志消息除了能打印到控制台, 还可以输出到文件,甚至可以通过邮件发送出去(例如生成环境出错的消息)

 

2. 日志内容应该可以做格式化, 例如变成纯文本,XML, HTML格式等等

 

3. 对于不同的Java class,不同的 package , 还有不同级别的日志,应该可以灵活地输出到不同的文件中。

例如对于com.foo 这个package,所有的日志都输出到 foo.log 文件中

对于com.bar 这个package ,所有文件都输出到bar. log文件中

对于所有的ERROR级别的日志,都输出到  errors.log文件中

 

4. 能对日志进行分级, 有些日志纯属debug , 在本机或者测试环境使用, 方便程序员的调试, 生产环境完全不需要。有些日志是描述错误(error)的, 在生产环境下出错的话必须要记录下来,帮助后续的分析。

 

小张仔细看了看,拍着胸脯对老村长说:“没问题, 明天一定让您老看到结果。”

 

3小张的设计

老村长走了以后,小张开始分析需求, 祭出“面向对象设计大法”,试图从村长的需求中抽象出一点概念。

 

首先要记录日志,肯定需要一个类来表达日志的概念,这个类至少应该有两个属性,一个是时间戳,一个是消息本身,把它叫做LoggingEvent吧,记录日志就像记录一个事件嘛。

 

其次是日志可以输出到不同的地方,控制台、文件、邮件等等, 这个可以抽象一下,不就是写到不同的目的地吗? 可以叫做LogDestination? 

 

嗯, 还是简单一点,叫做Appender吧, 暗含了可以不断追加日志的意思。

至于第二条的日志内容可以格式化,完全可以比葫芦画瓢, 定义一个Formatter接口去格式化消息。

 

对了, Appender 应该引用Formatter ,这样以来就可以对LoggingEvent记录格式化以后再发送。 

 

第三条需求把小张给难住了,不同的class, package 输出的目的地不同?  “目的地”这个概念是由Appender来表达的, 难道让不同的class, package 和Appender关联? 不不, 不能这样 ! 

 

还需要一个新的概念 , 这个概念是什么? 

 

从用户角度想一下, 村民们要想获取日志,必须得先获取个什么东西,这个东西是不是可以称为Logger啊?  灵感的火花就闪了那么一下就被小张抓住了: 获取Logger的时候要传入类名或者包名! 

这样一来,不同的class, package就区分开了, 然后让Logger 和Appender关联,灵活地设置日志的目的地, 并且一个Logger可以拥有多个Appender,同一条日志消息可以输出到多个地方, 完美!

小张迅速地画出了核心类的类图

:

 

 

还算漂亮,小张陶醉着自我欣赏了一下。 

 

再接再厉, 把第四条需求也设计一下,日志要分级,这个简单, 定义一个Priority的类,里边定义5个常量DEBUG, INFO, WARN, ERROR, FATAL, 表示5个不同的级别就OK了。当然这我5个级别有高低之分, DEBUG级别最低, FATAL级别最高。

 

还可以给Logger增加一些辅助编程的方法,如Logger.debug(....) , Logger.info(...)  , Logger.warn(...) 等等, 这样村民们将来就可以轻松地输出各种级别的日志了。

 

等一下, 老村长还说过“对于所有的ERROR级别的日志,都输出到  errors.log文件中” 类似这样的需求, 好像给忽略了。

 

这也好办嘛, 只要在Appender上增加一个属性,就叫做Priority, 如果用户要输出的日志是DEBUG级别, 但是有个FileAppender的Priority是 ERROR级别,那这个日志就不用在这个FileAppender中输出了 ,因为ERROR级别比DEBUG级别高嘛。

 

同理, 在Logger类上也可以增加一个Priority的属性,用户可以去设置, 如果一个Logger的Priority是ERROR, 而用户调用了这个Logger的debug方法, 那这个debug 的消息也不会输出。

 

小张全心全意地投入到设计当中,一看时间, 都快半夜了, 赶紧休息, 明天向村长汇报去。 

 

4正交性

第二天, 小张给老村长展示了自己设计的LoggerEvent, Logger , Appender, Formatter, Priority 等类和接口, 老村长捻着胡子满意地点点头:“不错不错,与上一次相比有巨大的进步。你知不知道我在需求中其实给了你引导?”

 

“引导? 什么引导? ”

 

“就是让你朝着正交的方向去努力啊”

 

“正交? ”

 

‘“如果你把Logger, Appender, Formatter看成坐标系中的X轴,Y轴,Z轴, 你看看,这三者是不是可以独立变化而不互相影响啊?”

 

“我赛,果然如此,我可以任意扩展Appender接口而影响不到Logger和Formatter, 无论有多少个Logger 都影响不了Appender和Formatter , 这就是正交了?”

 

“是啊,当你从系统中提取出正交的概念的时候,那就威力无比了,因为变化被封装在了一个维度上,你可以把这些概念任意组合,而不会变成意大利面条似的代码。 ”

 

听到村长做了理论的升华, 小张兴奋得直搓手。 

 

“好吧,你把这个设计实现了吧,对了,你打算叫什么名字? ”  村长问道

 

“我打算把他叫做Log4j , 意思是Log for Java”

 

“不错,就这么定了吧”

 

5Log4j

小张又花了两个月的时间把Log4j 开发了出来, 由于Log4j有着良好的设计,优异的性能, 不仅仅是张家村的人在用, Java帝国的很多村镇、部落都爱上了它。

 

后来张家村把Log4j 在Apache部落开源了, 这下子吸引了无数的人无偿帮助测试它,扩展它,改进它, 很快就成了帝国最流行的日志工具。

 

张家村建议帝国把Log4j 纳入到JDK 中, 帝国那效率低下的官僚机构竟然拒绝了。  消息传到了IO大臣的耳朵里,他不由的扼腕叹息: 唉,失去了一次极好的招安机会啊。 现在唯一的办法就是赶紧上奏皇上,在官方也提供一套,争取让臣民们使用官方版本。

 

到了第四代国王(JDK1.4),臣民们终于看到了帝国提供的java.util.logging包,也是用来记录日志的,并且其中的核心概念Logger, Formatter, Handler 和 Log4j非常相似,只是为时已晚, Log4j早已深入人心了, 不可撼动了。  

 

6尾声

Log4j 在Apache开源以后, 小张也逐渐地有点落寞,他闲不住又写了一个工具,叫做logback, 有了之前的经验,这logback 比log4j 还要快。 

 

如今的日志世界有了很多的选择 ,除了java.util.logging, log4j 之外,还有logback,tinylog 等其他工具。

 

小张想了想, 这么多日志工具,用户如果想切换了怎么办?不想用log4j了,能换到logback吗? 

 

我还是提供一个抽象层吧, 用户用这个抽象层的API来写日志, 底层具体用什么日志工具不用关心,这样就可以移植了。 

 

小张把这抽象层就叫做Simple Logging Facade for Java,简称SLF4J。  

 

对于Log4j , JDK logging, tinylog 等工具, 需要一个适配层, 把SLF4J 的API转化成具体工具的调用接口。 

 

由于Logback这个工具也是出自小张之手, 直接实现了SLF4J的API,所以连适配层都不需要了, 用起来速度飞快,效率最高,SLFJ4+Logback 成为了很多人的最爱, 大有超越Apache Common Logging + Log4j 之势。 

 

后记: 本文主要想讲一下日志工具的历史和现状, 尤其是Log4j核心的设计理念。

 

文中的小张其实就是Ceki Gülcü,他开发了Log4j , logback,以及slfj4, 为Java的日志事业做出了卓越的贡献。

 

日志?聊一聊slf4j吧

转自https://juejin.im/post/5ad1ccc86fb9a028c14ae528

作为一个Java程序员,肯https://juejin.im/post/5ad1ccc86fb9a028c14ae528定对于日志记录不会陌生,无论项目大小,日志记录都是必须的;因为好的日志可以很容易的帮助我们定位一些生产问题。

我怀念的是 无话不说 System.out.println("这里是重要的日志");

我怀念的是 一起作梦 System.err.println("这里是错误的日志");

对于日常开发来说,其实System.out.println挺好用的,但是为什么在实际的开发应用中不使用这个来输出日志呢?

System.out.println()除了使用方便一点之外,其他感觉真的没有什么多大的用处。方便在哪儿呢?在Eclipse中你只需要输入syso【IDEA中输出sout即可】,然后按下代码提示键,这个方法就会自动出来了,相信这也是很多Java新手对它钟情的原因,因为我一开始也是这么干的,直到...【此处省略泪水】。那缺点又在哪儿了呢?这个就太多了,比如日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分……

记得我最开始接触的是log4j,log4j作为Apache的一个开放源代码的项目,通过使用log4j,我们可以控制日志信息输送的目的地是控制台、文件等我们期望它输出到的地方;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。重要的是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

确实,log4j作为最先比较流行的日志框架,给我们在应用开发和维护带来了很大的便捷,那么为什么这样优秀的框架最后还是慢慢的走下“神坛”呢?而逐渐被logback代替呢?下面是摘自网上一位大佬对于这个问题的解释:

无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进。不过尽管难以一一细数,这里还是列举部分理由为什么选择logback而不是log4j。牢记logback与log4j在概念上面是很相似的,它们都是有同一群开发者建立。

  • 更快的执行速度

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

  • 充分的测试

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

slf4j log4j logback

slf4j:The Simple Logging Facade for Java 即java的简单日志门面

简答的讲就是slf4j是一系列的日志接口,slf4j作为一个日志的抽象行为存在,但是并没有提供真正的实现。

slf4j为各种日志框架提供了一个统一的界面,使用户可以用统一的接口记录日志,但是动态地决定真正的实现框架。logback,log4j,common-logging等框架都实现了这些接口。

slf4j 源码分析

想了很久都不知道从哪里开头写比较合适,slf4j包中总共29个类【1.7.21版本】,不可能一一列举。所以就从我们熟知的这个语句来说。

private static final Logger logger =
LoggerFactory.getLogger(DemoTest.class);

上面这段代码其实就是我们平时在代码里面申明一个日志对象的常用方式。

 

先来看下Logger接口提供的方法;

package org.slf4j;
 
public interface Logger {
    //根Logger
    String ROOT_LOGGER_NAME = "ROOT";
    String getName();
    //判断记录器Trace跟踪是否激活;Trace跟踪激活后一般会打印比较详细的信息。
    boolean isTraceEnabled();
    //trace级别
    void trace(String var1);
    void trace(String var1, Object var2);
    void trace(String var1, Object var2, Object var3);
    void trace(String var1, Object... var2);
    void trace(String var1, Throwable var2);
    boolean isTraceEnabled(Marker var1);
    void trace(Marker var1, String var2);
    void trace(Marker var1, String var2, Object var3);
    void trace(Marker var1, String var2, Object var3, Object var4);
    void trace(Marker var1, String var2, Object... var3);
    void trace(Marker var1, String var2, Throwable var3);
    //进行预先判断,提升系统性能
    boolean isDebugEnabled();
    void debug(String var1);
    void debug(String var1, Object var2);
    void debug(String var1, Object var2, Object var3);
    void debug(String var1, Object... var2);
    void debug(String var1, Throwable var2);
    boolean isDebugEnabled(Marker var1);
    void debug(Marker var1, String var2);
    void debug(Marker var1, String var2, Object var3);
    void debug(Marker var1, String var2, Object var3, Object var4);
    void debug(Marker var1, String var2, Object... var3);
    void debug(Marker var1, String var2, Throwable var3);
    //info级别
    boolean isInfoEnabled();
    void info(String var1);
    void info(String var1, Object var2);
    void info(String var1, Object var2, Object var3);
    void info(String var1, Object... var2);
    void info(String var1, Throwable var2);
    boolean isInfoEnabled(Marker var1);
    void info(Marker var1, String var2);
    void info(Marker var1, String var2, Object var3);
    void info(Marker var1, String var2, Object var3, Object var4);
    void info(Marker var1, String var2, Object... var3);
    void info(Marker var1, String var2, Throwable var3);
    //warn级别
    boolean isWarnEnabled();
    void warn(String var1);
    void warn(String var1, Object var2);
    void warn(String var1, Object... var2);
    void warn(String var1, Object var2, Object var3);
    void warn(String var1, Throwable var2);
    boolean isWarnEnabled(Marker var1);
    void warn(Marker var1, String var2);
    void warn(Marker var1, String var2, Object var3);
    void warn(Marker var1, String var2, Object var3, Object var4);
    void warn(Marker var1, String var2, Object... var3);
    void warn(Marker var1, String var2, Throwable var3);
    //error级别
    boolean isErrorEnabled();
    void error(String var1);
    void error(String var1, Object var2);
    void error(String var1, Object var2, Object var3);
    void error(String var1, Object... var2);
    void error(String var1, Throwable var2);
    boolean isErrorEnabled(Marker var1);
    void error(Marker var1, String var2);
    void error(Marker var1, String var2, Object var3);
    void error(Marker var1, String var2, Object var3, Object var4);
    void error(Marker var1, String var2, Object... var3);
    void error(Marker var1, String var2, Throwable var3);
}
 

isXXXXEnabled

以isDebugEnabled来说明下这个isXXXXEnabled方法的作用。

logger.debug("the debug msg is " +doMethod());

假设我们的日志级别设置为info,那这句话不会输出日志,但这个方法还是会调用(预判断作用)。要调用这个方法,必须提供参数。doMethod()方法返回的结果就是参数的一部分。doMethod()要执行n秒钟,n秒钟后,进入到debug()方法里;

但是日志级别为info。结果是日志虽然没有输出,却花费了n秒钟来构造参数。很显然这里得不偿失的。尽管实际应用中几乎不可能有这种花n秒钟来构造这样一个参数的情况,但如果并发数大的话,这样写还是会影响系统的性能的。这个时候,就应该写成:

if(logger.isDebugEnabled()){
    logger.debug("the debug msg is " + doMethod());
} 

接下来说LoggerFactory这个类;先从getLogger这个方法为入口来看下:

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}
 
public static Logger getLogger(Class<?> clazz) {
    Logger logger = getLogger(clazz.getName());
    if (DETECT_LOGGER_NAME_MISMATCH) {
        Class<?> autoComputedCallingClass = Util.getCallingClass();
        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName()));
            Util.report("See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
        }
    }
 
    return logger;
}

getLogger方法提供了两种重载方式,一种是传入一个name,用于标注当前日志的名字。另外一个是提供一个Class对象,其实里面也是通过clazz.getName()来作为日志的名称。

从上面的代码中可以比较明显的看到ILoggerFactory这个接口。

package org.slf4j;
 
public interface ILoggerFactory {
    Logger getLogger(String var1);
}

ILoggerFactory这个接口实际上就是为不同接入的日志实现提供了统一的顶层类型;每个日志框架都需要实现ILoggerFactory接口,来说明自己是怎么提供Logger的。像log4j、logback能够提供父子层级关系的Logger,就是在ILoggerFactory的实现类里实现的。同时,它们也需要实现Logger接口,以完成记录日志。

logback中的实现

public class LoggerContext extends ContextBase implements
ILoggerFactory, LifeCycle

上面提到过,对于不同的日志框架的实现都实现了ILoggerFactory接口。

 @Override
public final Logger getLogger(final String name) {
 
    if (name == null) {
        throw new IllegalArgumentException("name argument cannot be null");
    }
 
    // if we are asking for the root logger, then let us return it without
    // wasting time
    if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
        return root;
    }
 
    int i = 0;
    Logger logger = root;
 
    // check if the desired logger exists, if it does, return it
    // without further ado.
    Logger childLogger = (Logger) loggerCache.get(name);
    // if we have the child, then let us return it without wasting time
    if (childLogger != null) {
        return childLogger;
    }
 
    // if the desired logger does not exist, them create all the loggers
    // in between as well (if they don't already exist)
    String childName;
    while (true) {
        int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
        if (h == -1) {
            childName = name;
        } else {
            childName = name.substring(0, h);
        }
        // move i left of the last point
        i = h + 1;
        synchronized (logger) {
            childLogger = logger.getChildByName(childName);
            if (childLogger == null) {
                childLogger = logger.createChildByName(childName);
                loggerCache.put(childName, childLogger);
                incSize();
            }
        }
        logger = childLogger;
        if (h == -1) {
            return childLogger;
        }
    }
}

关于logback的源码可以参考这个系列的文章:logback源码系列文章

 

Java日志框架梳理-SLF4J+log4j

转自https://blog.youkuaiyun.com/xktxoo/article/details/76359366

  log4j的配置文件是用来设置纪录器的级别、存放位置和布局的,可以通过Java属性文件(key=value)格式设置或XML格式设置。log4j配置文件元素简介:

Logger

  Logger是一个允许应用纪录日志的对象,开发者不必考虑输出位置。应用可将具体需要打印的信息通过一个Object传递。Logger是命名了的实体,每个Logger相互独立,它们的名字大小写敏感且遵循层次化命名规则:

如果logger的名称带上一个点号后是另外一个logger的名称的前缀,则前者被称为后者的祖先。如果logger与其后代logger之间没有其他祖先,则前者就被称为子logger之父。

  根logger(rootLogger)位于logger等级的最顶端,它是每个层次等级的共同始祖。获取根logger的方式:

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

  logger可被分配级别,如果logger没有被分配级别,那它将从没有分配级别的最近祖先继承级别,直至根logger,默认情况下,根logger级别为DEBUG。

  logger可通过additivity标识设置其Appender的叠加性,定义如下:

logger A记录语句的输出会发送给A及其祖先的全部Appender,如果logger A的某个祖先B设置叠加性标识为false,则A的输出会发送给A与B(包含B)之间的所有Appender,但不会发送给B的任何祖先Appender

Appender

  每个Appender可独立配置纪录日志的设备,可以是:控制台、文件、数据库、消息系统等。log4j提供的Appender具体有如下几种:

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

Layout

  Layout也被称为Formatters,负责对日志时间中的数据进行转换和格式化,Layout决定了数据在一条日志记录中的最终形式。log4j提供的Layout有如下几种:

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



  log4j采用类似C语言中printf的打印格式化日志信息,打印参数如下

类型描述
%m输出代码中制定的消息
%p输出日志级别
%r输出自应用启动到出处该日志记录耗费的毫秒数
%c输出触发该日志事件的类
%t输出触发该日志事件的线程
%d输出日志事件发生的时间,如:%-d{yyyy-MM-dd HH:mm:ss}
%l输出日志发生的位置,包括类信息、线程、行数

Level

每个打印日志都可以单独指定日志级别,通过配置文件来控制输出级别。log4j提供的日志级别如下:

类型描述
ALL最低级别,用于打开所有日志记录
TRACE指定粒度比DEBUG更细的事件
DEBUG指定细粒度信息事件,对调试应用程序有帮助
INFO指定粗粒度信息事件,突出强调程序运行过程
WARN指定具有潜在危害的情况
ERROR指定错误事件,程序仍然允许运行
FATAL指定非常严重的错误事件,可能导致应用程序终止
OFF最高等级,用于关闭日志记录

 


SLF4J+log4j实践

  log4j适配器绑定可添加如下pom依赖,添加配置后会自动拉下来两个依赖包,分别是slf4j-api-1.7.25和log4j-1.2.17。

 

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
  •  

  log4j必须指定配置文件或默认配置。如果程序中引入了以上包,在没有编写配置文件,且没有设置默认配置器时打印日志,log4j会报如下错误: 

public class Test {
 
    public static void main(String [] args) {
        //BasicConfigurator.configure();
        Logger logger  = LoggerFactory.getLogger(Test.class);
        logger.debug( " this is {} debug log", Test.class.getName() );
        logger.error( " this is {} error log", Test.class.getName());
    }
}
 
结果:
log4j:WARN No appenders could be found for logger (com.xiaofan.test.Test).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
  •  

  通过BasicConfigurator.configure() 可指定log4j默认配置器,该配置默认生成rootLogger,并添加一个控制台Appender,源码如下:

  1.  

    static public void configure() {
        Logger root = Logger.getRootLogger();
        root.addAppender(new ConsoleAppender(
               new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
      }

     

  •  

  打印日志结果如下:

0 [main] DEBUG com.xiaofan.test.Test  -  this is com.xiaofan.test.Test debug log
2 [main] ERROR com.xiaofan.test.Test  -  this is com.xiaofan.test.Test error log

​​​​​​

  编写log4j配置文件有两种方式:Java属性文件(key=value)和XML形式。

  • log4j.properties 
       
      属性配置文件范例如下:
### set log levels ###
log4j.rootLogger = debug,stdout,D,E
 
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =  %-d{yyyy-MM-dd HH:mm:ss} %p [%c] %m%n
 
### 输出到日志文件 ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG 
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
 
### 保存异常信息到单独文件 ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
 
 
### logger ### 
# 设置后com.xiaofan路径下的日志将只输出ERROR日志
#log4j.logger.com.xiaofan=ERROR

  将log4j.properties文件放到工程的resources文件夹下,如果程序没有显示指定其他配置文件,log4j会默认加载log4j.properties文件作为配置文件。可通过PropertyConfigurator.configure()显示指定外部配置文件。 
  

  测试代码如下:

package com.xiaofan.test;
 
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
 * Created by Jerry on 17/7/24.
 */
public class Test {
 
    public static void main(String [] args) {
        PropertyConfigurator.configure(Test.class.getResource("/log4j.properties"));
        Logger logger  = LoggerFactory.getLogger(Test.class);
        logger.debug( " this is {} debug log", Test.class.getName() );
        logger.error( " this is {} error log", Test.class.getName());
    }
}
 
结果:
console:
    2017-07-27 09:44:50 DEBUG [com.xiaofan.test.Test]  this is com.xiaofan.test.Test debug log
    2017-07-27 09:44:50 ERROR [com.xiaofan.test.Test]  this is com.xiaofan.test.Test error log
/logs/error.log:
    2017-07-27 09:48:27  [ main:1 ] - [ ERROR ]   this is com.xiaofan.test.Test error log
/logs/log.log:
    2017-07-27 09:48:27  [ main:0 ] - [ DEBUG ]   this is com.xiaofan.test.Test debug log
    2017-07-27 09:48:27  [ main:1 ] - [ ERROR ]   this is com.xiaofan.test.Test error log
  • log4j.xml

      XML配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' >
 
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                   value="[%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p] %c{2\} [%t] - %m%n" />
        </layout>
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="levelMin" value="debug" />
            <param name="levelMax" value="error" />
            <param name="AcceptOnMatch" value="true" />
        </filter>
    </appender>
 
    <appender name="ERROR" class="org.apache.log4j.RollingFileAppender">
        <param name="File" value="logs/error.log" />
        <param name="Append" value="true" />
        <param name="threshold" value="error" />
        <param name="MaxBackupIndex" value="10" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%p (%c:%L)- %m%n" />
        </layout>
    </appender>
 
    <appender name="INFO" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="logs/log.log" />
        <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                   value="[%d{MMdd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n" />
        </layout>
    </appender>
 
    <logger name="mylogger" additivity="true">
        <level value="debug" />
        <appender-ref ref="INFO" />
    </logger>
 
    <root>
        <priority value ="debug"/>
        <appender-ref ref="INFO"/>
        <appender-ref ref="ERROR" />
        <appender-ref ref="console" />
    </root>
 
</log4j:configuration>

 将log4j.xml文件放到工程的resources文件夹下,如果程序没有显示指定其他配置文件,log4j会默认加载log4j.xml文件作为配置文件。可通过DOMConfigurator.configure()显示制定外部配置文件。 
  
  测试代码如下:

  1.  

    public class Test {
     
        public static void main(String [] args) {
            DOMConfigurator.configure(Test.class.getResource("/log4j.xml"));
            Logger logger  = LoggerFactory.getLogger(Test.class);
            logger.debug( " this is {} debug log", Test.class.getName() );
            logger.error( " this is {} error log", Test.class.getName());
        }
    }
     
    结果:
    控制台:
        [2017-07-27 12:43:20,474 DEBUG] test.Test [main] -  this is com.xiaofan.test.Test debug log
        [2017-07-27 12:43:20,484 ERROR] test.Test [main] -  this is com.xiaofan.test.Test error log
    /logs/error.log
        ERROR (com.xiaofan.test.Test:18)-  this is com.xiaofan.test.Test error log
    /logs/log.log
        [0727 12:50:04 829 DEBUG] [main] xiaofan.test.Test -  this is com.xiaofan.test.Test debug log
        [0727 12:50:04 830 ERROR] [main] xiaofan.test.Test -  this is com.xiaofan.test.Test error log

     

提示词编写:从新手到专家的进阶之路 本文旨在为提示词编写者提供一条从新手到专家的进阶之路。我们将详细探讨提示词编写的背景与意义、基础与进阶技巧、实战应用以及最佳实践。通过逐步深入的分析与实操,读者将掌握提示词编写的核心要点,提高编写效率和作品质量。提示词是用于引导人工智能生成文本的关键词或短语,它包含了作者想要传达的主题、情感和细节。随着人工智能技术的普及,自动化写作工具逐渐应用于各行各业。为了提高写作效率,项目团队决定开发一款基于人工智能的自动化写作工具。成功开发了一款自动化写作工具,生成文本质量较高。 阅读详情

相关推荐

从Jira到Notion:用Sequential Thinking重构你的敏捷工作流(含GPT-6配置模板)

本文探讨了如何利用Sequential Thinking这一AI认知模型,重构敏捷开发工作流。通过连接Jira、Notion等工具,构建动态依赖图谱,实现从被动响应到主动规划的转变。文章提供了详细的配置教程和GPT-6配置模板,帮助团队实现任务自动拆解、智能看板生成与跨平台协调,从而提升项目管理效率。

gpt4scribbler的博客 205

springboot 日志操作

springboot日志操作 作用:使用日志操作类将一些信息输出到日志中,后续再对日志进行分析 ********************************** 相关类 LoggerFactory:获取logger对象 public final class LoggerFactory { ************* 常用方法 public stati...

weixin_43931625的博客 895

XGBoost特征重要性为0的问题:深度解析与应对策略

在机器学习领域,XGBoost(Extreme Gradient Boosting)作为一款高效且强大的集成学习算法,已经被广泛应用于各类预测任务中。然而,在实际应用过程中,许多数据科学家和工程师们遇到了一个令人困惑的现象——某些特征的重要性值为0。这不仅影响了模型的解释性,还可能导致特征选择过程中的误判。本文将深入探讨这一问题,分析其背后的原因,并提供相应的解决方案。在深入探讨特征重要性为0的问题之前,我们首先需要了解XGBoost是如何计算特征重要性的。XGBoost提供了三种常见的特征重要性计算方法:

cda2024的博客 1704

日志:打印:多个对象型参数(3个及以上)

log4j是程序中经常使用的日志工具,分为5个级别:log4j的级别 今天的问题:打印参数个数为3个及以上时,如何处理? 直接按部就班会出现: logger.info("tryGetCustomerByJdPinAndDepartNo---入参 jdPin:{},departNo:{},init;{}", jdPin, departNo,JSON.toJSONString(init))...

6296

全志T113 Tina5.0开发:(零)源码下载&修改调试串口

本文章主要作为自己的笔记,如有错误欢迎指出$ repo start product-da-t113-tina-stable --all # 全部下载完成之后,创建分支运行过程可能会出现版本问题;rm -rf .repo //先删除试试( 我安装的是3.6版本)

u014428915的博客 1164

Cherry Studio 新手必看:5分钟搞定MCP服务器配置(附常见问题解决)

本文为Cherry Studio新手提供了详细的MCP服务器配置指南。文章从核心概念入手,解释了Cherry Studio作为MCP客户端如何通过MCP协议安全、标准化地扩展AI助手能力。重点讲解了uv和Bun环境的安装与常见网络问题的解决方案,并演示了如何添加首个MCP服务器(如mcp-server-fetch),助你快速构建可扩展的智能工作流。

corn8的博客 595

java 各种日志输出写法_logger4j的日志输出怎么写好?推荐格式

Logge有5个日志等级 trace、debug、info、warn、error, 这5个级别都有5个以String为首参数重载方法,如errorvoid error(String var1);void error(String var1, Object var2);void error(String var1, Object var2, Object var3);void error(Strin...

weixin_39633089的博客 709

QT5.12.1报错:1. clang++:error: :fatal error andriod Input/output error

QT5.12.1报错:1. clang++:error: :fatal error andriod Input/output error 解决方式: 1.qt程序不要放在共享目录下编译,直接拷贝到ubuntu系统中编译 2.修改/home/xt/Qt5.12.1/5.12.1/android_armv7/mkspecs/android-clang中 #QMAKE_LINK = QMAKECXXQMAKE_CXX QMAKEC​XXQMAKE_CFLAGS -Wl,–exclude-libs,libgc

xt18971492243的博客 736

java中log日志的输出方法说明

java中log日志的输出方法说明

m0_72167535的博客 2743

高级电机控制场景实例:基于Simulink的永磁同步电机基于Luenberger观测器的弱磁控制仿真

本文基于Simulink实现表贴式永磁同步电机(SPMSM)的Luenberger观测器弱磁控制仿真,通过构建“观测-弱磁-控制”协同逻辑,突破传统矢量控制的基速限制。利用线性化模型设计观测器实时估计电流与磁链,结合电压极限约束动态注入负d轴电流,实现转速扩展至12000rpm(4倍基速),弱磁区电流THD低于3%,动态响应时间小于5ms,验证了高速运行下系统稳定性与鲁棒性。附完整建模步骤、MATLAB函数代码及性能分析,适用于电动汽车、机床等高速驱动场景,为高级电机控制提供可复现的技术范式。

amy_mhd的博客 31

uniapp [全端兼容] - 实现人脸识别对比+人脸相似度,从相册选择或拍照上传两张图片进行人脸对比得到人脸相似度及判断是否是同一个人,人脸活体检测及对比(免费纯前端方案+调用第三方API接口方案)

uniApp人脸对比 人脸对比识别 人脸对比相似度,uniapp实现获取手机摄像头权限,调用相机拍照实现人脸识别相似度对比,uniapp实现人脸识别对比,计算两张人脸的相似度,从而判断是否同一个人,并给出相似度评分。uniapp H5网页如何实现人脸相似度比对,uniapp纯前端怎么对比两张图片的相似度,计算两幅图像的相似度,图像相似度对比方法,UNI-APP人脸相似度对比,快速识别两张照片的相似程度(用百分比)图片相似度对比在线测试,图片相似度对比,uniapp微信小程序人脸相似度对比分析,uniapp

🏆 前端领域优质创作者 5020

java中Object... 的用法是什么意思?

@SuppressWarnings("unchecked")      public List&lt;T&gt; findByConditions(String hql,Object... conditions) throws Exception{          Session session = getSessionFactory().getCurrentSession();        ...

blog 2万+

从 on_event 到 lifespan:FastAPI 生命周期管理的旧方法与新方法对比与迁移

从 on_event 到 lifespan:FastAPI 生命周期管理的旧方法与新方法对比与迁移

XiaoqiangClub的博客 1021

清空运行中的jar 日志_走进JavaWeb技术世界9:Java日志系统的诞生与发展

Java日志系统的演变史我们先看一个故事。项目经理A带着一帮兄弟开发了一套复杂的企业ERP系统,这个系统一连开发了好几年,开发人员也换了好几拨。阶段一最开始的时候,项目经理A安排小B在系统中添加日志功能,在控制台上打印一些必要的信息。最开始的时候,由于项目的功能比较少,于是小B就是用System.out.println的方式打印日志信息。经理A感觉这样使用比较方便,也便于项目小组人员的使用,于是就...

weixin_39858275的博客 206

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Java工程师学习指南第2部分:JavaWeb技术世界

本文整理了微信公众号【Java技术江湖】发表和转载过的Java Web优质文章,想看到更多Java技术文章,就赶紧关注吧。 IDEA中的Maven实战 老师,免费版的IDEA为啥不能使用Tomcat? IDEA 神器入坑指南!17 个常用快捷键奉上! 用了这么久 IDEA ,你竟然不知道有个功能叫自动补全! 精选IDEA中十大提高开发效率的插件! ...

程序员书单 1026

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Spring基于注解的方式二

Spring基于注解二 上一次介绍了很多的关于spring的基本的注解,这篇文章描述一下关于Spring注解的基本的原理,从简单的例子入手 @Configuration @Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class}) /** * @Import 导入组件,i...

初学者 2569

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Slf4j 一点小技能

Slf4j 一点小技能 说起来Web开发,分为很多模块,日志模块是其中不可或缺的一部分。日志的收集统计分析这里不讲这些,只是说一点在开发中常用的小技能点。 首先问看官两个问题: 打印日志的时候,‘{}’的作用是什么? 打印日志的时候,‘+’和‘,’的区别是什么? 打印日志的时候,对catch到的异常信息的打印中‘logger.error(“错误信息:”+e.getMessage())’和‘logg...

Reborn_Chang的博客 1913

Java之可变参数??(Object... 数组变量名 ???)

本篇博客的主要内容是围绕着Java的可变参数进行讨论!详细的讨论了可变参数是什么、可变参数的使用方法、可变参数使用时的注意事项,最后讨论了Object... 数组变量名的使用...

岁岁岁平安的博客 1346

Java 语言编码规范

整理: Daniel    2006-10-30   本文: http://www.vuu.cn/article/vuu.asp?item=20936   原文: http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html 目录 1 介绍(Introduction)2 1.1 为什么要有编码规范(Why Have Co

Gerry的专栏 1802

对象数据类型-Object

对象数据类型-Object 认识对象数据类型 - Object ​ 1. 是 js 中的一个数据类型, 是一个复杂数据类型 ​ 2. 是一个 “盒子”, 承载的是 数据 ​ 3. 是一个无序的数据集合, 也是一个键(key)值(value)对的集合 对象数据类型的创建 ​ 1. 字面量方式创建对象 ​ (1) 创建空对象: var obj = {} var obj = {} console.log(obj) ​ (2) 创建带有数据的对象: var obj

weixin_48190576的博客 4132

Spring 细节汇总(1)-AOP 的实现方式

文章目录1. AOP 的实现方式1.1 动态代理1.2 静态代理2. @Value 注入 Map 类型数据 1. AOP 的实现方式 AOP:面向切面编程,采用横向抽取的机制取代了传统的纵向继承体系 1.1 动态代理 Spring 中的 aop 通常是在运行时内存中临时生成代理类,故而又称作运行时增强。运行时增强其实就是动态代理,其底层实现有两种: 需增强的目标类有接口,采用 JDK 中的动态代...

谈谈1974 1255

[J2SE 1.5]逐渐挖掘Varargs

.article {padding:1em;background:white;}.abstract {padding:1em 1ex 1em 2em;}.author, .date {padding:0.5ex 0 0 0;}h1.title, h2.major, h3.minor, h3.listing { margin:1ex 2px 1ex;color:#0066CC

孙海涛的谈笑间 1万+

JsonObject、JsonArray操作json的个人总结

介绍 JsonObject、JsonArray之前,先介绍下JsonConfig JsonConfig: setClassMap(Map classMap)设置json属性类型,上json里的其中值为List时,可通过此属性设置属性类型 Map jMap = new HashMap(); jMap.put("contactPersonList",ContactPerson.cl...

weixin_30686845的博客 380

日志?聊一聊slf4j吧

作为一个Java程序员,肯定对于日志记录不会陌生,无论项目大小,日志记录都是必须的;因为好的日志可以很容易的帮助我们定位一些生产问题。 我怀念的是 无话不说 System.out.println("这里是重要的日志"); 我怀念的是 一起作梦 System.err.println("这里是错误的日志"); 对于日常开发来说,其实System.out.println挺好用的,...

weixin_33804582的博客 577
上一篇: 走进JavaWeb技术世界8:浅析Tomcat9请求处理流程与启动部署过程
下一篇: 走进JavaWeb技术世界10:从JavaBean讲到Spring
wrr-cat
博客等级 码龄9年 128粉丝 302原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值