Мост с июля по SLF4J
В настоящее время я наблюдаю, что сторонняя библиотека (а именно restfb) использует java.util.logging, и я вижу, что эти журналы заканчиваются в STDOUT, даже если у меня не настроен консольный приложение SLF4J в моем logback.xml. У меня также есть мост jul-to-slf4j в моем классе. Мост jul-to-slf4j только регистрирует к appenders, настроенным logback, когда мост установлен, или это также ведет к stdout?
6 ответов
Вам нужно позвонить SLF4JBridgeHandler.install()
, Вам также необходимо включить все уровни журналов в корневом логгере (причина приведена ниже в выдержке) в java.util.logging и удалить консольный appender по умолчанию.
Этот обработчик перенаправит регистрацию jul на SLF4J. Однако, только журналы, включенные в jul, будут перенаправлены. Например, если оператор журнала, вызывающий jul 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
во-первых (по причине, указанной выше в выдержке).
Как упомянуто в javadocs для SLF4JBridgeHandler, вы можете либо установить 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
Чтобы иметь журналы драйверов достаточно:
Добавьте мост jul-to-slf4j:
<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://stackru.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)
(оставил файл завершенным, так как он дает лучшее представление о том, как все можно настроить или как начальный шаблон)
(может иметь отношение к кому-либо - мой env: slf4j 1.7.5, logback 1.1.2, groovy 2.1.9)
В дополнение к командам настройки, представленным в ответе Dev . В коде, с которым я работаю, много сообщений уровня трассировки (FINEST), обернутыхisLoggable
проверка, сгенерированная сторонними библиотеками. Так что включение всех уровней безоговорочно не выглядит хорошей идеей.
Я использую SLF4J Simple Logger, и у него нет средств для динамического изменения уровней ведения журнала. Поэтому я добавил следующий фрагмент кода для автоматической настройки уровней ведения журнала Java на основе простой конфигурации регистратора slf4j, передаваемой через свойства системы:
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();
}
}
PS этот фрагмент не обрабатывает настройки Simple Logger, определенные в файле simplelogger.properties в вашем пути к классам. Предполагая, что они статичны, и при необходимости можно выполнить соответствующую настройку JUL.
The LevelChangePropagator
доступный в Logback, в основном делает то же самое, но динамически отслеживает изменения уровня.