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

欢迎!关于此工作方式,请参阅关于页面以获得更多信息。

0
tools.logging by

当使用 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应用程序,他们可能有一百个依赖项。突然,他们开始看到上面在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 invoke替换为my.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。如果你省略这一步,打算依赖用户提供的配置,你就会得到原始输出,也就是原帖用户所抱怨的输出。

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 不需要执行反射来找出调用类和方法)

我真的看不到这个更改有任何缺点。
...