How to log to different log files at the same time without duplication using Logback / slf4j

Background

I am using slf4j with Logback as my main logger.

The system (i.e., a group of interacting classes) can be used simultaneously by the client. Each call must have its own log file. The log file is provided by the client.

I want to

  • have a separate log file per system call

  • each call should log in its own log file, but not the other

  • The system itself has several parallel executions

  • The client can simultaneously call the system several times

I can programmatically configure the log file, similar to Logback - programmatically specify the name of the log file .

Problem

If the system is called twice, the first log file is still associated with the file application, resulting in the second call being written to both log files . I can call detachAndStopAllAppenders

in the log, but this results in a potentially concurrent call to lose the appender.

When adding a new File Appender without detaching previously attached applications:

Subsequent log statements are written to the new file as well as previously attached files.

When detaching File Appenders and adding a new File Appender:

The already running system is now written to a new log file instead of the actual log file.

Origin of the problem

If I am not mistaken, the source of the problem is static Loggerfactory

, i.e. because the system gets the registrar by calling LoggerFactory.getLogger(getClass)

, the same registrar is returned by different calls. Is there a non-static equivalent Loggerfactory

? Or how can I resolve the problem otherwise?

An ugly solution

While writing MWE, I came up with an ugly solution that seems to work. However, this solution requires synchronizing every log call, detaching the old application, adding a new application, and doing the actual logging. Here's the code in Scala:

package samples

import java.nio.file.{Files, Path}

import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.rolling.{SizeBasedTriggeringPolicy, FixedWindowRollingPolicy, RollingFileAppender}
import org.slf4j.LoggerFactory
import org.slf4j.helpers.SubstituteLogger

object SampleLogging {

  def main(args: Array[String]) {
    val sys1 = new Thread(new TheSystem(Files.createTempFile("sys1_",".log")))
    val sys2 = new Thread(new TheSystem(Files.createTempFile("sys2_",".log")))
    sys1.start()
    sys2.start()
    sys1.join()
    sys2.join()
    println("finished")
  }

  class TheSystem(logFile: Path) extends Runnable {
    def run() {
      val logger = new Logger(logFile)
      for (i <- 1 to 1000){
        logger.info(getClass)("Logging to file " + logFile)
      }
    }
  }

  class Logger(logFile: Path) {

    def info(context: Class[_])(msg: String){
      var slf4j = LoggerFactory.getLogger(context)
      while(slf4j.isInstanceOf[SubstituteLogger]){
        slf4j = LoggerFactory.getLogger(context)
      }
      slf4j match {
        case logback: ch.qos.logback.classic.Logger =>
          val appenderConfig = new RollingFileAppenderConfiguration(LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext])
          logback.synchronized {
            logback.detachAndStopAllAppenders()
            logback.addAppender(appenderConfig.appender)
            logback.info(msg)
          }
      }
    }

    private class RollingFileAppenderConfiguration(context: LoggerContext) {

      val appender: RollingFileAppender[ILoggingEvent] = {
        val rollingFileAppender = new RollingFileAppender[ILoggingEvent]
        rollingFileAppender.setFile(logFile.toAbsolutePath.toString)
        rollingFileAppender.setRollingPolicy(rollingPolicy(rollingFileAppender))
        rollingFileAppender.setTriggeringPolicy(triggeringPolicy)
        rollingFileAppender.setEncoder(encoder)
        rollingFileAppender.setContext(context)
        rollingFileAppender.start()
        rollingFileAppender
      }

      private def rollingPolicy(appender: => RollingFileAppender[ILoggingEvent]) = {
        val fixedWindowRollingPolicy = new FixedWindowRollingPolicy
        fixedWindowRollingPolicy.setFileNamePattern(s"log/${logFile.getFileName}.%i.log.zip")
        fixedWindowRollingPolicy.setMinIndex(1)
        fixedWindowRollingPolicy.setMaxIndex(3)
        fixedWindowRollingPolicy.setParent(appender)
        fixedWindowRollingPolicy.setContext(context)
        fixedWindowRollingPolicy
      }

      private def triggeringPolicy = {
        val sizeBasedTriggeringPolicy = new SizeBasedTriggeringPolicy[ILoggingEvent]
        sizeBasedTriggeringPolicy.setMaxFileSize("5MB")
        sizeBasedTriggeringPolicy.setContext(context)
        sizeBasedTriggeringPolicy
      }

      private def encoder = {
        val patternLayoutEncoder = new PatternLayoutEncoder
        patternLayoutEncoder.setPattern("%date{YYYY-MM-dd HH:mm:ss} %level [%thread] %logger{10} [%file:%line] %msg%n")
        patternLayoutEncoder.setContext(context)
        patternLayoutEncoder.start()
        patternLayoutEncoder
      }
    }
  }
}

      

I have a feeling that this over-locking is very bad for performance. Am I wrong? Can I achieve the same result without over-locking?

+3


source to share





All Articles