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

时间:2014-12-15 11:52:27

标签: java logging slf4j logback 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

2 个答案:

答案 0 :(得分:7)

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

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

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

  1. 覆盖LoggingFactory
  2. 这有点乱,因为有一些事情需要悲伤地复制:(这是我的实施:

    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<String, String> 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<String, Level> 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<ILoggingEvent> 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<String, String> 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<ILoggingEvent> 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"
    

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

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

答案 1 :(得分:5)

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

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

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

2.配置logback.xml,如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="OpLogger " class="ch.qos.logback.core.FileAppender">
    <file>/var/log/applicationname-mylogger.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- daily rollover -->
        <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
        <!-- keep 30 days' worth of history -->
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <append>false</append>
    <encoder>
        <pattern>%-5relative %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>
<appender name="classLogger" class="ch.qos.logback.core.FileAppender">
    <file>/var/log/applicationame-com.path.to.class.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- daily rollover -->
        <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
        <!-- keep 30 days' worth of history -->
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <append>false</append>
    <encoder>
        <pattern>%-5relative %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>
<logger name="mylogger">
    <level value="INFO" />
    <appender-ref ref="OpLogger" />
</logger>
<logger name="com.path.to.class">
    <level value="INFO" />
    <appender-ref ref="classLogger" />
</logger>
</configuration>

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

希望这可以帮助您解决问题。