I'm developing a desktop application with Kotlin and when configuring the log I'm not able to write each log level in a different file.
In the file build.gradle.kts
I have added the dependencies:
implementation("io.github.oshai:kotlin-logging-jvm:7.0.5")
implementation("ch.qos.logback:logback-classic:1.5.18")
And in src/main/resources
the logback.xml
file with the following content:
<configuration>
<import class="ch.qos.logback.core.ConsoleAppender"/>
<import class="ch.qos.logback.core.rolling.RollingFileAppender"/>
<import class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"/>
<import class="ch.qos.logback.classic.filter.LevelFilter"/>
<timestamp key="bySeconds" datePattern="yyyy-MM-dd HH:mm:ss.SSS"/>
<appender name="STDOUT" class="ConsoleAppender">
<encoder>
<pattern>${bySeconds} [%level] [%thread] %logger{36} %C{0}.%M\(%L\) - %msg%n</pattern>
</encoder>
</appender>
<appender name="DEBUG_FILE" class="RollingFileAppender">
<file>logs/debug.log</file>
<filter class="LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<append>true</append>
<rollingPolicy class="SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/%d{yyyy-MM-dd}/debug.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${bySeconds} [%level] [%thread] %logger{36} %C{0}.%M\(%L\) - %msg%n</pattern>
</encoder>
</appender>
<appender name="INFO_FILE" class="RollingFileAppender">
<file>logs/info.log</file>
<filter class="LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<append>true</append>
<rollingPolicy class="SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/%d{yyyy-MM-dd}/info.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${bySeconds} [%level] [%thread] %logger{36} %C{0}.%M\(%L\) - %msg%n</pattern>
</encoder>
</appender>
<appender name="WARN_FILE" class="RollingFileAppender">
<file>logs/warn.log</file>
<filter class="LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<append>true</append>
<rollingPolicy class="SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/%d{yyyy-MM-dd}/warn.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${bySeconds} [%level] [%thread] %logger{36} %C{0}.%M\(%L\) - %msg%n</pattern>
</encoder>
</appender>
<appender name="ERROR_FILE" class="RollingFileAppender">
<file>logs/error.log</file>
<filter class="LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<append>true</append>
<rollingPolicy class="SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/%d{yyyy-MM-dd}/error.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${bySeconds} [%level] [%thread] %logger{36} %C{0}.%M\(%L\) - %msg%n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT"/>
</root>
<logger level="debug" additivity="false">
<appender-ref ref="DEBUG_FILE"/>
</logger>
<logger level="info" additivity="false">
<appender-ref ref="INFO_FILE"/>
</logger>
<logger level="warn" additivity="false">
<appender-ref ref="WARN_FILE"/>
</logger>
<logger level="error" additivity="false">
<appender-ref ref="ERROR_FILE"/>
</logger>
</configuration>
If I run the application, I see that only the messages are printed on the console and not in the files. I see that the folder is created with the files of each level, but without content. I see this message on the console:
09:52:29,394 |-INFO in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Attaching appender named [STDOUT] to Logger[ROOT]
09:52:29,396 |-ERROR in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Could not find an AppenderAttachable at the top of execution stack. Near <appender-ref> at line 89
09:52:29,396 |-ERROR in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Could not find an AppenderAttachable at the top of execution stack. Near <appender-ref> at line 93
09:52:29,396 |-ERROR in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Could not find an AppenderAttachable at the top of execution stack. Near <appender-ref> at line 97
09:52:29,396 |-ERROR in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Could not find an AppenderAttachable at the top of execution stack. Near <appender-ref> at line 101
09:52:29,396 |-INFO in ch.qos.logback.core.model.processor.DefaultProcessor@3c9d0b9d - End of configuration.
These errors correspond to this section of the configuration file:
<logger level="debug" additivity="false">
<appender-ref ref="DEBUG_FILE"/>
</logger>
<logger level="info" additivity="false">
<appender-ref ref="INFO_FILE"/>
</logger>
<logger level="warn" additivity="false">
<appender-ref ref="WARN_FILE"/>
</logger>
<logger level="error" additivity="false">
<appender-ref ref="ERROR_FILE"/>
</logger>
However, if I put those inside
<root>
<appender-ref ref="STDOUT"/>
</root>
it writes to the files, but all messages of all levels.
I have gone through several posts on the internet and questions on this site, the configuration file is a collection of what I have been finding but even reading the Logback documentation, I can't find where I have the problem.
What am I doing wrong?
I have found the problem. As I don't define loggers by classes or packages, I had to put them in the global (<root>
) since in each <appender>
I have the filtering of the level I want.
So, the change to make is to delete all the <logger>
and add the <appender-ref>
inside <root>
, defining a minimun level for the <root>
:
...............
<root level="debug">
<appender-ref ref="STDOUT"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>
And now each log level goes into its own file.
To test it, in the code I have:
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
fun main() = application {
logger.debug { "Debug message" }
logger.info { "Info message" }
logger.warn { "Warn message" }
logger.error { "Error message" }
}