7 분 소요


로그란?

  • 로그는 간단하게 말해서 연속된 데이터의 기록이라고 할 수 있다
  • 일반적으로 처음 프로그래밍을 배울 때는 보통 System.out.print 사용을 많이한다
  • 이 때, 프로그램이 실행되면서 콘솔에 무엇인가가 출력되는데, 이런 것들이 로그가 될 수 있다

Logging Framework

  • System.out.print를 사용하여 디버깅을 할 수 있지만, 만약 어플리케이션의 사이즈가 커지게 되면 이런 방식은 너무 비효율적이다
  • 개발 후에 로그를 편하게 볼 수 있는 방법을 고려 해 놓는것도 좋은 프로그램을 개발하고 유지하기위한 방법이다

Slf4j

  • 로그를 남기고 추적하는 요구사항이 많이 생겨, 이와같은 요구사항들을 해소하고자 Loggin Framework이 생겼다
  • 스프링의 Logging Framework 중 가장 유명한 라이브러리는 Slf4j 이다
  • Slf4j를 사용하면 logback, log4j, log4j2 와 같은 구현체를 쉽게 교체하고 사용할 수 있다

로깅을 적용 해 보자

  • 개발환경은 다음과 같다
    • SpringBoot
    • Kotlin
    • Gradle

의존성 설정 (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 설정

  • xml 설정방법과 Jackson을 사용한 설정방법을 사용하여 log4j2 설정을 할 수 있다
  • Springboot의 resource 폴더 하위에 xml파일을 생성하자
  • 생성한 xml파일을 log4j2.xml로 저장하고, 아래의 내용을 적어준다
  <?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: 로그 설정을 위한 최상위 요소
    • status 속성: Log4j2 내부의 동작에 대한 로깅 레벨을 설정 (log4j 내부 문제를 해결하기 위한 용도의 로깅이 필요한 경우 사용)
  • Properties: 하단 설정에 사용할 변수들을 정의
    • name: 위 예제에서 name=”LOG_PATTERN”으로 설정하여 LOG_PATTERN이라는 변수를 정의
    • Appenders: 로그가 출력되는 위치
    • Console: 콘솔에 출력될 로그 설정
      • name: 어펜더의 이름
      • target: 로그 타겟 (default: SYSTEM_OUT)
    • PatternLayout: 로그의 패턴을 설정
  • Loggers: 로깅 작업의 주체로 각 패키지 별로 다양한 설정을 할 수 있음
    • Root: 모든 패키지에 대한 로깅을 하기 위한 일반적인 로그 정책 설정 (한 개만 설정할 수 있음)
      • AppenderRef: 상단에 설정한 Appender를 참조

TestController 작성

  • TestController를 작성하고, 해당 컨트롤러에서 Log를 출력하는 작업을 하자
  • Java에서는 Slf4j를 import하면, log를 바로 사용할 수 있지만 코틀린에서는 아래와같이 선언을 해 주어야한다.
  • Kotlin에서 Logger를 설정하는방법은 여러가지가 있기 때문에 참고하면 좋은 링크를 아래에 넣는다
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"
    }
}
  • 이제 api 호출을 통해, log를 출력 하면 아래와같이 로그가 출력되는것을 볼 수 있다
     [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와 DEBUG는 출력되지 않는다
    • Logging의 레벨이 있는데, TRACE와 DEBUG는 로그레벨을 낮추는 설정을 추가로 해야 볼 수 있다
    • 로그의 레벨은 다음과 같다
      TRACE > DEBUG > INFO > WARN > ERROR
      

로그를 파일로 남기자 (log4j2.xml을 수정하자)

  • 로그가 log4j2.xml에서 설정한 형태로 잘 출력이 되는 것을 볼 수 있다
  • 로그는 서버가 끊기더라도 다시 찾아 볼 수 있어야하기 떄문에, 기타 다른 플랫폼으로도 보내어 로그를 확인하는 방법들을 많이 사용한다
  • 이번 예제에서는 간단하기 “파일”로만 남기는 작업을 해 보려고 한다
  • log4j2.xml을 아래와같이 수정한다

    <?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: 조건에 따라 파일에 로그를 출력하도록 설정

      • name: 어펜더의 이름
      • fileName: 경로를 포함한 파일 이름
      • filePattern: 롤링 조건에 따른 경로를 포함한 파일 이름 패턴
      • Policies: 파일 롤링 정책
        • TimeBasedTriggeringPolicy: 1일 단위(interval=1)로 새로운 파일에 로그를 기록
        • SizeBasedTriggeringPolicy: 파일 사이즈를 기준으로 용량이 넘칠 경우 다음 파일을 생성하여 기록
        • DefaultRolloverStrategy: 파일 용량 초과 시 생성될 수 있는 파일의 최대 개수 설정
    • 위와같이 설정하고나면, log파일이 springboot 아래의 logs 폴더가 생성되며 쌓이게 된다

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

댓글남기기