【问题标题】:JUL to SLF4J Bridge七月到 SLF4J 桥
【发布时间】:2012-02-25 09:14:21
【问题描述】:

我目前观察到第 3 方库(即 restfb)正在使用 java.util.logging,我看到这些日志最终出现在 STDOUT 中,即使我没有在我的 logback 中配置 SLF4J 控制台附加程序.xml。我的类路径中也有jul-to-slf4j 桥。 jul-to-slf4j桥是在安装桥时只记录到logback配置的appender,还是也记录到stdout?

【问题讨论】:

    标签: java slf4j logback java.util.logging


    【解决方案1】:

    您需要致电SLF4JBridgeHandler.install()。您还需要在 java.util.logging 中的根 logger 中启用所有日志级别(原因在下面的摘录中),并删除默认的控制台附加程序。

    这个处理程序会将 jul 日志重定向到 SLF4J。但是,只有日志 在 j.u.l. 中启用将被重定向。例如,如果一个日志语句 调用 j.u.l. logger 禁用了该语句,根据定义,将 没有到达任何 SLF4JBridgeHandler 实例并且不能被重定向。

    整个过程可以这样完成

    import java.util.logging.Logger;
    import org.slf4j.bridge.SLF4JBridgeHandler;
    
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();
    Logger.getLogger("").setLevel(Level.FINEST); // Root logger, for example.
    

    出于性能原因,您可以将级别设置为高于最好的级别,但如果不先在java.util.logging 中启用这些日志,您将无法打开这些日志(出于上述摘录中提到的原因)。

    【讨论】:

    • 有趣的是,我的印象是在类路径中拥有桥就足够了。这只需要 7 月吗?
    • 是的。原因是 jul-to-slf4j 桥不能替换 java.util.logging 包中的类来静态地进行重定向,就像它对其他桥实现所做的那样。相反,它必须在根记录器上注册一个处理程序,并像任何其他处理程序一样侦听日志记录语句。然后它将重定向那些日志记录语句。
    • 我发现如果应用程序的任何部分创建了 JUL Logger 对象,例如在静态初始化程序中,在执行此过程之前,它们的日志级别不会更改。
    • LevelChangePropagator 解决了使用 Logback 时的性能问题:logback.qos.ch/manual/configuration.html#LevelChangePropagator
    • 从 Java 1.7 开始,Logger.getLogger("global") 可以替换为 Logger.getGlobal() - 稍微方便一点!
    【解决方案2】:

    正如 SLF4JBridgeHandler 的 javadocs 中所述,您可以通过调用以编程方式安装 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-slf4jbridge 部分讨论了这个问题。本质上,由于您已经在使用 logback,因此无论负载如何,启用 LevelChangePropagator 都会产生良好的性能。

    【讨论】:

    • 您是否见过即使类路径中确实存在 SLF4JBridgeHandler 也会发生 java.lang.ClassNotFoundException: org.slf4j.bridge.SLF4JBridgeHandler 的实例?当我使用 -Djava.util.logging.config.file= 指定 logging.properties 文件时,我似乎得到了这个。
    • @TaylorLeese .. 我目前面临同样的问题,如果您找到解决方法,请告诉我?查看此 SO 以获取更多信息。stackoverflow.com/questions/16472801/…
    • @Dexter 我发现的唯一解决方法是在代码中而不是通过属性文件初始化桥。
    • @TaylorLeese.. 谢谢!但是如何配置 tomcat 使其在启动期间不记录其默认格式?您能否查看 SO 链接 stackoverflow.com/questions/16472801/… 我认为您的 cmets 可能有助于我解决此问题。谢谢!
    • 如果有类似 maven 依赖项的东西,其中包含已配置好的 logging.properties 和 Bridge 会不会很好?因此,您可以将其添加到例如 maven-plugin depedencies 部分,否则您无法访问
    【解决方案3】:

    我使用 SLF4J 和new Postgres driver 42.0.0

    根据changelog,它使用 java.util.logging

    拥有驱动程序日志就足够了:

    1. 添加jul-to-slf4j bridge:

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

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

      static {
          SLF4JBridgeHandler.install();
      }
      

    【讨论】:

      【解决方案4】:

      我的解决方案:

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

      将 jul-to-slf4j 放在您的应用程序库或 glassfish 库上,这些将 JUL 重定向到 SLF4J(因此在我的情况下重定向到 LOG4J)

      那么对于泽西岛,您可以执行以下操作:

      <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>
      

      最后一个配置是为了避免被其他loggers污染

      【讨论】:

        【解决方案5】:

        看起来不错的解决方案(考虑到 JUL 桥接的情况)并且对我有用,因为我只需将所有内容写入 logback.groovy 文件

        1. (如果你根本不使用logback.groovy配置或logback,当然你必须把逻辑加入某个类(例如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: http://stackoverflow.com/a/9117188/1915920
           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

        【讨论】:

        • 你太棒了!! ...谢谢你,这个配置真的很酷!! ;) :)
        猜你喜欢
        • 2015-09-13
        • 1970-01-01
        • 2018-06-06
        • 2014-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-31
        • 2012-08-08
        相关资源
        最近更新 更多