2014-12-15 13 views
6

Ich habe eine dropwizard App-Datei, wo ich Logger Appen konfiguriert Datei wie folgt:Dropwizard loggt nicht benutzerdefinierte Logger

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 

Und erstellt Logger in meiner App:

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory;  

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

Testprotokollierung main():

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

Die Anwendung startet und läuft ohne Probleme; aber ich bekomme keine Protokolle (außer natürlich für Jetty-Zugriffsprotokolle, die korrekt in mylogs.log gedruckt sind), weder in stdout noch in mylogs.log. Wenn ich stattdessen die Logger-Konfiguration in configuration.yml lösche, werden alle Logs auf stdout gedruckt. Vielleicht ist es ein Problem von Dropwizard oder ich muss etwas zu configuration.yml hinzufügen? Ich benutze Dropwizard 0.8.0

+0

Sie haben bereits die Standard-Logging-Ebene INFO, die Zuweisung von INFO zu bestimmten Klassen ist an dieser Stelle überflüssig. Nur zu deiner Information. – Natan

Antwort

7

UPDATE Die neueste Version von dropwizard unterstützt Logging-Konfigurationen aus der Box

ich in die gleiche Ausgabe lief versucht Dropwizard (0.8.4) mit einem separaten Datei einzurichten. Ich bin auf dasselbe Problem gestoßen. Also habe ich ein bisschen tiefer gegraben und eine Lösung für mich gefunden (nicht die sauberste, aber ich konnte nicht anders funktionieren).

Das Problem ist, dass LoggingFactory#configure automatisch jeden Appender zu Root hinzugefügt. Dies ist nicht sehr ideal, also musste es überschrieben werden. Was ich tat, war:

  1. LoggingFactory überschreiben.

Dies ist etwas chaotisch, da es ein paar Dinge, die leider :(Hier kopiert werden müssen, ist meine Implementierung:

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

} 

Wie Sie sich, ich leider ein paar haben kopieren/einfügen private Mitglieder und Methoden, um die Dinge funktionieren wie beabsichtigt

ich ein neues Feld hinzugefügt:.

@JsonProperty("loggerMapping") 
private ImmutableMap<String, String> loggerMappings; 

Dies ermöglicht es mir, eine ma zu konfigurieren für jeden Logger. Dies war nicht out of the Box erlaubt, da ich einen Namen nicht bekommen kann (Dropwizard defaults die Appender-Namen, sehr unbequem ...)

Also habe ich einen neuen Logger hinzugefügt, der in meinem Fall auch Hostname Substitution, die ich aus verschiedenen Gründen benötigt. Dazu überschreibe ich die gute alte FileAppenderFactory und implementiere meine eigene Schnittstelle MappedLogger. Die Umsetzung hier:

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

Bitte beachten, dass, um einen neuen json Typ hinzugefügt werden, werden Sie die JavaDoc in AppenderFactory (Add Meta-inf auf dem Classpath und machen den neuen appender Erkennbar)

folgen müssen So weit, so gut, wir haben jetzt eine Konfiguration, die Logger-Mappings aufnehmen kann, wir haben einen Logger, der einen optionalen Namen annehmen kann.

Im configure Verfahren binde ich jetzt die beiden zusammen:

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

Für Rückwärtskompatibilität hielt ich das Standardverhalten. Wenn kein Name definiert ist, wird der Appender zum Root-Logger hinzugefügt. Ansonsten behebe ich den getippten Logger und füge den Appender nach Wunsch hinzu.

Und nicht zuletzt die gute alte yaml config:

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" 

Wie Sie sehen, ich bin die Abbildung der Ereignisse appender an den Logger Ereignisse. Auf diese Weise landen alle meine Ereignisse in Datei A, während die anderen Informationen woanders landen.

Ich hoffe, das hilft. Könnte nicht die sauberste Lösung sein, aber ich denke nicht, dass Dropwizard diese Funktion derzeit erlaubt.

+0

Hey - wäre es möglich für dich auch die 'imports' in dein Beispiel aufzunehmen? Vielen Dank. – Ztyx

+0

Sicher Sache. Habe es gerade aktualisiert. – pandaadb

+0

Ehrfürchtig. Vielen Dank! – Ztyx

5

Sie können separate Logger mit dem Dropwizard mit Logback implementieren.

1.Konfigurieren Sie den Logger in Ihrer Anwendungsklasse (d. H. Anwendungsstartpunkt mit Hauptmethode) wie unten.

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

2.Konfigurieren Sie logback.xml wie unten.

<?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.Now Verwendung Logger

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

EDIT:

Ich versuche haben den gleichen Logger in meinem Beispiel Projekt umzusetzen. Es funktioniert in meinem Fall gut. Wir können den LOGGER nicht verwenden, bevor die Dropwizard-Anwendung initialisiert wird. Die Dropwizard initialisiert nur, wenn Sie

new ExampleApplication().run(args); 

So nennen, wenn Logger verwendet wird, bevor Dropwizard initialisiert, Ihr Protokoll wird nicht da sein. Ich habe versucht, Szenario mit Hauptmethode zu implementieren. Die erste Protokollanweisung wird nicht gedruckt, da wir vor der Initialisierung des Dropwizard den Protokollierer verwendet haben, aber die zweite Protokollanweisung wird gedruckt.

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

hoffe, dies wird Ihnen Ihr Problem lösen helfen.

+0

Danke für Ihre Antwort; aber ich habe bereits Dropwizard, der Logback eingebettet hat; sollte ich nicht nur die Datei configuration.yml konfigurieren? –

+0

Ja, Dropwizard verwendet das Logbuch intern. Können Sie Ihren Hauptmethodencode dort veröffentlichen, wo Sie versuchen, den Logger zu verwenden und zu einer neuen ExampleApplication() aufzurufen. Run (args); ? Siehe auch meine bearbeitete Antwort. –