我目前正在观察第 3 方库(即 restfb)正在使用 java.util.logging,即使我的 logback.xml 中没有配置 SLF4J 控制台附加程序,我也看到这些日志最终出现在 STDOUT 中。我的类路径中还有jul-to-slf4j桥。jul-to-slf4j 网桥是否仅在安装网桥时记录到由 logback 配置的附加程序,还是也记录到标准输出?
5 回答
你需要打电话SLF4JBridgeHandler.install()
。您还需要在 java.util.logging 的根记录器中启用所有日志级别(原因在下面的摘录中),并删除默认的控制台附加程序。
这个处理程序会将 jul 日志重定向到 SLF4J。但是,只有在 7 月启用的日志才会被重定向。例如,如果调用 jul 记录器的日志语句禁用该语句,根据定义,将不会到达任何 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
(出于上述摘录中提到的原因)。
如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-slf4j桥的部分讨论了这个问题。本质上,由于您已经在使用 logback,因此无论负载如何,启用LevelChangePropagator都应该会产生良好的性能。
我使用 SLF4J 和新的 Postgres 驱动程序 42.0.0
根据changelog它使用 java.util.logging
拥有驱动程序日志就足够了:
-
<dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>${slf4j.version}</version> <scope>runtime</scope> </dependency>
添加 logback.xml (logback-test.xml)
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"> <resetJUL>true</resetJUL> </contextListener> <appender ... <logger name="org.postgresql" level="trace"/>
添加代码
static { SLF4JBridgeHandler.install(); }
我的解决方案:
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>
最后一个配置是为了避免被其他记录器污染
看起来不错的解决方案(考虑到 JUL 桥接的情况)并且对我有用,因为我只需将所有内容都写在logback.groovy文件中。
(如果您根本不使用logback.groovy配置或logback,当然您必须将逻辑部分放入某个类(例如
class MyApp { static { /* log init code here */ } ... }
)。)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)