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命名空间名称。

这是一个可能解决方案,当执行日志记录的APP的作者和配置JUL的代码的作者同一个人的时候,也就是日志是在APP级别配置和执行的。

用例

我会说,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。前者字符串信息量为0,后者使我们能够知道在何处查找执行的日志语句。

  2. 提高性能。如果调用.setSourceClassName,则日志系统不会使用反射来确定调用类和方法。这需要遍历堆栈跟踪,是一件缓慢的事情。

  3. 即使默认/无配置的JUL也会给出关于谁在stderr中制造所有这些消息的线索,如果用户不知道这个日志库/机制。

如果需要,我可以提出一个PR来影响这一变化。

1 个答案

0
by

我怀疑你的设置中可能出了点问题。我在项目中使用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。可能还需要通过实际属性或属性文件来设置格式。)

by
问题的关键在于调用System/setProperty。如果你省略该调用,意图依赖于 whatever 用户提供的配置,你将得到原帖中提到的问题输出

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
by
是的,但这却是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不需要执行反射来确定调用类和方法)

我真心看不出这种变化的缺点。
...