精华内容
下载资源
问答
  • 考虑下图的日志记录
    千次阅读
    2021-01-13 02:57:10

    数据库作业20第十章数据库恢复技术 课后习题

    数据库作业20:第十章:数据库恢复技术 课后习题

    4、考虑下图所示的日志记录:

    (1)如果系统故障发生在14之后,说明哪些事物需要重做,哪些事物需要回滚。

    T1、T3提交,T2回滚,T4开始了但没有结束。

    重做(REDO):T1,T3;

    撤销(UNDO):T4。

    (2)如果系统故障发生在10之后,说明哪些事物需要重做,哪些事物需要回滚。

    T1提交。T2回滚,T3开始但没结束,T4未开始。

    重做:T1;

    撤销:T3。

    (3)如果系统故障发生在9之后,说明哪些事物需要重做,哪些事物需要回滚。

    T1提交,T2、T3开始但未结束,T4未开始。

    重做:T1;

    撤销:T2,T3.

    (4)如果系统故障发生在7之后,说明哪些事物需要重做,哪些事物需要回滚。

    T1提交,T2开始但未结束,T3,T4未开始。

    重做:T1

    撤销:T2

    5、考虑题4所示的日志记录,假设开始时A,B,C的值都是0:

    (1)如果系统故障发生在14之后,写出系统恢复后A,B,C的值;

    T1,T3重做,T4撤销,

    A=8,B=7,C=11。

    (2)如果系统故障发生在12之后,写出系统恢复后A,B,C的值;

    T1提交,T2回滚,T3,T4开始但未结束,所以T1重做,T3,T4撤销。

    A=10,B=0,C=11。

    (3)如果系统故障发生在10之后,写出系统恢复后A,B,C的值;

    重做:T1;撤销:T3。

    A=10,B=0,C=11

    (4)如果系统故障发生在9之后,写出系统恢复后A,B,C的值;

    T1提交。T2、T3开始但没结束,T4未开始。

    T1重做,T2,T3撤销。

    A=10,B=0,C=11

    (5)如果系统故障发生在7之后,写出系统恢复后A,B,C的值;

    T1重做,T2撤销

    A=10,B=0,C=11

    (6)如果系统故障发生在5之后,写出系统恢复后A,B,C的值;

    T1,T2开始但未结束,T3、T4未开始。

    T1、T2撤销。

    A=0,B=0,C=0

    在系统发生故障之前,提交了的事物需要重做,开始了但没有结束的需要撤销,已经回滚的不做操作,相当于没有进行。

    系统恢复后,回滚和撤销的事物相当于没有执行,只需要考虑重做的事物就行。

    结论:这次作业比较简单,掌握结论就可以做题。

    数据库作业20第十章数据库恢复技术 课后习题相关教程

    更多相关内容
  • 如何在 Java 中进行日志记录

    千次阅读 2021-10-27 14:02:26
    您可以使用本指南为您的应用程序发现、理解...迟早,每个 Java 应用程序都需要日志记录。 可能您只是想将系统状态或用户操作记录到文件中,以便您的操作人员了解正在发生的事情。 <span style="color:#21252

    您可以使用本指南为您的应用程序发现、理解和使用正确的 Java 日志库,例如 Log4j2、Logback 或 java.util.logging。

    日志“似乎”是一个非常简单的主题,但在实践中可能相当棘手,并且没有在任何地方进行足够详细的介绍。阅读本指南以充分了解 Java 日志环境。

    介绍

    迟早,每个 Java 应用程序都需要日志记录。

    可能您只是想将系统状态或用户操作记录到文件中,以便您的操作人员了解正在发生的事情。

    logger.info("Application successfully started on port 8080");
    

    可能需要在发生异常时记录错误消息,然后向人员发送电子邮件或文本消息以进行紧急干预。

    logger.error("Database connection is down", exception);
    

    或者,您的批处理作业之一可能希望在无法导入 csv 文件的某些记录时记录并向基于 GUI 的中央日志服务器发送警告。

    logger.warn("Invalid bank account number for record=[{}]", 53);
    

    不管你想做什么,你都需要确保有一个合适的日志库,然后正确配置和使用它。

    不幸的是,Java 世界有大量可用的日志库(请参阅此视频以了解 Java 日志地狱的概述),开发人员应该大致了解为什么有这么多选项以及何时使用哪个选项。

    让我们来了解一下。

    遗留日志库

    要了解 Java 日志库的最新发展,有必要认识并了解恐龙 - Java 最古老的日志库,您今天仍然可以在某些生产环境中找到它们。

    java.util.logging (JUL)

    从 Java 1.4 (2002) 开始,JDK 捆绑了自己的日志记录“框架”,称为 java.util.logging,通常缩写为 JUL。以下是如何使用 JUL 记录事物的示例:

    // java.util.logging
    java.util.logging.Logger logger =  java.util.logging.Logger.getLogger(this.getClass().getName());
    logger.info("This is an info message");
    logger.severe("This is an error message"); // == ERROR
    logger.fine("Here is a debug message"); // == DEBUG

    与每个日志库的情况一样,首先您可以获得特定类或包的 Logger,然后您可以记录语句。您可能认为日志级别 'severe' 和 'fine' 看起来很奇怪,但它们基本上对应于所有现代 Java 日志库的 'error' 和 'debug' 级别。

    当然,您可以使用所谓的处理程序配置记录器,例如 FileHandler(将日志语句写入文件)或 ConsoleHandler(写入 System.err)。

    FileHandler fileHandler = new FileHandler("status.log");
    logger.addHandler(fileHandler);

    所以,你可能会问,如果有 JUL,为什么还有人需要另一个日志框架?

    虽然 JUL 完成了这项工作,但过去一直在讨论它的缺点,包括其不一致的 API、性能缓慢、缺乏(复杂的)配置选项、文档等——最终导致人们开发和使用其他日志框架。

    Log4j (v1)

    很长一段时间以来,Java 领域最流行的日志选择是Log4j(版本 1),它最初于 2001 年发布并一直维护到 2015 年。 事实上,您仍然会发现它在相当多的企业项目中使用,在 2018 年。

    使用 Log4j,上面的日志示例如下所示:

    // Log4j V1
    org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(MyClass.getClass().getName());
    logger.info("This is an info message");
    logger.error("This is an error message");
    logger.debug("Here is a debug message");

    Log4j 不仅具有合理的日志级别名称,例如“错误”和“调试”。它还带有大量不同且聪明的 appender,如 SMTPAppender(发送电子邮件日志事件)、SyslogAppenders(发送事件到远程系统日志守护进程)、JdbcAppenders(发送它们到数据库)等等

    PatternLayouts的帮助下,它还可以让您对日志消息的确切外观进行相当多的控制。因此,Java 应用程序中的相同日志事件可以像这样打印在日志文件中,具体取决于布局:

    # contents of status.log
    
    [INFO] 2012-11-02 21:57:53,662 MyLoggingClass - Application succesfully started on port 8080
    
    # or
    
    2010.03.23-mainThread --INFO -MyLoggingClass:Application succesfully started on port 8080
    
    # or other endless possibilities

    Log4j 做的很好,但在过去几年被 Log4j2 取代,它与 Log4j1 不完全兼容,因此我们将在下一节中讨论它。

    Apache 公共日志记录 (JCL)

    大约在同一时间,在 2002 年,另一个名为JCL 的库出现了,它有两个名字。Jakarta Commons Logging 或 Apache Commons Logging。

    JCL 的有趣之处在于,它本身并不是一个日志框架实现。相反,它是其他日志记录实现的接口。这意味着什么?

    正如您可能已经猜到的那样,日志记录代码本身仍然相当简单,只是您现在引用 JCL 类而不是引用 JUL 或 Log4j 类 - 前提是您的类路径中有 JCL 库。

    // Apache Commons Logging
    org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(MyApp.class);
    log.info("This is an info message");
    log.error("This is an error message");
    log.debug("Here is a debug message");

    因此,您的代码仅使用 JCL 特定的类。但是实际的日志记录是由另一个日志记录框架完成的,无论是 Log4j、JUL 还是(已不复存在的)Apache Avalon都无关紧要。

    这意味着您需要在类路径上使用另一个库,例如 Log4j,并将这两个库配置为协同工作。

    为什么会有人想要这样做?

    为什么不想直接使用 Log4j 呢?有一个主要用例,即编写库:

    在编写库时,您根本不知道库的用户想要在他自己的应用程序中使用哪个日志记录框架。因此,编写您的库以使用日志记录接口是有意义的 - 然后用户可以在部署自己的应用程序时插入他或她想要的任何日志记录实现。

    问题在哪里?

    JCL 的问题在于,它依赖于类加载器技巧来找出它应该在运行时使用哪个日志记录实现。这会导致很多痛苦。此外,您会发现 API 有点不灵活,它带有大量的杂物,而且现在有更好的替代品。

    现代日志库

    SLF4J & Logback

    在某个时候,Log4j 的原始创建者 Ceki Gülcü 决定从 Log4j 项目中分离出来并创建一个继承者,它不叫 Log4j2,而是Logback。您可以在此处阅读他尝试使用 Logback 改进的内容。

    可以说,Logback 是一个成熟而可靠的日志库,具有大量功能,其中开发人员似乎记得最多的功能是在生产中自动重新加载配置文件。

    同时,他还开始为 Java 编写 Simple Logging Facade,也称为SLF4J,它与上面的 Apache Commons Logging 'bridging' 库非常相似,只是有更好的实现。让我们看看这意味着什么:

    要开始使用 SLF4J,您只需要在类路径上有一个库,即 slf4j-api 依赖项(请参阅下一节的截屏视频)。如果您使用像Maven这样的依赖管理工具,那么您需要将以下依赖添加到您的依赖项部分:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>

    在类路径上使用 API 将允许您编写如下日志语句:

    // SLF4J
    org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MyClass.class);
    logger.info("This is an info message");
    logger.error("This is an error message");
    logger.debug("Here is a debug message"); //  you do not need 'logger.isDebugEnabled' checks anymore. SLF4J will handle that for you).

    就像 JCL 一样,SLF4J 不能自己做日志记录。它需要一个日志库来进行实际的日志记录,例如 Log4j、JUL、Logback 等。因此,假设您想使用 Log4j v1,那么您将需要在类路径中使用 slf4j-log4j12 绑定库:

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

    该依赖项将为您传递 Log4j (v1),并确保 SLF4J 记录“通过”Log4j。如果您对其工作原理感兴趣,请阅读SLF4J 手册中有关绑定的部分。

    其他的库,比如 Logback,不需要绑定库,因为它们原生实现了 SLF4J,所以你可以简单地使用 slf4j-api 依赖,也可以放入 logback-classic jar 中,你可以通过 Logback 登录。

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

    这种方法的美妙之处在于,您的代码只知道 SLF4J。没有对 Log4j、Logback 或 Jul 的引用。如果您正在编写库,那就更好了。因为如果您的库使用 SLF4J,那么您的库的最终用户可以决定使用 Log4j、Logback 或他想要的任何库进行日志记录。因为可以通过在类路径中添加或删除几个 jar 来简单地做出选择。

    等等,我们是不是错过了什么?

    当您使用 3rd 方库时,事情变得很有趣,这些库被硬编码为使用特定的日志库。假设您正在使用一个 PDF 生成器库,该库被硬编码为使用 Log4j。您还使用了一个使用 JUL 的电子邮件发送库。您自己的应用程序使用 SLF4J,但您不能仅仅更改这些库的源代码以也使用 SLF4J。

    现在做什么?

    值得庆幸的是,SLF4J 的创建者也考虑了这个用例(请参阅此处的截屏视频)。让我们先看看 Maven 依赖项,看看它是什么样子:

    每当您引入使用 Log4j 的 3rd 方库时,显然它都会引入 Log4j 依赖项。Log4j 依赖项如下所示:

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    然后,您需要确保从您的项目中排除该依赖项,并使用以下直接替换:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.30</version>
    </dependency>

    诀窍是:在 log4j-over-slf4j.jar 中,您会发现像 org.apache.log4j.Logger 这样的类,但它们与 Log4j 无关!相反,这些是 SLF4J 特定的类,即您的代码“认为”它调用 Log4j,但所有内容都被路由到 SLF4J。(对于其他“over-slf4j”库也是如此,JUL 库除外,您可以在此处阅读有关内容)。

    这反过来意味着,作为库的最终用户,您可以使用您想要的任何日志库,即使原始库创建者希望您专门使用 Log4j。

    现实生活

    因此,根据您正在构建的内容和正在使用的第三方库,您的类路径中可能最终会包含以下库:

    • SLF4J API

    • 您的 SLF4J 实现,例如 Logback 或 Log4j 等。

    • 一个或多个桥接库,如 log4j-over-slf4j、jul-to-slf4j、jcl-over-slf4j 等。

    主要外卖

    使用 SLF4J,您可以对 API 进行编码,并且可以在稍后(编译时)选择实现(Log4j、Logback 等)。此外,您可以使用桥接库让传统的 3rd 方库“说”SLF4J。

    虽然所有这些对于初学者来说可能看起来很可怕,但只要有一点经验,这一切都是有道理的。

    日志4j2

    有人可能认为 SLF4J 以及所有周围的日志库,几乎可以满足所有日志需求。好像不是这样。2014 年,发布了 Log4j (v1) 库的继任者,称为Log4j2 - 完全重写,当然受到所有其他现有日志库的极大启发。

    此外,就像 SLF4J、JCL 或 Commons Logging 一样,Log4j2 可以用作桥梁,因为它带有两个依赖项:

    API 依赖项:

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.0</version>
    </dependency>

    以及实际的日志实现:

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.0</version>
    </dependency>

    API 依赖项适用于各种其他日志记录框架,就像 SLF4J 或 JCL 一样。您可以加入 Log4j2 自己的日志记录实现,使用 SLF4J 实现,或使用桥接/适配器库之一以您希望的任何方式设置日志记录。但是,您的代码只会像这样引用 Log4j2 类:

    // Log4j (version 2)
    org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(MyApp.class);
    logger.info("This is an info message");
    logger.error("This is an error message");
    logger.debug("Here is a debug message");

    如果您阅读了前面的部分,您可能会得出结论,SLF4J 和 Log4j2 有很多共同点,但不清楚为什么要使用 Log4j2 而不是只坚持使用 SLF4J。

    Log4j2 的创建者试图在这里自己回答这个问题,主要区别似乎是性能(AsyncLogger、垃圾收集)和稍微好一点的 API(记录对象的能力,而不仅仅是字符串、Lambda 支持等)。

    虽然应该说,虽然这些原因可能会对复杂的高负载应用程序产生影响,但在“普通”应用程序上工作的开发人员可能不会注意到差异。

    日志记录

    如果不提及JBoss-Logging,则谈论日志库是不完整的。它是另一个日志桥,与 SLF4J 或 JCL 非常相似,因此您必须将它与另一个日志实现甚至 SLF4J 本身一起使用。

    与这些其他伐木桥相比,它的主要声望似乎是它的国际化特征。除此之外,似乎没有什么理由将您的项目完全基于 jboss-logging,尽管您会发现像Hibernate这样的项目使用它,因为这两个库都是在RedHat 保护伞下开发的。

    如何登录

    一旦你决定了你最喜欢的日志框架,就该真正使用你的记录器了。这给我们带来了一个问题:应该如何登录?

    一个小的、技术性的挑剔

    如果您查看组织中的不同 Java 项目,或者甚至只查看一个项目,您可能会看到人们尝试获取 Logger 实例的多种方式: 使他们能够首先登录的类 -地方。

    这可以采用以下外观:

    // in class 1
    private Logger LOG = LoggerFactory.getLogger(...);
    
    // in class 2
    private static final Logger LOGGER = ....;
    
    // in class 3
    private static Logger log = Logger.getLogger(...);
    
    // in class 4
    private Logger LOG_INSTANCE = ...;
    
    // etc. etc.

    现在应该是什么样子呢?对此有一个简单的答案。如果您正在创建的类,以及您为创建该类而调用的方法,内部都包含“logger”一词,则调用变量“logger”。

    不要太担心静态或非静态、最终或非最终,只需确保在整个项目中选择同质化即可。

    最后,真的没有必要仅仅为了它而 UPPER_CASE 你的记录器,当然不是你的代码库中的唯一例外。

    日志级别和文件

    一个非常有趣的话题是:您应该实际登录到哪个日志级别?您可以选择 TRACE、DEBUG、INFO、WARN、ERROR、FATAL,而且相当多的开发人员不确定何时使用哪一个。

    这是我在一些地方看到成功使用的一般方法,但请注意,这不是一成不变的(请参阅此处的截屏视频)。在适当的情况下对这些指南进行更改,但请确保您有一个可靠的用例和推理。最重要的是,确保您的开发人员和运营人员在同一页面上。

    现在让我们先分别看看“错误组”日志级别,以及您可能将它们用于什么。

    致命的

    此级别的任何内容都意味着您的 Java 进程无法继续,现在将终止。

    最不有趣的日志级别,因为您不太可能在您的应用程序中使用它,而且诸如 SLF4J 之类的 API甚至不直接支持它

    错误

    请求已中止,根本原因需要尽快进行人工干预。

    警告

    请求未得到令人满意的服务,需要尽快进行干预,但不一定立即进行。

    这在实际中意味着什么?

    要根据 ERROR 和 WARN 评估条目,您可以提出问题“需要采取什么行动”,如果这听起来不像是“天哪!现在就采取行动!” 事件类型,它会因不符合标准而降级到较低级别。

    考虑一下您将闪亮的金融科技(银行)应用程序的新功能推向生产,不幸的是,每当用户尝试显示其银行帐户的最近交易时,就会触发臭名昭著的 Hibernate LazyLoadingException。这听起来像是一个非常强大的 OMG 情况,您会希望将这些错误记录为“错误” - 并触发适当的反应措施。

    2018-09-11 08:48:36.480 ERROR 10512 --- [ost-startStop-1] com.marcobehler.UserService        : Retrieving transaction list for user[id={}] failed
    
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: User.transactionDetails, could not initialize proxy - no Session
            at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:582)
            at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:201)
            at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:561)
            at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:132)
            at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:277)
            at java.lang.Iterable.forEach(Iterable.java:74)
            at LibraryTest.spring_test(LibraryTest.java:78)
        ...

    然后考虑一个批处理作业,它每天或每周导入事务。通常情况下,某些记录可能格式不正确,因此无法导入系统。有人,一个人,需要手动查看这些记录并修复它们。但这可能不像错误情况那样具有时间敏感性和紧迫性,因此您将选择使用 WARN 级别记录这些项目。

    2018-09-11 00:00:36.480 WARN 10512 --- [ost-startStop-1] com.marcobehler.BatchJob        : Could not import record[id=25] from csv file[name=transactions.csv] because of malformed[firstName,lastName]
    

    保持 ERROR 和 WARN 标签干净的主要原因是它使监控和对这些事件的反应变得更加简单。

    或者简单地说:确保在凌晨 3 点唤醒您的操作人员,以发现正确的(某种)错误。

    信息

    Info 是开发人员可能觉得最“舒服”的使用日志级别,在实践中你会发现开发人员打印出大量具有 INFO 级别的语句,从客户端活动(webapps)、进度信息(批处理作业)到相当复杂的,内部流程细节。

    同样,决定什么应该是 INFO 什么应该是 DEBUG 可能是一条模糊的界线,但一般来说,流程详细信息应该与调试级别一起记录,而不是在信息中完全复制用户通过您的应用程序的旅程。日志。

    从历史上看,将几乎所有内容都作为 INFO 注销的主要原因是,很难动态更改应用程序的日志级别,而不必重新启动(退回)所述应用程序。有时,开发人员和运营人员之间的组织孤岛也太大,无法轻松快速地更改日志级别。因此,开发人员选择安全起见,向控制台打印“更多”而不是更少,以便能够通过系统跟踪整个调用。

    足够的介绍。让我们看一些例子。

    显然,您可以使用 INFO 级别来注销应用程序状态,如下所示:

    2018-09-11 08:46:26.547  INFO 8844 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    

    但是另一种考虑 INFO 级别的有趣方式是作为一个额外的(弱)错误案例:请求没有得到令人满意的服务,但解析细节已传递给请求者,不需要主动支持。

    示例信息是“用户登录失败,用户名或密码不正确”。

    2018-09-11 08:46:26.547  INFO 8844 --- [           main] com.marcobehler.UserService  : User with id[=45] tried to login with wrong username/password combination
    

    您可能想要记录这些信息,因为用户(通过支持层)可能会向操作员询问他们为什么不能使用该应用程序的问题。操作人员将能够在日志文件中看到原因(即使用户已经通过应用程序前端获得了此信息)。

    最后,还有两个日志级别,“调试”和“跟踪”。关于在线跟踪级别的必要性已经有很多激烈的讨论,并且 SLF4J 仅在其较晚(较新)版本之一中引入了跟踪日志级别 - 在许多社区请求之后。

    再说一次,这两者之间的界限可能很模糊,但让我们快速浏览一下:

    调试

    内部流程的高级细节。这仅在调查特定问题期间打开,然后再次关闭。根据所使用的日志库,可能无法在不弹跳(重新启动)应用程序的情况下执行此操作,这可能是不可接受的。

    2018-08-01 05:05:00,031 DEBUG - Checking uploaded XML files for valid structure [...]
    2018-08-01 05:06:00,031 DEBUG - Checking uploaded XML files for valid content [...]
    2018-08-01 05:07:00,031 DEBUG - Masking inputs for XML file[id=5] [...]
    2018-08-01 05:08:00,031 DEBUG - Replacing [...] XML sections for file[id=5] with [...]
    ...
    2018-08-01 05:09:00,142 DEBUG - Forwarding XML file to archiving service

    TRACE - 比调试更多的细节或保留在特定环境中使用

    您可以看到跟踪级别与调试级别一样详细,或者您可以决定将跟踪级别与某些环境(即 DEV 或 TEST 环境)结合起来,在那里开发人员可以发疯并随意注销他们想要的任何东西,知道“跟踪”在生产中将始终被禁用。(虽然这也可以通过不同的日志配置/配置文件轻松实现)

    不过,如果您想看看一个勤奋使用 TRACE 日志记录框架的框架,那么只需看看Spring Framework。当使用 Spring 的事务管理时,您将只能看到真正的数据库事务边界,当您启用 TRACE 日志级别时:

    2018-08-01 05:05:00,031 TRACE - Getting transaction for [com.marcobehler.BitcoinApp.mine]
    
    ... your own log statements./..
    
    2018-08-01 05:05:00,142 TRACE - Completing transaction for [com.marcobehler.BitcoinApp.mine]

    日志文件

    在谈论日志文件时,一种常见的方法是为不同的用例使用单独的文件。这意味着应用程序通常会记录到多个日志文件。

    您可能有一个error.log(文件名模式为 <appname>.<instance-name>.YYYYMMDD.ZZZ.error.log),由监控和警报系统以及操作人员使用。显然,您只需要该日志文件中的条目,即您想要发出警报的条目,即您的 ERROR 或 WARN 语句。

    你可以有另一个日志文件名为info.log建立status.log(与<应用程序名称>。<实例名称>的文件名模式.YYYYMMDD.ZZZ.status.log),其中包含有关应用进展或用户活动的上述信息,以及例如 trace.log 文件,只要您想对日志记录发疯。

    当记录到单独的文件时,有一个命令行实用程序(如log-merger,或只是一个普通的旧 bash 脚本)来动态合并这些单独的日志文件以获得特定的时间戳是有意义的。

    假设你有两个文件:

    错误日志

    2015-08-29 15:49:46,641 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-4) MSC000001: Failed to start service jboss.undertow.listener.default: org.jboss.msc.service.StartException in service jboss.undertow.listener.default: Could not start http listener
            at org.wildfly.extension.undertow.ListenerService.start(ListenerService.java:150)
            at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
            at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
            at java.lang.Thread.run(Thread.java:745)
    Caused by: java.net.BindException: Die Adresse wird bereits verwendet
            at sun.nio.ch.Net.bind0(Native Method)
            at sun.nio.ch.Net.bind(Net.java:436)
            at sun.nio.ch.Net.bind(Net.java:428)
            at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:214)
            at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:74)
            at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:67)
            at org.xnio.nio.NioXnioWorker.createTcpConnectionServer(NioXnioWorker.java:182)
            at org.xnio.XnioWorker.createStreamConnectionServer(XnioWorker.java:243)
            at org.wildfly.extension.undertow.HttpListenerService.startListening(HttpListenerService.java:115)
            at org.wildfly.extension.undertow.ListenerService.start(ListenerService.java:147)
            ... 5 more

    状态日志

    2015-08-29 15:49:46,033 INFO  [org.xnio] (MSC service thread 1-3) XNIO version 3.3.1.Final
    

    运行日志合并实用程序后,可以按如下方式即时查看它们:

    [1] 2015-08-29 15:49:46,033 INFO  [org.xnio] (MSC service thread 1-3) XNIO version 3.3.1.Final
    [0] 2015-08-29 15:49:46,641 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-4) MSC000001: Failed to start service jboss.undertow.listener.default: org.jboss.msc.service.StartException in service jboss.undertow.listener.default: Could not start http listener
    [0]         at org.wildfly.extension.undertow.ListenerService.start(ListenerService.java:150)
    [0]         at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
    [0]         at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
    [0]         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    [0]         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    [0]         at java.lang.Thread.run(Thread.java:745)
    [0] Caused by: java.net.BindException: Die Adresse wird bereits verwendet
    [0]         at sun.nio.ch.Net.bind0(Native Method)
    [0]         at sun.nio.ch.Net.bind(Net.java:436)
    [0]         at sun.nio.ch.Net.bind(Net.java:428)
    [0]         at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:214)
    [0]         at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:74)
    [0]         at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:67)
    [0]         at org.xnio.nio.NioXnioWorker.createTcpConnectionServer(NioXnioWorker.java:182)
    [0]         at org.xnio.XnioWorker.createStreamConnectionServer(XnioWorker.java:243)
    [0]         at org.wildfly.extension.undertow.HttpListenerService.startListening(HttpListenerService.java:115)
    [0]         at org.wildfly.extension.undertow.ListenerService.start(ListenerService.java:147)
    [0]         ... 5 more

    您当然也可以选择从一开始就将所有内容都记录到一个文件中。

    然而,有一个警告:经验表明开发人员经常错误地假设,仅仅因为日志语句具有时间/位置相关性 - 这可能会违反直觉,尤其是当我们刚刚谈到合并日志文件时。

    下面是一个示例:假设您有一个使用 Hibernate 的应用程序。它启动到某个点然后挂起,您看不到更多日志消息。该应用程序根本无法启动。

    您看到的最后一条日志消息如下:

    2018-09-11 09:35:19.166  INFO 14620 --- [ost-startStop-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
    

    很容易假设某些东西必须被 JPA 或 Hibernate 破坏,仅仅因为这是您的最后一条日志消息。事实上,它可能是 Hibernate,但您的应用程序也可能在尝试启动另一个部分/第三方框架时挂起,但尚未将内容注销。

    因此,当您快速得出结论时要小心,这通常发生在高压情况下:当生产中出现严重错误时。通过位置/时间戳日志文件关联并不自动意味着它IS相关,只知道它CAN是。

    MDC

    为了加强日志语句的相关性,还有一个重要的概念需要了解,尤其是当您使用多个分布式进程(微服务)时:映射诊断上下文 (MDC)线程上下文

    假设您有一个用户请求,该请求被路由到多个不同的微服务。当出现问题时,请求失败,您如何知道微服务的哪些日志行对应于该请求。简单:您需要一个生成的请求 ID,您希望在每条日志消息中注销该 ID 。

    因为你很懒,所以你不想手动注销那个 id,它应该自动工作。这就是 MDC 的用武之地。

    在您的代码中的某个地方,在 HTTP servlet 过滤器中,您将拥有如下内容:

    MDC.put("requestId", "lknwelqk-12093alks-123nlkasn-5t234-lnakmwen");
    

    就够了。只需调用一次静态方法。

    稍后,在您的应用程序代码中,您将像往常一样继续记录:

    logger.info("Hi, my name is: Slim Shady!");
    

    您还需要配置您的日志库以在每个日志语句中注销 MDC 变量(请参阅此处)。这将为您提供如下所示的日志消息:

    [lknwelqk-12093alks-123nlkasn-5t234-lnakmwen] - Hi, my name is: Slim Shady!
    

    然后很容易关联所有相应的日志消息,您只需在所有日志文件或集中式日志服务器中指定或搜索相同的请求 ID。

    敏感的信息

    不用说,您应该避免(阅读:不得)注销敏感信息:用户凭据(即密码)或财务信息(如信用卡号等)或类似的敏感用户详细信息。

    根据您系统的复杂性,您可能不想担心修复系统中的每个单独的日志语句(尽管您可能被迫通过审计的方式),但有更多的通用解决方案来确保某些信息被屏蔽 - 部分或完全,取决于您需要遵守的安全标准。

    例如,在 Log4j2 的情况下,这意味着编写自定义LogEventPatternConverter,根据您的规定屏蔽日志事件。

    显然,完整的屏蔽解决方案超出了本指南的范围,但您可以在此处此处获得一些指示。

    主动帮助

    另一个在任何地方都没有真正详细介绍的主题是在日志语句中到底要写什么。这让我们想到了主动帮助的概念。

    一个很好的例子是Spring Boot框架。当您使用 Spring Boot 构建 Web 应用程序并启动它进行测试时,该应用程序将在端口 8080 下运行,因此您可以在浏览器中从http://localhost:8080访问它。

    有时会发生,您有另一个 Spring Boot 应用程序或同一应用程序的旧版本已经在端口 8080 上运行。这意味着您无法启动您的应用程序,因为这会失败。在较旧的 Spring Boot 版本中,他们只是简单地注销了“原始”异常,如下所示:

    2018-09-11 09:35:57.062 ERROR 15516 --- [           main] o.apache.catalina.core.StandardService   : Failed to start connector [Connector[HTTP/1.1-8080]]
    
    org.apache.catalina.LifecycleException: Failed to start component [Connector[HTTP/1.1-8080]]
            at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167)
            at org.apache.catalina.core.StandardService.addConnector(StandardService.java:225)
            at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.addPreviouslyRemovedConnectors(TomcatWebServer.java:256)
            at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:198)
            at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.startWebServer(ServletWebServerApplicationContext.java:300)
            at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:162)
            at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:553)
            at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)
            at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
            at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395)
            at org.springframework.boot.SpringApplication.run(SpringApplication.java:327)
            at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255)
            at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243)
            at com.marcobehler.MarcobehlerRootApplication.main(MarcobehlerRootApplication.java:26)
    Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed
            at org.apache.catalina.connector.Connector.startInternal(Connector.java:1020)
            at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
            ... 13 common frames omitted
            ...

    开发人员当然可以进行从“无法启动连接器”→“另一个 Spring Boot 实例正在运行”所需的心理转换,但更好的方法是 Spring Boot 在较新版本中提供的内容:

    2018-09-11  17:44:49.179 ERROR 24745 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :
    
    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Embedded servlet container failed to start. Port 8080 was already in use.
    
    Action:
    
    Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

    通过添加它可以是一个随机进程或只是需要手动关闭的应用程序的另一个 (IDE) 实例,可以进一步改进它。

    然而,这里的主要内容是,不仅可以在任何地方注销错误消息,而且如果可以的话,还可以提示可能的修复/号召性用语。这将节省以后分析错误和相应日志文件的时间。

    (如果您想查看更多主动帮助的示例,您还可以查看Apache Wicket框架,该框架附带了针对大量错误的“建议修复”。查看其源代码以了解更多详细信息。 )

    迁移遗留应用程序

    从正常情况到本指南中讨论的设置(即统一日志记录实践、日志库等)需要一些时间。特别是如果您正在谈论多个现有的遗留应用程序,您都希望将它们迁移到某种程度相同的日志记录策略。即使不考虑管理支持等所有问题,您也需要为多个版本的所有应用程序缓慢迁移。

    一种方法可能是开始时将所有内容都放入同一个日志文件中,当重新访问代码和日志记录配置时,它会修改所有日志记录以满足新标准。不幸的是,这只是一个粗略的指针,因为完整详细的迁移指南超出了本指南的范围。

    集中记录

    一旦你有一个以上的应用程序实例在运行,甚至多个应用程序的多个实例(想想更大的组织或微服务),“如何管理这些日志”的问题就变得有趣了。一个非常简单的解决方案可能是 OP 具​​有某种脚本,它将所有实例中的所有日志文件复制到某个共享网络文件夹,以便开发人员可以访问它。

    更高级的解决方案是集中式日志服务器或堆栈,如GraylogSplunkElk

    让我们来看看 Graylog。有适用于所有主要 Java 日志记录框架的Graylog 扩展日志格式 - GELF附加程序,这意味着您可以配置例如 Log4j2 将其日志事件直接发送到 Graylog。Graylog 将根据需要使用来自尽可能多的实例和应用程序的日志事件,并将它们显示在一个漂亮的小图形 UI 中——尽管搜索特定日志事件/日期范围的语法需要一些时间来适应。

    以下是 Graylog 仪表板外观的概览:(链接自graylog 主页)。有关 Graylog 外观的更多信息,请参阅其文档。

     

    要点:如果您大到需要一个集中式日志服务器,请对所有可用选项进行快速比较,然后确保以下各项,与您的最终选择无关:

    您的开发人员和应用程序不仅可以写入您的日志服务器。但运维人员和开发人员实际上也知道如何正确处理日志服务器的 UI 并发出正确的搜索查询——尤其是在涉及多个应用程序的多个实例时。

    决定正确的 (™) 登录方式

    到目前为止,这是一段相当长的旅程。我们讨论了大量不同的日志库和不同的日志记录方式。让我们总结一下一切,并且 - 一如既往 - 记住没有“唯一正确的方法”。

    • 如果您正在开发一个新项目,请从SLF4J + LogbackLog4j2 开始。你会没事的。

    • 如果您正在处理遗留项目,请尝试将所有内容迁移到日志门面 (SLF4J),并在您的所有团队中引入同构和统一的日志记录方法

    • 练习正确的日志级别、消息内容、监控措施等需要时间,所以不要沮丧。这是一个迭代过程。

    • 如果您足够大(多个应用程序,多个实例),您可能需要查看集中式日志服务器

    • 最重要的是,享受日志记录!

    这就是今天的内容。如果您有任何问题或发现一些错误(拼写、逻辑等),只需将它们发布到评论部分或给我发送电子邮件。

    展开全文
  • 考虑题4所示的日志记录,假设开始时A、B、C的值都是0 1. 实验 Transaction 性质 2. 实验结论 3. 题目 3.1 如果系统故障发生在14之后,写出系统恢复后A、B、C的值 3.2. 如果系统故障发生在12之后,写出系统恢复...

    目录

    T5.考虑题4所示的日志记录,假设开始时A、B、C的值都是0

    1. 实验 Transaction 性质

    2. 实验结论

    3. 题目

    3.1 如果系统故障发生在14之后,写出系统恢复后A、B、C的值

    3.2. 如果系统故障发生在12之后,写出系统恢复后A、B、C的值

    3.3 如果系统故障发生在10之后,写出系统恢复后A、B、C的值

    3.4 如果系统故障发生在9之后,写出系统恢复后A、B、C的值

    3.5 如果系统故障发生在7之后,写出系统恢复后A、B、C的值

    3.6 如果系统故障发生在5之后,写出系统恢复后A、B、C的值


    T5.考虑题4所示的日志记录,假设开始时A、B、C的值都是0

    1. 实验 Transaction 性质

    首先做实验研究下transaction之间影不影响。这个很困扰我。

     

     

    现在数据已经到了磁盘中,并重新读到内存为0,0,0。

    下面测试一下事务的ROLLBACK怎么工作的。

    关闭嵌套事务自动提交:

    时刻1 & 2,开始事务T1,更新A 在内存中的值Set A, A=10:

    此时刷新表test_rollback:

    因为没有commit,所以A的值没有改变。

     

     

    时刻3:事务 T2 开始

    很可惜,一创建新的transaction,之前的事务就被强制提交了。

    这种测试方法无法得到结论。

    也许可以创建两个数据库连接来测试一下,console一个,navicat一个:

    首先还原A的值:

    然后在navicat中也创建一个transaction:

    这样就有了两个transaction了:

    然后再从头来模拟一下一个事件:

    需要验证的问题是如果两个事务A,B之间已经产生了嵌套:

    再来一遍:

    T1:transaction navicat set A = 10;

    刷新一下,可以看到此时没有更新:

    T2: transaction navicat set b = 10;

    Transaction console set c = 10;

    刷新一下表,发现仍然没有更新,这次也没有发生强制更新的情况:

    T3: transaction navicat rollback;

    T4: transaction console commit;

    刷新,发现:

    此时C依然被更新成功,得到结论:

    2. 实验结论

    1. rollback只会取消对应transaction的操作,不影响其他transaction的正常操作
    2. 事务之间相互是独立的,不要在同一块内存上考虑,因为有数据库锁机制在保护。

     

     

    3. 题目

    3.1 如果系统故障发生在14之后,写出系统恢复后A、B、C的值

    T1, T2, T3重做,T4撤销。

    相当于T1,T2,T3有效,T4无效。

    等效于下图中T13的状态:

     

    而T2自己回滚了且成功回滚,所以等效于删了T2,所以可以进一步简化为:

     

                                                                                                            

    从头到尾推理一遍,所有值都取最新的,可以得到:A = 8,B = 7, C = 11。

    3.2. 如果系统故障发生在12之后,写出系统恢复后A、B、C的值

    T1, T2重做,T3, T4撤销。

    相当于T1, T2 有效,T3, T4无效。

    T2回滚没影响删掉,最终等效于下图对应的状态:

     

     

    也就是等效于只有T1,所以A = 10, B = 0, C = 11。

     

    3.3 如果系统故障发生在10之后,写出系统恢复后A、B、C的值

    T3,T4撤销,T1重做。

    此时T2回滚成功,相当于T2没有发生,此时也可以等效为只有T1:

     

     

    所以答案和第二问相同,依然是A = 10, B=0, C=11。

     

    3.4 如果系统故障发生在9之后,写出系统恢复后A、B、C的值

    此时T2 回滚失败,T2全部撤销,此时也可以等效为只有T1:

     

     

    所以答案仍然为A = 10, B = 0,C = 11。

    3.5 如果系统故障发生在7之后,写出系统恢复后A、B、C的值

    答案为A = 10, B = 0,C = 11不变,理由同(4)。

    3.6 如果系统故障发生在5之后,写出系统恢复后A、B、C的值

    此时T1,T2,T3,T4全部撤销,等效于没有有效事务发生。

    答案为 A=0, B=0,C=0。

    展开全文
  • 一文搞懂Java日志级别,重复记录、丢日志问题

    万次阅读 多人点赞 2020-12-06 21:33:46
    13 | 日志:日志记录真没你想象的那么简单 2020-04-07 朱晔 你好,我是朱晔。今天,我和你分享的是,记录日志可能会踩的坑。 一些同学可能要说了,记录日志还不简单,无非是几个常用的API方法,比如debug、info、...

    1 SLF4J

    日志行业的现状

    • 框架繁
      不同类库可能使用不同日志框架,兼容难,无法接入统一日志,让运维很头疼!
    • 配置复杂
      由于配置文件一般是 xml 文件,内容繁杂!很多人喜欢从其他项目或网上闭眼copy!
    • 随意度高
      因为不会直接导致代码 bug,测试人员也难发现问题,开发就没仔细考虑日志内容获取的性能开销,随意选用日志级别!

    Logback、Log4j、Log4j2、commons-logging及java.util.logging等,都是Java体系的日志框架。
    不同的类库,还可能选择使用不同的日志框架,导致日志统一管理困难。

    • SLF4J(Simple Logging Facade For Java)就为解决该问题而生

    • 提供统一的日志门面API
      图中紫色部分,实现中立的日志记录API

    • 桥接功能
      蓝色部分,把各种日志框架API桥接到SLF4J API。这样即使你的程序使用了各种日志API记录日志,最终都可桥接到SLF4J门面API

    • 适配功能
      红色部分,绑定SLF4J API和实际的日志框架(灰色部分)

    SLF4J只是日志标准,还是需要实际日志框架。日志框架本身未实现SLF4J API,所以需要有个前置转换。
    Logback本身就按SLF4J API标准实现,所以无需绑定模块做转换。

    虽然可用log4j-over-slf4j实现Log4j桥接到SLF4J,也可使用slf4j-log4j12实现SLF4J适配到Log4j,也把它们画到了一列,但是它不能同时使用它们,否则就会产生死循环。jcl和jul同理。

    虽然图中有4个灰色的日志实现框架,但业务开发使用最多的还是Logback和Log4j,都是同一人开发的。Logback可认为是Log4j改进版,更推荐使用,已是社会主流。

    Spring Boot的日志框架也是Logback。那为什么我们没有手动引入Logback包,就可直接使用Logback?

    spring-boot-starter模块依赖spring-boot-starter-logging模块,而
    spring-boot-starter-logging自动引入logback-classic(包含SLF4J和Logback日志框架)和SLF4J的一些适配器。

    2 异步日志就肯定能提高性能?

    如何避免日志记录成为系统性能瓶颈呢?
    这关系到磁盘(比如机械磁盘)IO性能较差、日志量又很大的情况下,如何记录日志。

    2.1 案例

    定义如下的日志配置,一共有两个Appender:

    • FILE是一个FileAppender,用于记录所有的日志
    • CONSOLE是一个ConsoleAppender,用于记录带有time标记的日志

    把大量日志输出到文件中,日志文件会非常大,若性能测试结果也混在其中,就很难找到那条日志了。
    所以,这里使用EvaluatorFilter对日志按照标记进行过滤,并将过滤出的日志单独输出到控制台。该案例中给输出测试结果的那条日志上做了time标记。

    配合使用标记和EvaluatorFilter,可实现日志的按标签过滤

    • 测试代码:实现记录指定次数的大日志,每条日志包含1MB字节的模拟数据,最后记录一条以time为标记的方法执行耗时日志:

    执行程序后发现,记录1000次日志和10000次日志的调用耗时,分别是5.1s和39s

    对只记录文件日志的代码,这耗时过长了。

    2.2 源码解析

    FileAppender继承自OutputStreamAppender

    在追加日志时,是直接把日志写入OutputStream中,属同步记录日志

    所以日志大量写入才会旷日持久。如何才能实现大量日志写入时,不会过多影响业务逻辑执行耗时而影响吞吐量呢?

    2.3 AsyncAppender

    使用Logback的AsyncAppender,即可实现异步日志记录。

    AsyncAppender类似装饰模式,在不改变类原有基本功能情况下,为其增添新功能。这便可把AsyncAppender附加在其他Appender,将其变为异步。

    定义一个异步Appender ASYNCFILE,包装之前的同步文件日志记录的FileAppender, 即可实现异步记录日志到文件

    • 记录1000次日志和10000次日志的调用耗时,分别是537ms和1019ms

      异步日志真的如此高性能?并不,因为它并没有记录下所有日志。

    3 AsyncAppender异步日志的天坑

    • 记录异步日志撑爆内存
    • 记录异步日志出现日志丢失
    • 记录异步日志出现阻塞。

    3.1 案例

    模拟个慢日志记录场景:
    首先,自定义一个继承自ConsoleAppenderMySlowAppender,作为记录到控制台的输出器,写入日志时睡1s。

    • 配置文件中使用AsyncAppender,将MySlowAppender包装为异步日志记录

    • 测试代码

    • 耗时很短但出现日志丢失:要记录1000条日志,最终控制台只能搜索到215条日志,而且日志行号变问号。

    • 原因分析
      AsyncAppender提供了一些配置参数,而当前没用对。

    源码解析

    • includeCallerData
      默认false:方法行号、方法名等信息不显示
    • queueSize
      控制阻塞队列大小,使用的ArrayBlockingQueue阻塞队列,默认容量256:内存中最多保存256条日志
    • discardingThreshold
      丢弃日志的阈值,为防止队列满后发生阻塞。默认队列剩余容量 < 队列长度的20%,就会丢弃TRACE、DEBUG和INFO级日志
    • neverBlock
      控制队列满时,加入的数据是否直接丢弃,不会阻塞等待,默认是false
      • 队列满时:offer不阻塞,而put会阻塞
      • neverBlock为true时,使用offer
    public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
    	// 是否收集调用方数据
        boolean includeCallerData = false;
        protected boolean isDiscardable(ILoggingEvent event) {
            Level level = event.getLevel();
            // 丢弃 ≤ INFO级日志
            return level.toInt() <= Level.INFO_INT;
        }
        protected void preprocess(ILoggingEvent eventObject) {
            eventObject.prepareForDeferredProcessing();
            if (includeCallerData)
                eventObject.getCallerData();
        }
    }
    public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {
    
    	// 阻塞队列:实现异步日志的核心
        BlockingQueue<E> blockingQueue;
        // 默认队列大小
        public static final int DEFAULT_QUEUE_SIZE = 256;
        int queueSize = DEFAULT_QUEUE_SIZE;
        static final int UNDEFINED = -1;
        int discardingThreshold = UNDEFINED;
        // 当队列满时:加入数据时是否直接丢弃,不会阻塞等待
        boolean neverBlock = false;
    
        @Override
        public void start() {
           	...
            blockingQueue = new ArrayBlockingQueue<E>(queueSize);
            if (discardingThreshold == UNDEFINED)
            //默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法
                discardingThreshold = queueSize / 5;
            ...
        }
    
        @Override
        protected void append(E eventObject) {
            if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
                return;
            }
            preprocess(eventObject);
            put(eventObject);
        }
    
        private boolean isQueueBelowDiscardingThreshold() {
            return (blockingQueue.remainingCapacity() < discardingThreshold);
        }
    
        private void put(E eventObject) {
            if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
                blockingQueue.offer(eventObject);
            } else {
                putUninterruptibly(eventObject);
            }
        }
        //以阻塞方式添加数据到队列
        private void putUninterruptibly(E eventObject) {
            boolean interrupted = false;
            try {
                while (true) {
                    try {
                        blockingQueue.put(eventObject);
                        break;
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            } finally {
                if (interrupted) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }  
    

    默认队列大小256,达到80%后开始丢弃<=INFO级日志后,即可理解日志中为什么只有两百多条INFO日志了。

    queueSize 过大

    可能导致OOM

    queueSize 较小

    默认值256就已经算很小了,且discardingThreshold设置为大于0(或为默认值),队列剩余容量少于discardingThreshold的配置就会丢弃<=INFO日志。这里的坑点有两个:

    1. 因为discardingThreshold,所以设置queueSize时容易踩坑。
      比如本案例最大日志并发1000,即便置queueSize为1000,同样会导致日志丢失
    2. discardingThreshold参数容易有歧义,它不是百分比,而是日志条数。对于总容量10000队列,若希望队列剩余容量少于1000时丢弃,需配置为1000

    neverBlock 默认false

    意味总可能会出现阻塞。

    • discardingThreshold = 0,那么队列满时再有日志写入就会阻塞
    • discardingThreshold != 0,也只丢弃≤INFO级日志,出现大量错误日志时,还是会阻塞

    queueSize、discardingThreshold和neverBlock三参密不可分,务必按业务需求设置:

    • 若优先绝对性能,设置neverBlock = true,永不阻塞
    • 若优先绝不丢数据,设置discardingThreshold = 0,即使≤INFO级日志也不会丢。但最好把queueSize设置大一点,毕竟默认的queueSize显然太小,太容易阻塞。
    • 若兼顾,可丢弃不重要日志,把queueSize设置大点,再设置合理的discardingThreshold

    以上日志配置最常见两个误区

    再看日志记录本身的误区。

    4 如何选择日志级别?

    使用{}占位符,就不用判断log level了吗?

    据不知名网友说道:SLF4J的{}占位符语法,到真正记录日志时才会获取实际参数,因此解决了日志数据获取的性能问题。
    是真的吗?

    • 验证代码:返回结果耗时1s

    若记录DEBUG日志,并设置只记录>=INFO级日志,程序是否也会耗时1s?
    三种方法测试:

    • 拼接字符串方式记录slowString
    • 使用占位符方式记录slowString
    • 先判断日志级别是否启用DEBUG。



    前俩方式都调用slowString,所以都耗时1s。且方式二就是使用占位符记录slowString,这种方式虽允许传Object,不显式拼接String,但也只是延迟(若日志不记录那就是省去)日志参数对象.toString()字符串拼接的耗时。

    本案例除非事先判断日志级别,否则必调用slowString。所以使用{}占位符不能通过延迟参数值获取,来解决日志数据获取的性能问题。

    除事先判断日志级别,还可通过lambda表达式延迟参数内容获取。但SLF4J的API还不支持lambda,因此需使用Log4j2日志API,把Lombok的@Slf4j注解替换为**@Log4j2**注解,即可提供lambda表达式参数的方法:

    这样调用debug,签名Supplier<?>,参数就会延迟到真正需要记录日志时再获取:



    所以debug4并不会调用slowString方法

    只是换成Log4j2 API,真正的日志记录还是走的Logback,这就是SLF4J适配的好处。

    总结

    • SLF4J统一了Java日志框架。在使用SLF4J时,要理清楚其桥接API和绑定。若程序启动时出现SLF4J错误提示,可能是配置问题,可使用Maven的dependency:tree命令梳理依赖关系。
    • 异步日志解决性能问题,是用空间换时间。但空间毕竟有限,当空间满,要考虑阻塞等待or丢弃日志。若更希望不丢弃重要日志,那么选择阻塞等待;如果更希望程序不要因为日志记录而阻塞,那么就需要丢弃日志。
    • 日志框架提供的参数化记录方式不能完全取代日志级别的判断。若日志量很大,获取日志参数代价也很大,就要判断日志级别,避免不记录日志也要耗时获取日志参数!
    展开全文
  • Spring Boot 各种日志框架记录方式

    千次阅读 2020-06-28 15:09:49
    1、常用日志框架比较 ...结构如: 门面模式的核心为Facade即门面对象,门面对象核心为: 知道所有子角色的功能和责任 将客户端发来的请求委派到子系统中,没有实际业务逻辑 不参与子系统内
  • IOS日志记录

    万次阅读 2014-08-19 11:30:31
    在开发过程中,众所周知,日志记录调试的关键部分,尤其是当产品发布的时候,有用户feedback一些崩溃问题或者是其他问题时,日志就显得尤其重要,通过分析日志可以很快地找出问题的症结所在并快速解决问题。...
  • Java中使用log4j记录日志

    千次阅读 2016-02-04 09:35:03
    在项目开发中,记录错误日志是一个很有必要功能。一是方便调试;二是便于发现系统运行过程中的错误;三是存储业务数据,便于后期分析; 在java中,记录日志,有很多种方式。 比如,自己实现。 自己写类,将...
  • 6.1.6 日志压缩

    千次阅读 2021-04-21 22:10:07
    不管是传统的RDBMS还是分布式的NoSQL,存储在数据库中的数据总会更新。更新数据有两种方式:直接更新(找到数据库中的已有位置,以最新的值替换旧的值)、以追加方式更新...如6-35所示,Kafka的消息由键值组成,在.
  • 数据库第十章课后题(2020.4.29作业)

    千次阅读 多人点赞 2020-04-30 10:02:51
    1.考虑下图所示的日志记录: 序号 日志 1 T1:开始 2 T1:写A,A=10 3 T2:开始 4 T2:写B,B=9 5 T1:写C,C=11 6 T1:提交 7 T2:写C,C=13 8 T3:开始 9 T3:写A,A=8 10 T2:回滚 11 T3:写B,B=7 12 T4:开始 13 T3:提交 14...
  • 数据库第十章习题作业

    千次阅读 多人点赞 2021-05-18 18:34:27
    考虑下图所示的日志记录:(1)如果系统故障发生在14之后,说明哪些事务需要重做,哪些事务需要回滚。(2)如果系统故障发生在10之后,说明哪些事务需要重做,哪些事务需要回滚。(3)如果系统故障发生在9之后,说明...
  • ELK 收集 Docker 日志

    千次阅读 2022-04-10 10:12:57
    文章目录00 收集日志的目的01 安装Docker环境1.1 Ubuntu18.04 安装Docker1.2 Docker安装Nginx镜像1.3 查看Docker镜像的日志文件02 Filebeat根据容器ID收集Docker日志03 Filebeat收集多个Docker容器日志3.1 启动多个...
  • Ingress(Nginx)日志持久化与可视化(多预警) Ingress(Nginx)日志持久化与可视化(多预警) 前言 部署架构 部署步骤 一、ingress持久化步骤 1. 自建kubernetes的ingress持久化 (1) ingress添加PVC用于...
  • JAVA中使用LOG4J记录日志

    千次阅读 2018-01-25 18:20:45
    在项目开发中,记录错误日志是一个很有必要功能。一是方便调试;二是便于发现系统运行过程中的错误;三是存储业务数据,便于后期分析; 在java中,记录日志,有很多种方式。 比如,自己实现。 自己写类,将...
  • 网页制作实习日志

    千次阅读 2021-06-13 07:52:28
    网页制作实习日志实习日志就是实习生在实习这段时间记录所学到的东西一种文本。下面是关于网页制作实习日志的内容,欢迎阅读!网页制作实习日志1具体情况通过这半年的学习实践中 和老师的指导以大量明晰的操作步骤...
  • 网站用户行为日志采集和后台日志服务器搭建

    万次阅读 多人点赞 2018-07-04 00:50:16
    web 服务器软件(httpd、nginx、tomcat)自带的日志记录功能,如 Nginx 的 access.log 日志; 自定义采集用户行为数据,通过在页面嵌入自定义的 javascript 代码来 获取用户的访问行为(比如鼠标悬停的位置,点击的...
  • 在多线程应用程序中进行日志记录

    千次阅读 2012-12-06 11:16:41
    要分析并找出导致这些问题的原因,程序员所广泛使用的一种方法就是日志记录。在本文中,您将了解如何使用循环缓冲区通过内存操作(而不是文件操作)高效地进行日志记录。为该缓冲区选择合适的大小,从而确保转储相关...
  • 一文详解 C++ 日志框架

    千次阅读 2020-10-18 23:28:58
    1日志框架日志框架一个经过专门设计的实用程序,用于规范应用程序的日志记录过程。日志框架可以自己编写(需要一定的能力哦),也可以由第三方(例如:log4cplus)提供。对于不同的日志框...
  • 虽然纯文本数据在某些情况仍然很有用,但是在进行扩展分析以收集有洞察力的基础设施数据并改进代码质量时,寻找一个可靠的日志管理解决方案是值得的,该解决方案可以增强业务工作流的能力。 日志不是一件容易处理...
  • 部署Zipkin环境的操作记录: 部署Zipkin,比较麻烦的是前期环境的准备,只有先把前期环境安装好了,后面的部署就顺利多了。(部署机ip为192.168.1.102) 一、环境准备:  1)java环境安装(Centos中yu
  • 一篇文章教你如何用 Python 记录日志

    万次阅读 2018-02-02 00:00:00
    (点击上方公众号,可快速关注)编译: Python开发者 - 李趴趴要化身女超人,英文:Mario Corcherohttp://python.jobbole.com/89007/对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不...记录日志允许我们在开发
  • Mysql——DELETE操作对应的undo日志

    千次阅读 2021-11-13 20:38:20
    目录一、正常记录链表二、垃圾链表三、PAGE...4、什么TRX_UNDO_DEL_MARK_REC类型的undo日志保存旧记录的trx_id值和roll_pointer值六、删除操作生成undo日志的示例 一、正常记录链表 记录的头信息中的next_record属性组
  • Hadoop/Yarn的日志清理

    千次阅读 2020-09-22 11:50:39
    Hadoop/Yarn的日志清理可以分为两个子话题讨论: Hadoop/Yarn的本地日志(非Yarn Container生成的日志) Yarn的Container生成的日志 我们这里讨论的日志清理并不是通过定时的日志删除命令去实现,这一做法显然优雅...
  • Spring Boot 日志各种使用姿势,是时候捋清楚了!

    千次阅读 多人点赞 2020-12-16 09:38:17
    之前录过一个视频和大家分享 Spring Boot 日志问题,但是总感觉差点意思,因此松哥打算再通过一篇文章来和大家捋一捋 Java 中的日志问题,顺便我们把 Spring Boot 中的日志问题也说清楚。 1. Java 日志概览
  • 从命令行如何查看Linux日志

    千次阅读 2021-05-08 21:31:35
    本文作者介绍了在Linux查看日志的具体方法。在你作为Linux管理员的职业生涯中,应该早晚都会查看日志文件。因为日志文件可以帮助你排查问题,每个经验丰富的管理员在出现问题后,要做的头一件事就是查看日志。你会...
  • 最佳日志数据实践

    千次阅读 2017-07-13 11:24:38
    原文链接: https://zhuanlan.zhihu.com/p/273634840. 缘起大约在三年前,我曾经写过一篇 最佳日志实践,还被码农周刊选为那年的 最受欢迎技术干货 之一。当时我任职于网易杭州研究院的存储平台组,主要做网易...
  • 基于深度学习的日志数据异常检测

    万次阅读 多人点赞 2020-12-27 12:00:12
    基于日志数据的异常检测 数据对象 ...系统运行时数据主要包含监控数据和日志数据,监控数据记录的是指系统运行状态的资源占用情况,如中央处理器使用率、内存使用率、网络流量、进程数目以及进程资源使
  • Linux原生日志系统Rsyslog详解

    千次阅读 2021-09-25 12:55:02
    Rsyslog 是一个 syslogd 的多线程增强版,依然基于Syslog协议(linux6之前默认使用syslog程序,centos6用rsyslog所取代)完成系统日志的处理转发,官方形容它是一个极速(如火箭般快速)的日志处理系统。它提供高...
  • 出品丨Docker公司(ID:docker-cn)编译丨小东每周一、三、五晚6点10分 与您不见不散简 介在传统上,设计和实现集中的日志记录总是成为马后炮。要等到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 158,234
精华内容 63,293
关键字:

考虑下图的日志记录