Spring Boot 로깅 (2) - logback-spring.xml 설정 및 Grafana Loki 연동

이번글에서는 Logback 아키텍처를 이해하고 `logback-spring.xml` 파일 작성하는 방법을 정리하려 합니다. 그리고 최종적으로 Grafana Loki로 연동하여 수집된 로그가 모니터링 도구에서 어떻게 표시되는지까지 확인해보려고 합니다. 이전에는 AI의 도움을 받아 설정 파일을 급하게 작성한 적이 있었는데 앞으로는 직접 작성할 수 있으면 좋을 것 같고, Grafana Loki에 연동했을 때 대시보드에 어떻게 로그가 보이는지 직접 살펴보는 것이 목표입니다.

 

실습을 진행한 환경은 윈도우 11이며 아래 링크는 제가 실습을 진행하며 사용한 Spring Boot 프로젝트 입니다.

 

java-play-ground/practice_logging at main · HeeChanN/java-play-ground

java로 이것저것. Contribute to HeeChanN/java-play-ground development by creating an account on GitHub.

github.com

 

 

1. Logback 아키텍쳐

Logback은 크게 Logger, Appender, Layout 세 가지 구성요소로 이루어져 있다. 앞으로의 내용에서 이해하기 위해 간단히 개념만 살펴보자.


`Logger`는 애플리케이션 코드가 직접 호출하는 로깅의 진입점이다.

`Appender`는 로그 이벤트를 최종 목적지(콘솔, 파일, 외부 수집 시스템 등)로 전달하는 출력기다. 하나의 Logger에 여러 Appender를 연결해 동시에 여러 대상으로 로그 내용을 전송할 수 있다.

