Dropwizard不会将自定义记录器记录到文件中

我有一个dropwizard应用程序,我将logger appender配置为文件,如下所示:

logging: level: INFO loggers: "mylogger": INFO "com.path.to.class": INFO appenders: - type: file currentLogFilename: .logs/mylogs.log archivedLogFilenamePattern: .logs/archive.%d.log.gz archivedFileCount: 14 

并且,在我的应用中创建了记录器:

 import org.slf4j.Logger; import org.slf4j.LoggerFactory; private final Logger OpLogger = LoggerFactory.getLogger("mylogger"); (and) private final Logger ClassLogger = LoggerFactory.getLogger(pathToClass.class); 

在main()中进行一些测试记录:

 OpLogger.info("test 1"); ClassLogger.info("test 2); 

应用程序启动并运行没有问题; 但我没有得到任何日志(除了Jetty访问日志,当然,正确打印到mylogs.log),无论是在stdout还是在mylogs.log文件中。 相反,如果我删除configuration.yml中的记录器配置,我会将所有日志打印到stdout。 也许这是dropwizard的问题,或者我必须在configuration.yml中添加一些东西? 我正在使用Dropwizard 0.8.0

更新最新版本的dropwizard支持开箱即用的日志配置

我遇到了同样的问题,尝试使用单独的文件设置Dropwizard(0.8.4)。 我遇到了同样的问题。 所以我挖得更深一些,为我找到了一个解决方案(不是最干净但我似乎无法以不同的方式工作)。

问题是LoggingFactory#configure自动将每个appender添加到root。 这不是很理想,所以需要覆盖。 我做的是:

  1. 覆盖LoggingFactory

这有点凌乱,因为有一些事情需要悲伤地复制:(这是我的实现:

 import java.io.PrintStream; import java.lang.management.ManagementFactory; import java.util.Map; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.logback.InstrumentedAppender; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.PatternLayout; import ch.qos.logback.classic.jmx.JMXConfigurator; import ch.qos.logback.classic.jul.LevelChangePropagator; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.util.StatusPrinter; import io.dropwizard.logging.AppenderFactory; import io.dropwizard.logging.LoggingFactory; public class BetterDropWizardLoggingConfig extends LoggingFactory { @JsonIgnore final LoggerContext loggerContext; @JsonIgnore final PrintStream configurationErrorsStream; @JsonProperty("loggerMapping") private ImmutableMap loggerMappings; private static void hijackJDKLogging() { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); } public BetterDropWizardLoggingConfig() { PatternLayout.defaultConverterMap.put("h", HostNameConverter.class.getName()); this.loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); this.configurationErrorsStream = System.err; } private Logger configureLevels() { final Logger root = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); loggerContext.reset(); final LevelChangePropagator propagator = new LevelChangePropagator(); propagator.setContext(loggerContext); propagator.setResetJUL(true); loggerContext.addListener(propagator); root.setLevel(getLevel()); for (Map.Entry entry : getLoggers().entrySet()) { loggerContext.getLogger(entry.getKey()).setLevel(entry.getValue()); } return root; } @Override public void configure(MetricRegistry metricRegistry, String name) { hijackJDKLogging(); final Logger root = configureLevels(); for (AppenderFactory output : getAppenders()) { Appender build = output.build(loggerContext, name, null); if(output instanceof MappedLogger && ((MappedLogger) output).getLoggerName() != null) { String appenderName = ((MappedLogger) output).getLoggerName(); String loggerName = loggerMappings.get(appenderName); Logger logger = this.loggerContext.getLogger(loggerName); logger.addAppender(build); } else { root.addAppender(build); } } StatusPrinter.setPrintStream(configurationErrorsStream); try { StatusPrinter.printIfErrorsOccured(loggerContext); } finally { StatusPrinter.setPrintStream(System.out); } final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { final ObjectName objectName = new ObjectName("io.dropwizard:type=Logging"); if (!server.isRegistered(objectName)) { server.registerMBean(new JMXConfigurator(loggerContext, server, objectName), objectName); } } catch (MalformedObjectNameException | InstanceAlreadyExistsException | NotCompliantMBeanException | MBeanRegistrationException e) { throw new RuntimeException(e); } configureInstrumentation(root, metricRegistry); } private void configureInstrumentation(Logger root, MetricRegistry metricRegistry) { final InstrumentedAppender appender = new InstrumentedAppender(metricRegistry); appender.setContext(loggerContext); appender.start(); root.addAppender(appender); } } 

正如你可以看到的,我遗憾地不得不复制/粘贴一些私有成员和方法,以使事情按预期工作。

我添加了一个新字段:

 @JsonProperty("loggerMapping") private ImmutableMap loggerMappings; 

这允许我为每个记录器配置映射。 这不是开箱即用的,因为我无法得到一个名字(dropwizard默认了appender名称,非常不方便……)

