JUL到SLF4J桥接器

87
我目前观察到一个第三方库(即restfb)正在使用java.util.logging,尽管我没有在我的logback.xml中配置SLF4J控制台appender,但我看到这些日志最终出现在STDOUT中。我还在类路径中拥有jul-to-slf4j桥接器。当安装了jul-to-slf4j桥接器时,它是否仅记录到logback配置的appender中,还是也会记录到stdout中?
6个回答

126
你需要调用 SLF4JBridgeHandler.install()。同时需要在 java.util.logging 的根记录器中启用所有日志级别(原因见下面的摘录),并删除默认控制台附加程序。

此处理程序将重定向 jul 日志记录到 SLF4J。然而,只有在 j.u.l. 中启用的日志记录才会被重定向。例如,如果调用 j.u.l. 记录器的日志语句禁用该语句,则根据定义,该语句将无法到达任何 SLF4JBridgeHandler 实例,并且无法被重定向。

可以按以下方式完成整个过程:

import java.util.logging.Logger;
import org.slf4j.bridge.SLF4JBridgeHandler;

SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Logger.getLogger("").setLevel(Level.FINEST); // Root logger, for example.

出于性能的原因,您可以将级别设置为比finest更高的值,但是在启用java.util.logging之前,您将无法打开这些日志记录(原因如上摘录所述)。


1
有趣的是,我认为将桥接器放在类路径中就足够了。这只对JUL必要吗? - Taylor Leese
8
是的。原因在于,jul-to-slf4j桥不能像其他桥接实现一样静态地替换java.util.logging包中的类来进行重定向。相反,它必须在根记录器上注册一个处理程序,并像任何其他处理程序一样监听日志记录语句。然后,它将重定向这些日志记录语句。 - Dev
4
我发现如果应用程序的任何部分(例如静态初始化程序)在执行此过程之前创建JUL Logger对象,则它们的日志级别不会被更改。 - wberry
4
LevelChangePropagator 旨在解决使用 Logback 时的性能问题:http://logback.qos.ch/manual/configuration.html#LevelChangePropagator - Jason
4
自Java 1.7以来,Logger.getLogger("global")可被替换为Logger.getGlobal() - 稍微更加方便! - Muel
显示剩余4条评论

48

正如SLF4JBridgeHandler的Java文档中所提到的,你可以通过调用以下方法之一来在程序中安装SLF4JBridgeHandler:

 // Optionally remove existing handlers attached to j.u.l root logger
 SLF4JBridgeHandler.removeHandlersForRootLogger();  // (since SLF4J 1.6.5)

 // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during
 // the initialization phase of your application
 SLF4JBridgeHandler.install();

或者通过logging.properties文件

 // register SLF4JBridgeHandler as handler for the j.u.l. root logger
 handlers = org.slf4j.bridge.SLF4JBridgeHandler

关于性能问题,jul-to-slf4j桥接的部分讨论了这个问题。实质上,由于您已经在使用logback,启用LevelChangePropagator应该可以提供良好的性能,无论负载如何。


3
你是否曾经遇到过这种情况:即使SLF4JBridgeHandler存在于类路径中,仍会出现java.lang.ClassNotFoundException: org.slf4j.bridge.SLF4JBridgeHandler的情况?当我使用-Djava.util.logging.config.file=来指定logging.properties文件时,似乎就会出现这种情况。 - Taylor Leese
@TaylorLeese.. 我目前也遇到了同样的问题,如果你找到了解决方法,请告诉我好吗?请查看此SO以获取更多信息.. https://dev59.com/JnHYa4cB1Zd3GeqPRvA0 - Dexter
@Dexter 我找到的唯一解决方法是在代码中初始化桥接器,而不是通过属性文件。 - Taylor Leese
1
@TaylorLeese.. 谢谢!但是我该如何配置Tomcat,以便在启动期间不记录其默认格式?您能否检查SO链接https://dev59.com/JnHYa4cB1Zd3GeqPRvA0 我认为您的评论可能对我解决此问题有所帮助。谢谢! - Dexter
1
如果有像Maven依赖项一样包含预配置的logging.properties和Bridge的东西,那不是很好吗?这样,您可以将其添加到Maven插件依赖项部分,否则您无法访问该部分。 - Hendrik Jander