`Layout`은 로그 메시지의 형식(포맷)을 정의한다. 기본 패턴 외에 커스텀 클래스를 만들어 사용할 수 있다.(보통 logback-spring.xml의 encoder를 통해 포맷을 적용하는 경우가 많다.

 

개념만으로 살펴본다면 이해하기 힘드니 한번 실제 Spring Boot의 기본 설정 파일을 기준으로 살펴보자

 

2. Spring Boot에서 제공하는 기본 설정 파일 (base.xml, defaults.xml, console-appender.xml, file-appender.xml)

코드 같은 경우 Spring Boot 3.5.3 버전을 기준으로 학습했습니다. 아래 링크에서는 main 브랜치에 존재하는 xml파일들을 볼 수 있습니다.

 

spring-boot/core/spring-boot/src/main/java/org/springframework/boot/logging/logback at main · spring-projects/spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss. - spring-projects/spring-boot

github.com

 

 

`base.xml`

<?xml version="1.0" encoding="UTF-8"?>

<!--
Base logback configuration provided for compatibility with Spring Boot 1.1
-->

<included>
	<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
	<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
	<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
	<include resource="org/springframework/boot/logging/logback/file-appender.xml"/>
	<root level="INFO">
		<appender-ref ref="CONSOLE"/>
		<appender-ref ref="FILE"/>
	</root>
</included>

 

`defaults.xml`

<?xml version="1.0" encoding="UTF-8"?>

<!--
Default logback configuration provided for import
-->

<included>
	<conversionRule conversionWord="applicationName" class="org.springframework.boot.logging.logback.ApplicationNameConverter"/>
	<conversionRule conversionWord="clr" class="org.springframework.boot.logging.logback.ColorConverter"/>
	<conversionRule conversionWord="correlationId" class="org.springframework.boot.logging.logback.CorrelationIdConverter"/>
	<conversionRule conversionWord="esb" class="org.springframework.boot.logging.logback.EnclosedInSquareBracketsConverter" />
	<conversionRule conversionWord="wex" class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
	<conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

	<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}){} %clr(${PID:-}){magenta} %clr(--- %esb(){APPLICATION_NAME}%esb{APPLICATION_GROUP}[%15.15t] ${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
	<property name="CONSOLE_LOG_CHARSET" value="${CONSOLE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>
	<property name="CONSOLE_LOG_THRESHOLD" value="${CONSOLE_LOG_THRESHOLD:-TRACE}"/>
	<property name="CONSOLE_LOG_STRUCTURED_FORMAT" value="${CONSOLE_LOG_STRUCTURED_FORMAT:-}"/>
	<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:-} --- %esb(){APPLICATION_NAME}%esb{APPLICATION_GROUP}[%t] ${LOG_CORRELATION_PATTERN:-}%-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
	<property name="FILE_LOG_CHARSET" value="${FILE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>
	<property name="FILE_LOG_THRESHOLD" value="${FILE_LOG_THRESHOLD:-TRACE}"/>
	<property name="FILE_LOG_STRUCTURED_FORMAT" value="${FILE_LOG_STRUCTURED_FORMAT:-}"/>

	<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
	<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
	<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
	<logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>
	<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
	<logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>
	<logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>
	<logger name="org.springframework.boot.actuate.endpoint.jmx" level="WARN"/>
</included>

 

`console-appender.xml`

<?xml version="1.0" encoding="UTF-8"?>

<!--
Console appender logback configuration provided for import, equivalent to the programmatic
initialization performed by Boot
-->

<included>
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>${CONSOLE_LOG_THRESHOLD}</level>
		</filter>
		<encoder>
			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
			<charset>${CONSOLE_LOG_CHARSET}</charset>
		</encoder>
	</appender>
</included>

`file-appender.xml`

<?xml version="1.0" encoding="UTF-8"?>

<!--
File appender logback configuration provided for import, equivalent to the programmatic
initialization performed by Boot
-->

<included>
	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>${FILE_LOG_THRESHOLD}</level>
		</filter>
		<encoder>
			<pattern>${FILE_LOG_PATTERN}</pattern>
			<charset>${FILE_LOG_CHARSET}</charset>
		</encoder>
		<file>${LOG_FILE}</file>
		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
			<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
			<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
			<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
			<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
			<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
		</rollingPolicy>
	</appender>
</included>

 

위 파일들을 보면 Logback 아키텍처의 Appender와 Layout에 대해 이해해 볼 수 있다. Logger의 경우 우리가 일반적으로 애플리케이션 레벨에서 사용하는 코드라고 생각하면 된다.

@RestController
@Slf4j
public class TestController {

    @GetMapping("/api/error")
    public void error() {
        log.error("Error!");
    }
}

 

base.xml 같은 경우 따로 설정을 진행하지 않는다면 기본적으로 동작하는 logback 설정 파일이다. defaults.xml 파일과 2가지 Appender를 포함시키는 코드를 볼 수 있는데 defaults.xml에는 기본 설정파일들에서 사용할 기본적인 환경변수 값들이 들어가 있다.

console-appender와 file-appender의 경우 우리가 터미널에서 보던 로그 출력기와 jar로 배포했을 때 파일에 입력되던 로그 출력 형식을 맡고 있는 파일들이다. 설정 파일에는 아래와 같이 간단하게 만들어진 Appender를 가져와 사용할 수 있다. 해당 태그 내부에 encoder를 볼 수 있는데 이것이 바로 defaults.xml에 적혀있던 설정값들을 가져와 사용하는 부분이고 이 <encoder> 태그를 통틀어 Logback의 Layout이라고 부른다.

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">

 

그럼 이제 이 기본 템플릿들을 이용해 우리 프로젝트에 사용할 logback-spring.xml 파일을 만들어보자.

 

3. logback-spring.xml 작성하기 (Console 출력, 파일 출력)

먼저, 이번 실습을 진행할 때, 총 3가지 Profile을 고려해 진행하려고 합니다.

  1. 개발 환경 (dev)
  2. 테스트 환경 (staging)
  3. 운영 환경 (prod)

따라서, Logback 설정 파일에도 이런 환경에 맞게 로그를 출력하는 방식을 결정하는 Appender를 사용해야 한다.

가장 먼저 기본 설정 파일들로만 구성할 수 있는 1번과 2번 환경설정을 진행해 보자.

 

개발환경(dev)의 경우 보통 로그를 콘솔에서 보기 때문에 Console 출력으로 설정했습니다. 위에서 살펴본 ` console-appender.xml`을 사용해 간단하게 작성해 본다면 다음과 같이 작성해 볼 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <springProfile name="dev">
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>
    
</configuration>

 

Spring Boot Logging 관련 공식문서를 보면 springProfile에 대한 설명이 나와있다. (아래 링크를 참고하자.)

 

Logging :: Spring Boot

By default, Spring Boot logs only to the console and does not write log files. If you want to write log files in addition to the console output, you need to set a logging.file.name or logging.file.path property (for example, in your application.properties)

docs.spring.io

springProfile 태그는 내부적으로 logback에서 설명하는 <if> 태그를 사용하고 있다. 이를 통해 `if(profile == staging)`과 같은 문법으로 동작한다. 

이 태그를 사용해 특정 Spring Profile일 경우에 대해 Appender를 설정해 줄 수 있다. dev 환경의 경우 Console 출력으로 진행하기 때문에 `console-appender.xml`을 추가하였고, 그 내부의 필요한 환경변수들을 제공해 주기 위해 defaults.xml을 포함시켰습니다.

이후, root의 info 레벨에 대해서 (애플리케이션에서 발생하는 모든 로그) 콘솔 출력으로만 나오도록 설정해 주었습니다.

 

다음은 staging 환경의 파일 출력이다. 역시 `file-appender.xml`을 그대로 사용하는 방법으로 한번 작성해 보았다. 콘솔 출력과의 차이는 file-appender의 경우 파일을 따로 주기적으로 삭제하고 압축하는 설정들을 제어하기 위해 Spring의 설정파일에 값을 지정해 줘야 한다는 것이다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>

    <springProfile name="dev">
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <springProfile name="staging">
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <include resource="org/springframework/boot/logging/logback/file-appender.xml"/>

        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
</configuration>

 

추가된 설정파일의 경우 크게 차이는 없다. 만약 Spring의 설정 파일에서 LOG_FILE 값이나 LOG_PATH 값을 제공하지 않았을 때, 기본 로그 저장 위치를 /tmp/spring.log 파일로 설정해 주는 환경변수만 추가해 주었다.

하지만 `file-appender.xml`의 내부는 `console-appender.xml`과 차이가 크다.

아래는 `file-appender.xml`에만 존재하는 파일 관리 설정 값들이다.

<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
	<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
	<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
	<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
	<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
	<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
</rollingPolicy>

 

간단하게 각 설정값들에 대해 이해하고 이들을 Spring 설정파일(application.yml)에 입력해서 넣어보자. 값들을 보면 기본값으로 주어지는 것들이 보이는데 이를 `application.yml`에서 원하는 값들로 채워 넣을 수 있다.

 

`LOG_FILE`

저장될 로그 파일 이름이다. base.xml 파일을 보면 아래와 같이 아무 설정이 없다면 spring.log로 설정된다는 것을 알 수 있다.

<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>

 

 

`<fileNamePattern>`

보관 파일의 이름 규칙을 결정한다. 로그 파일의 경우 특정 기준에 따라 새로운 파일을 압축 생성해 관리하는데 그 파일의 이름을 생성할 때 사용하는 규칙을 정해준다.

  • %d{yyyy-MM-dd} : 하루 주기로 보관 파일을 구분
  • %i : 같은 날짜(기간) 안에서 사이즈가 초과될 때, 0,1,2,.... i 인덱싱.
  • .gz : 보관 파일을 gzip으로 즉시 압축

`<cleanHistoryOnStart>`

애플리케이션 시작 시 보관 파일 정리를 즉시 한 번 수행할지 여부를 결정한다. 즉, 애플리케이션을 재배포할 때, 기존의 로그 파일들을 모두 삭제할지에 관한 설정이다.

 

`<maxFileSize>`

현재 기간에서 단일 보관 파일의 최대 크기를 의미한다. 예를 들어, 하루에 로그 파일의 크기가 25MB가 나온다면 하루 안에서 크기 기반 세분화가 일어난다.

  • ...2025-09-19.0.gz (10MB)
  • ...2025.09-19.1.gz (10MB)
  • ...2025.09-19.2.gz (나머지 5MB)

`<totalSizeCap>`

전체 보관 파일의 총 용량 상한을 의미한다. (0은 제한이 없음을 의미) 값이 설정되어 있으면, Logback은 오래된 보관 파일부터 지워 총용량이 cap 이하가 되도록 관리한다. 이는 모든 보관된 로그파일의 총용량을 합산해서 제한한다.

 

`<maxHistory>`

기간 단위로 보관 개수를 제한한다. 예를 들어 설정값을 14로 한다면 15일에는 첫날 로그 파일을 삭제한다. 하루에 여러 개의 인덱스가 생겨도 그 날짜 묶음은 하루로 계산된다.

 

기본 설정 값들은 공식문서를 살펴보면 다음과 설정 파일로 주입할 수 있다고 나와있다.

 

Logging :: Spring Boot

By default, Spring Boot logs only to the console and does not write log files. If you want to write log files in addition to the console output, you need to set a logging.file.name or logging.file.path property (for example, in your application.properties)

docs.spring.io

따라서, application.yml 파일에 원하는 파일 관리 설정값을 세팅하면 그 값들이 logback-spring.xml에 전달된다.

아래는 제가 실습에 사용한 Spring 설정 파일들입니다.

`application.yml`

spring:
  profiles:
    active: staging

 

`application-staging.yml`

logging:
  file:
    name: ./logs/app.log
  logback:
    rollingpolicy:
      clean-history-on-start: true
      max-file-size: 100MB
      total-size-cap: 0
      max-history: 30

 

두 가지 환경에 대한 최종 실행 결과를 살펴보자. 설정된 profile이 dev일 경우는 아래와 같이 Console에 출력되는 것을 볼 수 있다.

profile을 dev에서 staging으로 변경해 보자. 그럼 root 디렉터리 기준으로 `logs/` 폴더가 생기고 아래와 같이 log 파일에 출력이 쌓이는 것을 볼 수 있다. 이 설정으로 애플리케이션을 실행시키면 콘솔에는 아무것도 나오지 않는다.

log 파일 생성
log 파일 출력
빈 Console

 

4. logback-spring.xml을 작성하며 겪었던 문제 상황

설정 파일을 작성하며 NHN의 meetup 블로그 글을 참고하여 진행하였다.

 

(Spring Boot)Logging과 Profile 전략 : NHN Cloud Meetup

(Spring Boot)Logging과 Profile 전략

meetup.nhncloud.com

 

이 글에서 <root> 태그 안에 springProfile을 넣어서 사용하는 방법을 제시하는데 똑같은 방법으로 진행했을 때 내 환경에서는 오류가 났다.

문제의 원인은 logback에서 root 태그 안에 if 태그로 분기하는 것을 허용하지 않는다는 점에서 발생하고 있었다. 블로그 글이 작성되었던 시점에는 이런 문제가 존재하지 않았을 것으로 예상된다.

 

실제로 Spring Boot 3년 전 이슈 중에 내가 이번에 겪었던 오류를 똑같이 겪는 사람을 발견할 수 있었고 해당 문서에서 <springProfile>은 <if>로 동작한다는 것을 파악할 수 있었다.

 

No warning is given when <springProfile> is used in a Logback <root> block · Issue #33610 · spring-projects/spring-boot

Hi Community, I have the following spring-logback.xml configuration <?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/...

github.com

 

따라서, Spring Boot 3.0 이상의 환경이라면 아래 형식은 사용할 수 없으니, Spring Boot 공식문서에서 제공하는 코드 형식을 사용하도록 하자.

<root>
  <springProfile name="console-logging">
    <appender-ref ref="CONSOLE"/>
  </springProfile>

  <springProfile name="file-logging">
    <appender-ref ref="FILE"/>
  </springProfile>

  <springProfile name="remote-logging">
    <appender-ref ref="REMOTE_LOG_SERVER"/>
  </springProfile>
</root>

 

5. 운영환경(prod) logback-spring.xml 설정하기 

위 설정까지 진행했으면 이제 남은 건 운영 환경이다. 운영환경의 로그 수집 과정이 어떤 과정으로 일어나는지 간단하게 살펴보자. 먼저 Logback 아키텍처 관점으로 보면 운영환경에서 Spring Boot의 역할은 다음과 같다.

 

Logger를 이용해 로그 데이터를 Appender에 보내고 Appender는 데이터 수집기에 데이터를 전송하기 전에 로그 데이터를 압축하거나 형식을 변환하는 작업을 진행한 후, 로그를 수집하는 서버에 전송한다.

따라서, 이번 실습에서는 아직 어떤 모니터링 관리 시스템을 프로젝트에 사용할지 결정하지 못해 가장 많이 사용되는 시스템 중 Grafana Loki를 이용하여 실습을 진행해 보려고 한다. 이후, 실제 운영 서버에 도입할 때는, ELK와 Loki를 비교 분석하여 어떤 스택을 사용하는 게 맞을지 생각해 보고 결정하려고 합니다.  

 

 

로그를 Loki로 보내는 방식에는 2가지 방법이 있다.

`loki4j appender`로 직접 loki로 Push 하는 방법이 있고 `수집기(Alloy, Promtail)`를 이용하여 File 데이터를 읽어 Loki에 전달하는 방식이 있다.

 

현재 실습 단계에서는 따로 사이드 카를 구축하지 않고 사용할 수 있는 loki4j를 이용하여 애플리케이션 내부에서 loki로 로그를 전송하는 방식으로 진행하고 이후 Grafana Loki를 이용하는 게 확정된다면 두 시스템을 비교하여 서비스에 더 적합한 방식을 도입해 보겠습니다.

 

loki4j appender를 이용하기 위한 설정 파일과 환경 설정은 공식문서에 친절하게 나와있다. 해당 오픈소스는 커뮤니티 오픈소스로 간단하게 logback-spring.xml 파일을 구성할 때 유용하게 사용할 수 있다. 원래라면 아래 문서를 참고해 작성해야 했을 텐데, 이런 점들이 바로 오픈소스의 매력이지 않을까?

 

Chapter 4: Appenders

There is so much to tell about the Western country in that day that it is hard to know where to start. One thing sets off a hundred others. The problem is to decide which one to tell first. —JOHN STEINBECK, East of Eden Chapter 4: Appenders What is an Ap

logback.qos.ch

 

공식문서를 따라가며 작성한 설정 파일은 다음과 같다.

gradle 의존성 추가

dependencies {
	...
	//loki4j
	implementation 'com.github.loki4j:loki-logback-appender:2.0.0'
	implementation 'com.github.loki4j:loki-protobuf:0.0.2_pb4.31.0'
	implementation 'org.apache.httpcomponents:httpclient:4.5.14'
}

 

`logback-spring.xml`

<springProfile name ="prod">

    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

    <appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
        <labels>
            app = my-app
            host = ${HOSTNAME}
            level = %level
        </labels>
        <structuredMetadata>off</structuredMetadata>
        <message>
            <pattern>%-5level [%thread] %logger{20} - %msg%n</pattern>
        </message>
        <http>
            <url>http://localhost:3100/loki/api/v1/push</url>
            <useProtobufApi>true</useProtobufApi>
            <sender class="com.github.loki4j.logback.ApacheHttpSender" />
            <requestTimeoutMs>10000</requestTimeoutMs>
        </http>
        <batch>
            <maxItems>100</maxItems>
            <timeoutMs>10000</timeoutMs>
        </batch>
        <verbose>true</verbose>
    </appender>

    <root level="INFO">
        <appender-ref ref="LOKI"/>
    </root>
</springProfile>

 

이 설정 파일에 대해서는 아래 공식문서를 보고 각 태그에 대해 이해하면 좋을 것 같다.

 

Loki4j Logback · Pure Java Logback appender for Grafana Loki

Pure Java Logback appender for Grafana Loki

loki4j.github.io

 

첫 태그에 추가한 OnConsoleStatusListener는 로그 전송과 관련되어서 콘솔에 표시해 주는 옵션이다.

애플리케이션 로그는 출력되지 않지만, Loki에 로그 데이터를 전송하는 Appender와 관련된 로그는 출력되는 것을 볼 수 있다.

 

현재까지 진행한 구조는 아래와 같습니다. 아직 Grafana Loki와 Grafana DashBoard 환경을 구축하지 않았기 때문에 해당 환경을 구축하고 실제 어떻게 로그가 수집되고 표현되는지 살펴보자.

 

6. 외부 환경 설정하기 ( Grafana Loki, Grafana DashBoard)

두 서비스를 실행시키는 방법으로는 Doceker를 이용하는 방법과 로컬에서 직접 애플리케이션을 다운로드하여 실행시키는 방법이 있다. 제 노트북 환경에서는 Docker Desktop을 설치해놓지 않아 번거롭지만 Local에 다운받아 실행시키는 방법으로 진행했습니다.

 

Install Grafana Loki locally | Grafana Loki documentation

Install Grafana Loki locally You can use Grafana Cloud to avoid installing, maintaining, and scaling your own instance of Grafana Loki. Create a free account to get started, which includes free forever access to 10k metrics, 50GB logs, 50GB traces, 500VUh

grafana.com

그라파나 공식 사이트에 설치 방법이 다양하게 제공되고 있으니 참고해서 진행하면 됩니다~

 

저는 Locally 방법으로 진행했습니다. 하지만 설치는 윈도 환경이라 깃헙에

올라와있는 압축 파일을 다운로드하는 방식으로 진행하였습니다.

 

Releases · grafana/loki

Like Prometheus, but for logs. Contribute to grafana/loki development by creating an account on GitHub.

github.com

 

또한, 공식문서에서는 아래 명령어로 yaml 파일을 다운로드하아야 한다고 나와있어 제가 다운로드한 버전인 3.5.5 버전으로 `loki config` 파일을 다운받았습니다.

// 공식문서 내용
wget https://raw.githubusercontent.com/grafana/loki/<사용할 버전>/cmd/loki/loki-local-config.yaml

// 윈도우에서 3.5.5 버전 설정파일 다운받기
curl.exe -L -o C:\loki\loki-local-config.yaml ^
  https://raw.githubusercontent.com/grafana/loki/v3.5.5/cmd/loki/loki-local-config.yaml

 

그리고 설정파일을 아래와 같이 수정해서 실행시켰다. 옵션들은 AI의 도움을 받아 수정했는데 나중에 하나씩 의미를 알아가 보며 세팅해야겠다.

auth_enabled: false

server:
  http_listen_port: 3100
  log_level: info


common:
  instance_addr: 127.0.0.1
  path_prefix: ./loki-data
  storage:
    filesystem:
      chunks_directory: ./loki-data/chunks
      rules_directory:  ./loki-data/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

query_range:
  results_cache:
    cache:
      embedded_cache:
        enabled: true
        max_size_mb: 64

limits_config:
  metric_aggregation_enabled: true

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

# pattern_ingester:
#   enabled: true
#   metric_aggregation:
#     loki_address: localhost:3100

ruler:
  alertmanager_url: http://localhost:9093

frontend:
  encoding: protobuf

 

마지막으로 아래 명령어를 통해 Loki를 실행시킬 수 있다.

loki-windows-amd64.exe --config.file=loki-local-config.yaml

 

다음은 Grafana 대시보드 실행이다. 실제 Loki가 수집한 로그 데이터를 로그 쿼리를 통해 대시보드에서 살펴볼 수 있다. 먼저, 아래 링크에서 OS 환경에 맞게 다운로드하자.

 

Download Grafana | Grafana Labs

Overview of how to download and install different versions of Grafana on different operating systems.

grafana.com

압축을 풀고 `bin/` 폴더에 있는 grafana-server.exe를 실행시키자. 그 후, 웹 브라우저로 localhost:3000으로 접속하면 다음과 같은 화면이 뜨는데 admin / admin으로 접속할 수 있다.

 

로그인 버튼을 누르면 아래와 같이 비밀번호를 바꾸라고 하는데 저는 local에서 실습 용도로 사용하기 때문에 skip 하였습니다.

 

접속하면 왼쪽 sidebar에 Connections에서 Add new connection을 선택합니다. 그 후, Loki 검색 -> Loki 선택 -> Add new Datasource를 클릭합니다.

 

그럼 아래와 같은 화면이 나오는데 URL만 localhost:3100으로 맞춰주고 생성합니다.

 

이후 다시 sidebar에서 Explore -> logs 탭으로 들어가면 아래와 같이 현재까지 수집한 로그를 볼 수 있습니다. 저는 Controller 하나를 추가해 요청을 보내면 error 로그를 출력하는 코드를 추가하여 아래와 같이 ERROR와 INFO 2개의 level이 뜨는 모습을 볼 수 있습니다.

 

7. 마무리하며

logback-spring.xml 파일을 작성하는 법부터 간단하게 local에서 Grafana Loki와 연동하는 과정을 실습으로 진행해 보며 학습해 보았는데, 작성한 Appender가 한 번에 잘 동작해서 기분이 좋았고, Loki 역시 한번에 동작해서 편안하게 실습을 마무리할 수 있었습니다. 

이번 실습을 통해 어떤 과정으로 logback-spring.xml을 작성하고 어떻게 로그 수집 시스템과 연동하는지 살펴볼 수 있었는데, 이제, 다음으로 진행할 작업은 어떤 로그 수집 아키텍처를 선택할지 (ELK vs Grafana Loki), 그리고 만약 Grafana Loki를 선택한다면 어떻게 로그를 전송할지 (Loki4j vs Promtail, Alloy)를 결정해 보려고 합니다.

각각의 장단점을 비교해 보고 진행하는 프로젝트에 가장 적합한 기술 스택을 선정하는 과정으로 진행하며 최종적으로 Slack Alert까지 추가해보려고 합니다.