学会这些Logback高级知识点,程序日志性能提高几十倍(上)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

  • 五 Appenders

前言

=============================================================

在我们日常开发中,毫无疑问,我们需要花费精力去考虑如何将日志打印请求合理的嵌入到我们的程序中。有数据表明,大约有百分之四的代码是用于日志记录。即使是中等大小的应用程序也会嵌入上千行日志记录语句。而且,日志是我们排查 bug ,调试程序的重要依据,所以我们需要学习,并且使用工具来管理这些日志语句。

一 Logback 说明

=======================================================================

Logback 目的是作为流行的 log4j 日志框架的继承者。它是由 log4j 的创始人 Ceki Gülcü 设计的。logback 比所有现有的 logging 系统更快,并且占用的内存空间更小。而且,logback 提供了其他日志记录系统所缺少的独特且相当有用的功能。

Logback 的架构可以分为三个模块:logback-core,logback-classic 和 logback-access。logback-core 模块是其他2个模块的基础。logback-classic 扩展了 logback-core,它是对应于 log4j 的显着改良版本。logback-access 模块与 Servlet 容器集成在一起,以提供 HTTP 访问日志功能。我们一般使用 logback-core 和 logback-classic。

使用 Logback-classic 时,除了引入 logback-classic.jar 外,还需要 slf4j-api.jar 和 logback-core.jar。不过如果我们使用 Maven 等构建工具,只需要引入 logback-classic 依赖,会自动引入其他2个依赖。

先简单演示下效果,首先导入依赖 logback-classic。

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

4.0.0

com.nobody

logback-demo

1.0-SNAPSHOT

ch.qos.logback

logback-classic

1.2.3

package com.nobody;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

  • @Description

  • @Author Mr.nobody

  • @Date 2021/2/20

  • @Version 1.0

*/

public class Main {

public static void main(String[] args) {

// 定义一个 Logger 记录器对象,名字为Main类的全限定名,即 com.nobody.Main

Logger logger = LoggerFactory.getLogger(Main.class);

// 在 info 级别上输出日志 Hello Logback!

logger.info(“Hello Logback!”);

}

}

运行程序后,在控制台打印出了如下日志。根据 logback 的默认配置策略,当未找到默认日志配置文件时,logback 会添加一个 ConsoleAppender(控制台输出器) 关联到根 logger 记录器中。

22:46:18.430 [main] INFO com.nobody.Main - Hello Logback!

请注意,以上示例未明显引用任何 logback 类,是不是很奇妙。其实在大多数情况下,就日志记录而言,需要打印日志的类中仅需要导入 SLF4J 的类即可。通过 LoggerFactory 获取 Logger 日志记录器对象。因为 SLF4J 运用了门面设计模式,屏蔽了底层具体的日志实现框架。因为 logback 完整实现了SLF4J API ,所以我们可以很方便地更换成其它日志系统如 log4j 或 JDK14 Logging,而不用修改代码。

Logback 可以通过使用内置状态系统打印有关其内部状态的信息。我们可以通过一个叫 StatusManager 的组件来访问在 logback 生命周期内发生的重要事件。我们通过调用 StatusPrinter 类的静态方法 print() 来指示 logback 打印其内部状态信息 。

package com.nobody;

import ch.qos.logback.classic.LoggerContext;

import ch.qos.logback.core.util.StatusPrinter;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class Main {

public static void main(String[] args) {

Logger logger = LoggerFactory.getLogger(Main.class);

logger.info(“Hello Logback!”);

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();

StatusPrinter.print(loggerContext);

}

}

运行程序,控制台输出如下日志。意思是说没有找到 logback-test.xml 和 logback.xml 配置文件,所以使用默认策略配置了 ConsoleAppender。一个 Appender 可以看成是一个日志输出目的地的类。它有很多种输出目的地,包括控制台,文件,Syslog,TCP Sockets,JMS等等。当然,我们也可以根据自己的具体情况轻松创建自己的Appender。

请注意,如果出现错误,logback 将会自动在控制台上打印其内部状态信息。

22:59:57.169 [main] INFO com.nobody.Main - Hello Logback!

22:59:57,020 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]

22:59:57,021 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]

