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?
source to share
No one has answered this question yet
See similar questions:
or similar: