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

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