22:59:57,021 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]

22:59:57,036 |-INFO in ch.qos.logback.classic.BasicConfigurator@439f5b3d - Setting up default configuration.

我们可以通过以下步骤来往我们的项目中添加日志系统:

  1. 通过 Maven 或 Gradle 添加日志框架依赖

  2. 配置日志配置文件 logback.xml

  3. 在需要打印日志的类中,通过 LoggerFactory 获取 Logger 对象,然后调用它的 debug(),info(),warn() 和 error() 等方法。

二 Logger,Appenders 和 Layouts

=======================================================================================

Logback 依赖于三个主要类:Logger,Appender 和 Layout。配合这三种类型的组件,能让开发人员能够根据日志消息类型和级别记录日志,并在运行时控制日志格式。

Logger 类是 logback-classic 模块的一部分。而 Appender 和 Layout 接口是 logback-core 模块的一部分。作为通用模块,logback-core 没有 Logger 记录器的概念。

2.1 Logger 说明


在 logback-classic 中,Logger 是有继承关系的。每个单独的 logger 都会关联到一个 LoggerContext,LoggerContext 负责制造 logger, 并将它们按树状结构排列。

logger 记录器是带有名称的 Logger 对象,它们的名称区分大小写,并且遵循层级命名规则。

Logger 名称层次规则

记录器的名称层级规则跟 “.” 有关,它们有父亲或者祖先的关系。

例如命名为 com.nobody 的 logger 是命名为 com.nobody.User 的 logger 的父亲;同理,java 是 java.util 的父亲,但是是 java.util.List 的祖先。

root logger 位于 Logger 层次结构的顶部。我们可以按其名称获取到它,如下所示:

public class Main {

public static void main(String[] args) {

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

rootLogger.info(“rootLogger:{}”, rootLogger.getName()); // 输出 rootLogger:ROOT

}

}

我们一般用如下方式,将类的全限定名称作为 logger 名称,获取一个 Logger 对象,用于类中打印日志:

// Logger logger = LoggerFactory.getLogger(“com.nobody.Main”); 等价

Logger logger = LoggerFactory.getLogger(Main.class);

logger.info(“Hello Logback!”);

通过 LoggerFactory.getLogger 获取相同名字的 logger 记录器,都是返回同一对象。例如以下返回的三个对象都是同一个对象。

Logger logger1 = LoggerFactory.getLogger(Main.class);

Logger logger2 = LoggerFactory.getLogger(“com.nobody.Main”);

Logger logger3 = LoggerFactory.getLogger(“com.nobody.Main”);

2.1.1 有效级别

有效级别也称为日志级别继承规则。我们可以为 logger 分配级别,级别种类有 TRACE,DEBUG,INFO,WARN 和 ERROR,它们在 ch.qos.logback.classic.Level 中定义。在 logback 中,Level 类是 final 的,不能被继承的。

级别按以下顺序排序: TRACE < DEBUG < INFO < WARN < ERROR。

如果没有为给定的 logger 记录器分配一个级别,那么它将从其最接近的祖先那里继承一个已分配的级别。严格上讲,比如一个 logger 的有效级别等于其层次结构中的第一个非空级别,它从其本身开始,在层次结构中向上扩展直到 root logger。

为了确保所有 logger 记录器最终都可以继承到级别,root logger 始终具有分配的级别。默认情况下,此级别是 DEBUG。

| Logger name | Assigned level | Effective level |

| — | — | — |

| root | DEBUG | DEBUG |

| X | none | DEBUG |

| X.Y | none | DEBUG |

| X.Y.Z | none | DEBUG |

| Logger name | Assigned level | Effective level |

| — | — | — |

| root | ERROR | ERROR |

| X | INFO | INFO |

| X.Y | DEBUG | DEBUG |

| X.Y.Z | WARN | WARN |

| Logger name | Assigned level | Effective level |

| — | — | — |

| root | DEBUG | DEBUG |

| X | INFO | INFO |

| X.Y | none | INFO |

| X.Y.Z | ERROR | ERROR |

| Logger name | Assigned level | Effective level |

| — | — | — |

| root | DEBUG | DEBUG |

| X | INFO | INFO |

| X.Y | none | INFO |