17

我使用SLF4J和新的Postgres驱动程序42.0.0

根据更改日志,它使用java.util.logging

要查看驱动程序日志,只需要:

  1. 添加jul-to-slf4j桥接器

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <version>${slf4j.version}</version>
        <scope>runtime</scope>
    </dependency>
    
  2. 在logback.xml(logback-test.xml)中添加

  3. <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>
    
    <appender ...
    
    <logger name="org.postgresql" level="trace"/>
    
  4. 添加代码

  5. static {
        SLF4JBridgeHandler.install();
    }
    

5

我的解决方案:

SLF4JBridgeHandler.install();
java.util.logging.LogManager.getLogManager().getLogger("").setLevel( Level.INFO);

将jul-to-slf4j放在您的应用程序库或glassfish库中,这将重定向JUL到SLF4J(因此在我的例子中重定向到LOG4J)。
对于Jersey,您可以做以下操作:
<logger name="com.sun.jersey" additivity="false">
    <level value="WARN" />
    <appender-ref ref="JVM" />
    <appender-ref ref="CONSOLE" />
</logger>   

<logger name="com.sun.common.util.logging" additivity="false">
    <level value="ERROR" />
    <appender-ref ref="JVM" />
    <appender-ref ref="CONSOLE" />
</logger>

最后一个配置是为了避免被其他记录器污染。

4

考虑到JUL桥接方面的情况,这个解决方案看起来很不错,而且对我来说非常有效,因为我只需在logback.groovy文件中编写所有内容

  1. (If you are not using logback.groovy configuration or logback at all, of course you have to put the logic part into some class (e.g. like class MyApp { static { /* log init code here */ } ... }).)

  2. src/logback.groovy:

     import org.slf4j.bridge.SLF4JBridgeHandler
     import ch.qos.logback.classic.jul.LevelChangePropagator
    
     // for debug: just to see it in case something is logging/initialized before
     System.out.println( 'my myapp logback.groovy is loading' )
    
     // see also: http://logback.qos.ch/manual/configuration.html#LevelChangePropagator
     // performance speedup for redirected JUL loggers
     def lcp = new LevelChangePropagator()
     lcp.context = context
     lcp.resetJUL = true
     context.addListener(lcp)
    
     // needed only for the JUL bridge: https://dev59.com/G2ox5IYBdhLWcg3wk1Ig#9117188
     java.util.logging.LogManager.getLogManager().reset()
     SLF4JBridgeHandler.removeHandlersForRootLogger()
     SLF4JBridgeHandler.install()
     java.util.logging.Logger.getLogger( "global" ).setLevel( java.util.logging.Level.FINEST )
    
     def logPattern = "%date |%.-1level| [%thread] %20.20logger{10}|  %msg%n"
    
     appender("STDOUT", ConsoleAppender) {
         encoder(PatternLayoutEncoder) {
             pattern = logPattern
         }
     }
    
     /*// outcommenting in dev will not create dummy empty file
     appender("ROLLING", RollingFileAppender) {  // prod
         encoder(PatternLayoutEncoder) {
             Pattern = "%date %.-1level [%thread] %20.20logger{10}  %msg%n"
         }
         rollingPolicy(TimeBasedRollingPolicy) {
             FileNamePattern = "${WEBAPP_DIR}/log/orgv-fst-gwt-%d{yyyy-MM-dd}.zip"
         }
     }
     */
    
     appender("FILE", FileAppender) {  // dev
    
         // log to myapp/tmp (independent of running in dev/prod or junit mode:
    
         //System.out.println( 'DEBUG: WEBAPP_DIR env prop:  "."='+new File('.').absolutePath+',  \${WEBAPP_DIR}=${WEBAPP_DIR},  env=' + System.getProperty( "WEBAPP_DIR" ))
         String webappDirName = "war"
         if ( new File( "./../"+webappDirName ).exists() )  // we are not running within a junit test
             file = "../tmp/myapp.log"
         else  // junit test
             file = "tmp/myapp-junit-tests.log"
    
         encoder(PatternLayoutEncoder) { pattern = logPattern }
     }
    
     // without JUL bridge:
     //root(WARN, ["STDOUT", "ROLLING"])  // prod
     //root(DEBUG, ["STDOUT", "FILE"])  // dev
    
     // with JUL bridge: (workaround: see links above)
     def rootLvl = WARN
     root(TRACE, [/*"STDOUT",*/ "FILE"])
     // I manually added all "root package dirs" I know my libs are based on to apply
     // the root level to the second "package dir level" at least
     // depending on your libs used you could remove entries, but I would recommend
     // to add common entries instead (feel free to edit this post if you like to
     // enhance it anywhere)
     logger( "antlr", rootLvl )
     logger( "de", rootLvl )
     logger( "ch", rootLvl )
     logger( "com", rootLvl )
     logger( "java", rootLvl )
     logger( "javassist", rootLvl )
     logger( "javax", rootLvl )
     logger( "junit", rootLvl )
     logger( "groovy", rootLvl )
     logger( "net", rootLvl )
     logger( "org", rootLvl )
     logger( "sun", rootLvl )
    
    
     // my logger setup
    
     logger( "myapp", DEBUG )
    
    
     //logger( "org.hibernate.SQL", DEBUG )  // debug: log SQL statements in DEBUG mode
     //logger( "org.hibernate.type", TRACE )  // debug: log JDBC parameters in TRACE mode
     logger( "org.hibernate.type.BasicTypeRegistry", WARN )  // uninteresting
    
     scan("30 seconds")  // reload/apply-on-change config every x sec
    

