kotlinlogginglogback

Configure Logback to write each log level to a separate file


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?


Solution

  • 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" } 
    }