| X.Y.Z | none | INFO |

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

以下演示 logger 级别的继承关系,日志级别关系决定是否能打印:

package com.nobody;

import ch.qos.logback.classic.Level;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

  • @Description

  • @Author Mr.nobody

  • @Date 2021/2/21

  • @Version 1.0

*/

public class LoggerLevelTest {

public static void main(String[] args) {

// 获取一个名为 “com.foo” 的 logger 对象,并且转换为 ch.qos.logback.classic.Logger logger,

// 这样我们能为它设置级别

ch.qos.logback.classic.Logger logger =

(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(“com.foo”);

logger.setLevel(Level.INFO);

// 获取一个名为 “com.boo.Bar” 的 logger 对象,没有设置级别,根据继承关系,继承"com.foo"的 logger 的级别 INFO

Logger barLogger = LoggerFactory.getLogger(“com.foo.Bar”);

// 可以执行,因为 WARN >= INFO

logger.warn(“Low fuel level.”);

// 不能执行,因为 DEBUG < INFO.

logger.debug(“Starting search for nearest gas station.”);

// 根据级别继承关系,可以执行,因为 INFO >= INFO.

barLogger.info(“Located nearest gas station.”);

// 根据级别继承关系,不能执行,因为 DEBUG < INFO.

barLogger.debug(“Exiting gas station search”);

}

}

运行程序,控制台输出的日志信息如下:

14:37:58.354 [main] WARN com.foo - Low fuel level.

14:37:58.368 [main] INFO com.foo.Bar - Located nearest gas station.

2.2 Appenders 说明


Logback 允许日志记录请求打印到多个目标目的地。在 logback 中,输出目标称为 appender。目前,存在的 appender 有,控制台,文件,远程 socket 服务,MySQL,PostgreSQL,Oracle 和其他数据库,JM S和 远程 UNIX Syslog 守护程序等等。

一个 logger 可以与多个 appender 绑定。

一个可以执行的日志打印请求,会将日志输出到当前 logger 关联的 appender,并且会根据层级关系输出到所有上层级 logger 所关联的 appender 中。我们可以通过将 logger 的可叠加性标志(additivity flag)设置为 false,覆盖此默认行为,这样不会将日志输出到更高层级 logger 的 appender 中。

假如有个 logger X.Y.Z,默认会将日志输出到 X.Y.Z,X.Y,X 这三个 logger 所关联的 appender 中。如果将 X.Y 这个 logger 的 additivity flag 设置为 false,则 X.Y.Z logger 打印的日志只会输出到 X.Y.Z 和 X.Y。如果将 X.Y.Z logger 的 additivity flag 设置为 false,则 X.Y.Z logger 打印的日志只会输出到 X.Y.Z 。

| Logger Name | Attached Appenders | Additivity Flag | Output Targets | Comment |

| — | — | — | — | — |

| root | A1 | 不适用 | A1 | 由于 root logger 位于层次结构的顶部,因此可加性标志不适用于它。 |

| X | A2,A3 | true | A1,A2,A3 | root 和 X logger 总的 appender |

| X.Y | none | true | A1,A2,A3 | root 和 X logger 总的 appender |

| X.Y.Z | A4 | true | A1,A2,A3,A4 | root 和 X 和 X.Y.Z logger 总的 appender |

| K | B1 | false | B1 | 因为 K 的 additivity flag 设置为 false,所以 appender 只有它自己关联的。 |

| K.J | none | true | B1 | 因为 K 的 additivity flag 设置为 false,所以层级往上查找只到K,不能到 root。 |

2.3 Layouts 说明


通常,我们不仅希望自定义日志输出目的地,还希望自定义日志输出格式。这可以通过将 layout 和 appender 相关联来实现。layout 负责根据用户的需求格式化日志记录请求,而 appender 负责将格式化后的日志输出发送到目的地。

例如,如果 patternLayout 配置为 “%-4relative [%thread] %-5level %logger{32} - %msg%n”,将输出类似以下格式日志信息:

176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.

第一个字段是自程序启动以来经过的毫秒数。第二个字段是发出日志请求的线程名称。第三个字段是日志请求的级别。第四个字段是与日志请求关联的 logger 的名称。“-” 之后的文本是日志信息。

参数化的日志

因为 logback-classic 的 Logger 实现了 SLF4J 的 Logger 接口,所以有些日志打印方法允许使用多个参数。这些带有多个参数的打印方法能提高性能,同时最大程度提高代码可读性。

例如,以下写法,为了构造 debug 方法的 msg 参数,会将整数 i 和 entry[i] 字符串都转换为字符串,并连接中间字符串,从而产生开销。不管最终此行代码是否会打印。

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

为避免参数构造成本的一种方法,是在打印日志前,判断此 logger 是否开启此级别。

if(logger.isDebugEnabled()) {

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

}

这样,如果我们禁用了 debug 级别,则不会产生 msg 参数构造的开销。但是,如果 logger 开启了 debug 级别,则将产生两次开销,一次是判断 debugEnabled,一次是 debug 打印。实际上,debugEnabled 这种开销微不足道,因为它是实际日志打印请求所花费时间的不到1%。

不过有更好的选择,基于消息格式的方法,例如如下所示:

Object entry = new SomeObject();

logger.debug(“The entry is {}.”, entry);

只有在 debug 打印语句是开启的情况下,logger 才会格式化消息,并用 entry 代替 {}。也就是说,当禁用 debug 级别时,是不会产生 msg 参数构造的开销的。

以下两行将产生完全相同的输出。但是,在禁用日志记录语句的情况下,第二个将比第一个好至少30倍。

logger.debug(“The new entry is “+entry+”.”);

logger.debug(“The new entry is {}.”, entry);

如果需要传递三个或更多参数,您可以这样写:

Object[] paramArray = {newVal, below, above};

logger.debug(“Value {} was inserted between {} and {}.”, paramArray);

三 Logback 日志打印步骤

===========================================================================

当用户调用 logger 的日志打印方法时,logback 框架所采取的步骤是怎么样的呢?现在我们分析当用户调用名为 “com.nobody.UserService” 的 logger 的 info() 方法时,logback 采取的步骤 :