建议我使用它,因为您可以像在此处使用的Java代码变量/函数一样进行反应,例如SLF4JBridgeHandler或关于webappDirName的日志目录。

由于它可以设置所有内容或作为起始模板,因此将文件保留完整会给人留下更好的印象。

对某些人可能有用-我的环境:slf4j 1.7.5,logback 1.1.2,groovy 2.1.9


1
你太棒了!!……谢谢,这个配置真的很酷!!;):) - Carlos Saltos

0

除了Dev的答案提供的配置命令外,在我正在使用的代码中,有很多由第三方库生成的带有isLoggable检查的跟踪级别(FINEST)消息。因此,无条件启用所有级别看起来不是一个好主意。

我正在使用SLF4J简单记录器,它没有动态更改日志记录级别的方法。因此,我添加了以下代码片段,根据通过系统属性传递的slf4j简单记录器配置自动调整java util日志记录级别:

import java.util.logging.Level;
import java.util.logging.Logger;

class Main {
    static {
        java.util.Map<String,Level> levelMap = new java.util.HashMap<>();
        levelMap.put("TRACE", Level.FINEST);
        levelMap.put("DEBUG", Level.FINE);
        levelMap.put("INFO", Level.INFO);
        levelMap.put("WARN", Level.WARNING);
        levelMap.put("ERROR", Level.SEVERE);
        levelMap.put("OFF", Level.OFF);

        for (String property : System.getProperties().stringPropertyNames()) {
            if (property.startsWith(org.slf4j.simple.SimpleLogger.LOG_KEY_PREFIX)) {

                String logger = property.replaceFirst(org.slf4j.simple.SimpleLogger.LOG_KEY_PREFIX, "");
                String value = System.getProperty(property).toUpperCase();

                Level level = levelMap.getOrDefault(value, Level.INFO);
                Logger.getLogger(logger).setLevel(level);
            }
        }

        org.slf4j.bridge.SLF4JBridgeHandler.removeHandlersForRootLogger();
        org.slf4j.bridge.SLF4JBridgeHandler.install();
    }
}

附言:此片段不处理在类路径上定义的simplelogger.properties文件中定义的简单记录器设置。假设这些是静态的,相应的JUL调整可以根据需要执行。

Logback中可用的LevelChangePropagator基本上也是这样做的,但动态跟踪级别变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接