2024 Clojure状态调查中分享您的想法!

欢迎!请参阅关于页面以了解更多关于此功能的信息。

0
tools.logging

当使用tools.logging和java.util.logging(简称JUL)时,默认我们得到的日志前缀如下

Oct 23, 2023 2:14:47 PM clojure.tools.logging$eval3668$fn__3671 invoke

注意,记录的类和方法名称完全无用。

tools.logging库推荐的解决方案是用户应更改日志输出格式,使其不打印出类和方法,而是打印出记录器名称,即Clojure命名空间名称。

这是一种可能的解决方案,当日志执行的应用程序和配置JUL的代码的作者是同一个人时,即日志在应用程序级别配置和执行。

用例

我会说,tools.logging更常见的用例是由库作者使用,他不知道最终用户使用哪个日志解决方案。同时,最终用户也不知道他们的依赖树中存在这种日志。在这种情况下,有人构建了一个Clojure应用程序,他们可能有100个依赖项。突然他们开始在他们错误的stderr中得到上述的消息。

他们可能想要
- 将其重定向到文件
- 抑制消息
- 想要知道哪个依赖生成它们

所有这些任务都需要用户识别出他们正在查看JUL日志,并配置JUL以输出记录器名称来查找正在进行日志记录的命名空间。 他们还可能希望在他们的Java代码中保持方法名的格式。

方法

使用LogRecord类强制将类名转换为Clojure命名空间。示例代码

(cond-> (doto (LogRecord. level# ~msg)
                      (.setLoggerName ~ns)
                      (.setMessage ~msg)
                      (.setParameters (object-array ~params))
                      (.setSourceClassName ~ns))
              ~e (doto (.setThrown ~e)))

优点

  1. 在输出中替换clojure.tools.logging$eval3668$fn__3671 invokemy.lib.namespace。前者字符串没有信息价值,后者使我们能够了解日志语句执行的位置。

  2. 提高性能。如果调用.setSourceClassName,则日志系统不会使用反射来确定调用类和方法。这涉及到遍历堆栈跟踪,它很慢。

  3. 即使在默认/无配置的JUL中,也提供了一个关于在stderr中查找谁制作所有这些消息的线索,如果用户没有意识到这个日志库/机制。

如果需要,我可以提交一个PR来影响这个更改。

1 个答案

0

我怀疑你的设置中可能有什么问题。我在我的项目中使用Logback而不是SLF4J,因此格式中的logger键总是输出正确的命名空间。c.t.l logger的JUL实现与这没有太大区别。

为了绝对确定,我创建了一个只有单个命名空间的最简单的项目

(ns app.core
  (:require [clojure.tools.logging :as log]
            [clojure.tools.logging.impl :as log-impl]))

(defn -main []
  (System/setProperty "java.util.logging.SimpleFormatter.format"
                      "[%1$tF %1$tT] [%4$-7s] [%3$s] %5$s %n")
  (binding [log/*logger-factory* (log-impl/jul-factory)]
    (log/info "Hello")))

它只依赖于org.clojure/tools.logging {:mvn/version "1.2.4"}
当用clj -M -m app.core运行时,我在控制台看到如下:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[2023-11-05 16:57:41] [INFO   ] [app.core] Hello

如你所见,可读的命名空间名称就在那里。

(这只是为了快速测试,当然你不应该像那样使用c.t.l.impl。也许你应该通过实际的属性或属性文件来设置格式。)

关键是调用System/setProperty。如果你省略它,打算依赖用户提供配置,那么就会得到OP抱怨的原始输出。

Nov 05, 2023 10:35:35 AM user$eval464$fn__467 invoke
INFO: Hello

设置了该属性后,我得到的和你一样,但没有SLF4J的抱怨

[2023-11-05 10:37:35] [INFO   ] [app.core] Hello
是的,但这只是JUL的默认格式。其他日志库有其他的默认格式,也许还有不同的默认配置参数。在我看来,c.t.l不应该为此烦心,因为它只是对底层实现的一个非常薄的封装。
我完全同意这不是c.t.l应该关心的或需要解决的问题!我只是想指出,你的“重现”案例并不是OP问题的确切重现——因为你用setProperty调用“修复”了格式。

如果一个库选择使用c.t.l,而该库的用户对日志不进行任何操作,他们将会得到一份难看的打印输出。如果该库的用户对日志有明确的操作,那么他们可能已经控制了该输出。

在公司,我们使用log4j2,并将其一切桥接进来,我们使用c.t.l并强制c.t.l使用log4j2——所以如果库选择使用c.t.l,我们不在乎,因为一切都是一致的。

我想这有一个关于“(Clojure)库是否应该首先使用c.t.l”或应该是“日志中立”的深层哲学问题(那么那会是什么样子)?

clojure.java.data依赖于它,但只条件性地使用它——反过来又依赖于next.jdbc,这把日志引入了许多程序中。java.data默认不记录...
正如所说,问题是当随机的库用户开始在他们的std.err中看到这些奇怪的日志语句,他们几乎没有线索开始寻找原因。是的,显然你可以修复格式来省略这些信息。

我理解有些人认为这不是c.t.l要解决的问题,但我认为打印输出是由c.t.l宏引起的,更重要的是,这个问题主要是通过改变c.t.l来修复的。

我主张这种改变因为
没有负面的权衡
代码复杂性并没有真正增加
提高性能(JUL不需要执行反映来确定调用类和方法)

我真的看不到这种改变有什么副作用。
...