  1. 获得过滤器链决策

如果存在,那么 TurboFilter 链会被调用。Turbo filters 可以设置一个上下文范围阈值,或过滤掉某些基于信息例如 Marker, Level,Logger,日志消息的事件,或与每个日志记录请求相关联的 Throwable。如果过滤器链的答复是 FilterReply.DENY,那么日志记录请求会被抛弃。如果是 FilterReply.NEUTRAL,则继续进行下一步,即步骤2。如果答复为 FilterReply.ACCEPT,则跳过下一步,直接跳至步骤3。

  1. logger 级别比较规则

在此步骤中,logback 将 logger 的有效级别与打印请求级别进行比较。如果根据级别规则禁用了日志记录请求,则 logback 将丢弃该请求,(请求打印日志的级别小于 logger 设定的级别则抛弃请求)而无需进一步处理。否则,将继续进行下一步。

  1. 创建一个 LoggingEvent 对象

如果请求在先前的过滤器中存活下来了,则 logback 将创建一个 ch.qos.logback.classic.LoggingEvent 对象,其中包含请求的所有相关参数,例如请求的 logger,请求级别,日志消息,可能与请求一起传递的异常,当前时间,当前线程,跟发出日志记录请求相关的类的各种数据以及MDC。其中某些字段仅在实际需要时才延迟初始化。MDC用额外的上下文信息(例如请求唯一ID)来装饰日志记录请求。

  1. 调用 appenders

创建 LoggingEvent 对象后,logback 将调用所有能用的 appenders 的 doAppend() 方法。

  1. 格式化输出

被调用的 appender 负责格式化日志记录事件。但是,一些(但不是全部) appender 将格式化日志记录事件的任务委托给 layout。layout 会格式化 LoggingEvent 实例,并以字符串形式返回结果。注意,某些 appender,例如 SocketAppender 不会将日志记录事件转换为字符串,而是将其序列化。因此,它们没有 layout,也不需要 layout。

