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

欢迎!请参阅关于 页面以了解有关该工作的更多信息。

0
in 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应用程序,可能有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 over 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 需要关心的,因为它是底层实现的一个非常薄的包装。
by
哎呀,我完全同意,这不算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不会默认记录...
by
正如所述,问题是当随机库用户开始在自己的std.err中看到这些奇怪的日志语句时,他们几乎没有线索开始寻找原因。是的,显然你可以修复格式来省略这些信息。

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

我提倡这个改变,因为
- 没有负面权衡
- 代码复杂度没有真正增加
- 提高了性能(JUL不需要执行反射来找出调用类和方法)

我真的看不到这个改变的负面影响。
...