所以我添加了一个新的Logger,在我的例子中也进行了主机名替换,这是我出于不同的原因所需要的。 为此,我覆盖了旧的FileAppenderFactory并实现了我自己的MappedLogger接口。 在这里实施:

 import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.FileAppender; import ch.qos.logback.core.rolling.RollingFileAppender; import io.dropwizard.logging.AppenderFactory; import io.dropwizard.logging.FileAppenderFactory; @JsonTypeName("hostnameFile") public class HostnameFileAppender extends FileAppenderFactory implements AppenderFactory, MappedLogger { private static String uuid = UUID.randomUUID().toString(); @JsonProperty private String name; public void setCurrentLogFilename(String currentLogFilename) { super.setCurrentLogFilename(substitute(currentLogFilename)); } private String substitute(final String pattern) { String substitute = null; try { substitute = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { System.err.println("Failed to get local hostname:"); e.printStackTrace(System.err); substitute = uuid; System.err.println("Using " + substitute + " as fallback."); } return pattern.replace("${HOSTNAME}", substitute); } @Override public void setArchivedLogFilenamePattern(String archivedLogFilenamePattern) { super.setArchivedLogFilenamePattern(substitute(archivedLogFilenamePattern)); } @Override public String getLoggerName() { return name; } } 

请注意,为了添加新的json类型,您必须遵循AppenderFactory的JavaDoc(将Meta-inf添加到类路径并使新的appender可被发现)

到目前为止,我们现在有了一个可以接收记录器映射的配置,我们有一个可以选择名称的记录器。

在配置方法中,我现在将这两者结合在一起:

 for (AppenderFactory output : getAppenders()) { Appender build = output.build(loggerContext, name, null); if(output instanceof MappedLogger && ((MappedLogger) output).getLoggerName() != null) { String appenderName = ((MappedLogger) output).getLoggerName(); String loggerName = loggerMappings.get(appenderName); Logger logger = this.loggerContext.getLogger(loggerName); logger.addAppender(build); } else { root.addAppender(build); } } 

为了向后兼容,我保留了默认行为。 如果没有定义名称,则将appender添加到根记录器。 否则我解析输入的记录器,并根据需要添加appender。

最后但并非最不重要的是旧的yaml配置:

 logging: # The default level of all loggers. Can be OFF, ERROR, WARN, INFO, DEBUG, TRACE, or ALL. level: INFO loggers: "EVENT" : INFO loggerMapping: # for easier search this is defined as: appenderName -> loggerName rather than the other way around "eventLog" : "EVENT" appenders: - type: console threshold: ALL logFormat: "myformat" - type: hostnameFile # NOTE THE NEW TYPE WITH HOSTNAME RESOLVE currentLogFilename: /Users/artur/tmp/log/my-${HOSTNAME}.log threshold: ALL archive: true archivedLogFilenamePattern: mypattern archivedFileCount: 31 timeZone: UTC logFormat: "myFormat" - type: hostnameFile name: eventLog # NOTE THE APPENDER NAME currentLogFilename: something threshold: ALL archive: true archivedLogFilenamePattern: something archivedFileCount: 31 timeZone: UTC logFormat: "myFormat" - type: hostnameFile currentLogFilename: something threshold: ERROR archive: true archivedLogFilenamePattern: something archivedFileCount: 31 timeZone: UTC logFormat: "myFormat" 

如您所见,我将事件appender映射到事件记录器。 这样我的所有事件都会在文件A中结束,而其他信息最终会在其他地方结束。

我希望这有帮助。 可能不是最干净的解决方案,但我不认为Dropwizard目前允许此function。

您可以使用logback实现与dropwizard的单独记录器。

1.在Application类中配置记录器(即使用main方法的应用程序起始点),如下所示。

 LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory(); context.reset(); ContextInitializer initializer = new ContextInitializer(context); initializer.autoConfig(); 

2.配置如下的logback.xml。

    /var/log/applicationname-mylogger.log   logFile.%d{yyyy-MM-dd}.log  30  false  %-5relative %-5level %logger{35} - %msg%n    /var/log/applicationame-com.path.to.class.log   logFile.%d{yyyy-MM-dd}.log  30  false  %-5relative %-5level %logger{35} - %msg%n            

3.现在使用记录器

 static final Logger OpLogger = LoggerFactory.getLogger("mylogger"); static final Logger classLogger = LoggerFactory.getLogger("com.path.to.class"); 

编辑:

我试图在我的示例项目中实现相同的记录器。 它在我的情况下工作正常。 Dropwizard应用程序初始化之前我们无法使用LOGGER。 只有在你打电话时才会初始化Dropwizard

  new ExampleApplication().run(args); 

因此,如果在Dropwizard初始化之前使用了logger,那么您的日志就不会存在。 我试图用main方法实现方案。 由于我们在Dropwizard初始化之前使用了logger,因此不会打印第一个日志语句,但会打印第二个日志语句。

  OpLogger.info("test 1"); new ExampleApplication().run(args); ClassLogger.info("test 2); 

希望这能帮助您解决问题。