  1. 发出 LoggingEvent

日志记录事件被完全格式化后,每个 appender 会将其发送到其目的地。

下面是一个显示了全部工作原理的 UML 序列图。

在这里插入图片描述

四 logback.xl 配置

==========================================================================

通过编程,或使用以 XML 或 Groovy 格式表示的配置脚本都可以达到配置 Logback 的效果。

logback 会按以下步骤来配置自己:

  1. Logback 尝试在 classpath 中找一个名为 logback-test.xml 的文件 。

  2. 如果找不到此类文件,则 logback 尝试在 classpath 中找一个名为 logback.groovy 的文件 。

  3. 如果找不到这样的文件,它将在 classpath 中找一个名为 logback.xml 的文件。

  4. 如果还没有找到这样的文件, ServiceLoader(在JDK 1.6中引入)会通过 META-INF\services\ch.qos.logback.classic.spi.Configurator 加载 com.qos.logback.classic.spi.Configurator 接口的实现类。

  5. 如果以上方法均未成功,则 logback 将使用 BasicConfigurator 进行自动配置,这会将日志输出定向到控制台。

以下我们演示没有配置文件,logback 使用默认配置的效果。

package com.nobody;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class MyApp1 {

private static final Logger LOGGER = LoggerFactory.getLogger(MyApp1.class);

public static void main(String[] args) {

LOGGER.info(“我是在 MyApp1 类中,使用info级别打印日志”);

User user = new User();

user.say();

}

}

package com.nobody;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class User {

private static final Logger LOGGER = LoggerFactory.getLogger(User.class);

public void say() {

LOGGER.debug(“我是在 User 类中,使用debug级别打印日志”);

}

}

运行程序,控制台输出日志如下:

21:20:34.035 [main] INFO com.nobody.MyApp1 - 我是在 MyApp1 类中,使用info级别打印日志

21:20:34.041 [main] DEBUG com.nobody.User - 我是在 User 类中,使用debug级别打印日志

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

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

在这里插入图片描述

下面我们演示使用 logback.xml 进行配置,首先我们在类路径中创建 logback.xml 文件,此为 logback-demo/src/main/resources/logback.xml ,填入以下内容:

%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

再运行上面的程序,控制台会输出如下信息:

21:45:40.941 [main] INFO com.nobody.MyApp1 - 我是在 MyApp1 类中,使用info级别打印日志

21:45:40.954 [main] DEBUG com.nobody.User - 我是在 User 类中,使用debug级别打印日志

如果程序在解析配置文件期间发生警告或错误,则 logback 会自动在控制台上打印其内部状态信息。如果在没有警告或错误时,你也希望检查 logback 的内部状态,你可以指示通过调用 StatusPrinter 类的 print() 方法。如下所示:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

StatusPrinter.print(lc);

如果你不想在程序中编写打印 logback 的内部状态,那可以在配置文件 configuration 元素的 debug 属性设置为 true,同样也可以在程序启动时打印 logback 内部状态。当然,前提是找到配置文件或者配置文件是格式正确的XML才会输出内部状态。

%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

当然,我们也可以通过配置系统属性(logback.configurationFile)的方式,指定 logback 配置文件的位置。属性的值可以是URL,类路径上的资源或应用程序外部文件的路径。

java -Dlogback.configurationFile=/path/to/logback.xml com.nobody.MyApp1

4.1 自动重新加载配置文件


如果开启了自动重新加载配置文件,logback-classic 会扫描配置文件中的更改,并在配置文件更改时自动重新配置自身。在 标签中将 scan 属性设置为 true 即可开启。

当将 scan 属性设置为 true 时,在后台 ReconfigureOnChangeTask 会在单独的线程中运行,它会检查配置文件是否已更改。

由于在编辑配置文件时很容易出错,因此如果最新版本的配置文件具有 XML 语法错误,则它将回退到先前没有 XML 语法错误的配置文件。

默认情况下,每1分钟扫描一次配置文件是否有更改。我们可以设置 标签中的 scanPeriod 属性来指定扫描周期。单位可以为毫秒,秒,分钟或小时。如果未指定时间单位,则时间单位默认为毫秒

%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

4.2 在堆栈跟踪中启用包数据


注意从版本1.1.4开始,包装数据默认为禁用。可按如下配置开启包数据:

当然,也可以在程序中进行配置,如下:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

lc.setPackagingDataEnabled(true);

如果开启了,logback 会在输出的堆栈行中显示它是属于哪个 jar 或者哪个类的。此信息由 jar 文件的名称和版本组成,表明堆栈信息来源于此。此机制对于识别软件版本问题非常有用。但是,计算成本相当昂贵,尤其是在经常引发异常的应用程序中。以下演示开启的结果,即多了 [] 括号内的信息。

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]

