3 분 소요


What is a Log?

  • Simply put, a log is a continuous record of data
  • When first learning programming, most people frequently use System.out.print
  • During program execution, something gets printed to the console - these outputs can serve as logs

Logging Framework

  • While you can use System.out.print for debugging, this approach becomes very inefficient as your application grows larger
  • Planning ahead for convenient log viewing after development is one way to create and maintain good programs

Slf4j

  • As requirements for logging and tracing increased, logging frameworks emerged to address these needs
  • Among Spring’s logging frameworks, Slf4j is the most popular library
  • Using Slf4j, you can easily swap and use implementations like logback, log4j, and log4j2

Let’s Apply Logging

  • The development environment is as follows:
    • SpringBoot
    • Kotlin
    • Gradle

Dependency Configuration (build.gradle.kts)

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.7.1"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.spring") version "1.6.21"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.springframework.boot:spring-boot-starter-log4j2")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

configurations.forEach {
    it.exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging")
    it.exclude(group = "org.apache.logging.log4j", module = "log4j-to-slf4j")
}


tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

log4j2 Configuration

  • You can configure log4j2 using XML configuration or Jackson-based configuration
  • Create an xml file under SpringBoot’s resource folder
  • Save the created xml file as log4j2.xml and add the following content:
  <?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="INFO">
    <Properties>
        <Property name="LOG_PATTERN">%d{HH:mm:ss.SSSZ} [%t] %-5level %logger{36} - %msg%n</Property>
    </Properties>
    <Appenders>
        <Console name="ConsoleLog" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="ConsoleLog"/>
            <AppenderRef ref="FileLog"/>
        </Root>
    </Loggers>
</Configuration>
  • Configuration: The top-level element for log configuration
    • status attribute: Sets the logging level for Log4j2’s internal operations (used when logging is needed to resolve internal log4j issues)
  • Properties: Defines variables to be used in the configuration below
    • name: In the example above, name=”LOG_PATTERN” defines a variable called LOG_PATTERN
    • Appenders: Where logs are output
    • Console: Settings for logs output to the console
      • name: Name of the appender
      • target: Log target (default: SYSTEM_OUT)
    • PatternLayout: Sets the log pattern
  • Loggers: The entity responsible for logging operations, allowing various settings per package
    • Root: General log policy settings for logging across all packages (only one can be set)
      • AppenderRef: References the Appender configured above

Writing TestController

  • Let’s write a TestController and output logs from that controller
  • In Java, you can use log directly after importing Slf4j, but in Kotlin, you need to declare it as shown below
  • There are various ways to configure Logger in Kotlin, so I’ve included a helpful reference link below
package com.example.springkotlinloggingsample

import lombok.extern.slf4j.Slf4j
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping

@Slf4j
@Controller
class TestController {
    val logger: Logger = LoggerFactory.getLogger(TestController::class.java)

    @GetMapping("/")
    fun index(): String {
        logger.info("Hello, This is INFO Message")
        logger.debug("Hello, This is DEBUG Message")
        logger.trace("Hello, This is TRACE Message")
        logger.warn("Hello, This is WARN Message")
        logger.error("Hello, This is ERROR Message")
        return "index"
    }
}
  • Now when you output logs through an API call, you’ll see logs like this:
     [http-nio-8080-exec-2] INFO  com.example.springkotlinloggingsample.TestController - Hello, This is INFO Message
     [http-nio-8080-exec-2] WARN  com.example.springkotlinloggingsample.TestController - Hello, This is WARN Message
     [http-nio-8080-exec-2] ERROR com.example.springkotlinloggingsample.TestController - Hello, This is ERROR Message
    
    • TRACE and DEBUG are not displayed
    • Logging has levels, and TRACE and DEBUG require additional configuration to lower the log level to be visible
    • The log levels are as follows:
      TRACE > DEBUG > INFO > WARN > ERROR
      

Save Logs to Files (Modify log4j2.xml)

  • You can see that logs are being output in the format configured in log4j2.xml
  • Since logs should be retrievable even if the server goes down, many people use methods to send logs to other platforms
  • In this example, we’ll simply save logs to “files”
  • Modify log4j2.xml as follows:

    <?xml version="1.0" encoding="UTF-8" ?>
    <Configuration status="INFO">
        <Properties>
            <Property name="LOG_PATTERN">%d{HH:mm:ss.SSSZ} [%t] %-5level %logger{36} - %msg%n</Property>
        </Properties>
        <Appenders>
            <Console name="ConsoleLog" target="SYSTEM_OUT">
                <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
            </Console>
            <RollingFile name="FileLog"
                         fileName="./logs/spring.log"
                         filePattern="./logs/spring-%d{yyyy-MM-dd}-%i.log">
                <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8" />
                <Policies>
                    <TimeBasedTriggeringPolicy interval="1" />
                    <SizeBasedTriggeringPolicy size="10000KB" />
                </Policies>
                <DefaultRolloverStrategy max="20" fileIndex="min" />
            </RollingFile>
        </Appenders>
        <Loggers>
            <Root level="info">
                <AppenderRef ref="ConsoleLog" />
                <AppenderRef ref="FileLog" />
            </Root>
        </Loggers>
    </Configuration>
    
    • RollingFile: Configure log output to files based on conditions

      • name: Name of the appender
      • fileName: File name including path
      • filePattern: File name pattern including path based on rolling conditions
      • Policies: File rolling policies
        • TimeBasedTriggeringPolicy: Records logs to a new file on a daily basis (interval=1)
        • SizeBasedTriggeringPolicy: Creates a new file when the file size exceeds the limit
        • DefaultRolloverStrategy: Sets the maximum number of files that can be created when file capacity is exceeded
    • After configuring as above, a logs folder will be created under springboot and log files will accumulate there

댓글남기기