4.3 停止 logback-classic


为了释放 logback-classic 资源,停止 logback context 是一个好主意。如果停止,会关闭所有 loggers 关联的 appenders,并有序的停止所有活动线程。

import org.sflf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();

loggerContext.stop();

4.4 配置文件语法


4.4.1 元素

一个 logger 记录器可以使用 元素配置。 元素中,name 属性是必须的,level 级别属性是可选的,additivity 可叠加性属性也是可以选的(它的值是 true 或 false)。级别 level 属性的值是不区分大小写的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF。还有不区分大小写的值 INHERITED 或其同义词 NULL,代表将强制从层次结构中较高的层次继承记录器继承级别。

元素里面可包含0或多个 元素,这样引用的 appender 会关联到此 logger。不同 log4j,即便你在配置文件配置了 logger 关联的 appender,logback-classic 也不会关闭或者移除之前关联的 appender。

4.4.2 配置 root logger, 元素

元素用来配置 root logger。它支持单个属性,即 level 级别属性。它没有其他属性,因为可叠加性标志不适用于根记录器。此外,由于根记录器已被命名为 “ ROOT” ,因此它也不允许使用 name 属性。level 属性的值可以是不区分大小写的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。但是,根记录器的级别不能设置为 INHERITED 或 NULL。

与 元素类似, 元素也可以包含零个或多个 元素。如此引用的每个附加程序都会添加到根记录器中。不同 log4j,即便你在配置文件配置了 root logger 关联的 appender,logback-classic 也不会关闭或者移除之前关联的 appender。

下面演示个 demo,假设我们不想打印 “com.nobody.entity” 包下任何组件的任何 DEBUG 消息。可以按如下配置:

%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

我们可以根据需要配置任意数量的记录器。如下,我们将 com.nobody.A 记录器的级别设置 为 INFO,但同时将 com.nobody.B 记录器的级别设置为DEBUG。

4.4.3 配置 Appenders

一个 appender 使用 元素配置,该元素具有两个必填属性 name 和 class。name 属性指定 appender 的名称,class 属性指定实例化此 appender 的类。 元素可包含零个或一个 元素,零个或多个 元素,零个或多个层 元素。除了这三个公共元素之外, 可以包含任意数量的与 appender 类的JavaBean属性相对应的元素。

在这里插入图片描述

有个必填的属性指定实例化此对象的全限定类名。和 一样,它也有自己的相关属性。PatternLayout 有默认的属性值,所以可以不指定属性值。

有个必填的属性指定实例化此对象的全限定类名。PatternLayoutEncoder 有默认的属性值,所以可以不指定属性值。

1200页Java架构面试专题及答案

小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞

百度、字节、美团等大厂常见面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ion>

4.4.3 配置 Appenders

一个 appender 使用 元素配置,该元素具有两个必填属性 name 和 class。name 属性指定 appender 的名称,class 属性指定实例化此 appender 的类。 元素可包含零个或一个 元素,零个或多个 元素,零个或多个层 元素。除了这三个公共元素之外, 可以包含任意数量的与 appender 类的JavaBean属性相对应的元素。

在这里插入图片描述

有个必填的属性指定实例化此对象的全限定类名。和 一样,它也有自己的相关属性。PatternLayout 有默认的属性值,所以可以不指定属性值。

有个必填的属性指定实例化此对象的全限定类名。PatternLayoutEncoder 有默认的属性值,所以可以不指定属性值。

1200页Java架构面试专题及答案

小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞

[外链图片转存中…(img-ZGoZPEm5-1713465600419)]

[外链图片转存中…(img-kEqFaE8p-1713465600420)]

百度、字节、美团等大厂常见面试题

[外链图片转存中…(img-nRGat1pC-1713465600421)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-M1F5V4D2-1713465600421)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值