일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- HTTP Web Server
- linux foreground
- JavaScript 실행 디버깅
- Linux 디렉터리 구조
- 서버의 서비스 방식
- 자바스크립트 런타임
- Logback
- EC2 zsh
- EC2 HTTP 호스팅
- Linux apt
- Linux rmdir
- Linux 디렉터리 역할
- Linux mkdir
- 자바스크립트 이벤트 루프
- Linux apt-get
- javascript 정렬
- EC2 Apache2
- Linux cd
- JavaScript EventLoop
- AWS EC2 서버 만들기
- Linux pwd
- EC2 oh my zsh
- Linux 디렉터리 명령어
- Linux ls
- linux background
- Linux 파일 관리 명령어
- Linux oh my zsh
- Linux cat
- javascript scope
- Navigation Pattern
- Today
- Total
HyunJun 기술 블로그
스프링 프레임워크 Logback Log -> File, DB 저장하기 본문
1. 로그 저장의 필요성?
로그를 분석 및 관리하고 시간이 지나더라도 해당 시간대의 로그를 확인하고 싶다면 로그를 저장할 필요성이 있다.
2. 로그 저장 플랫폼
이 전까지 작성했던 로그 관련 글의 순서대로 기술해 보자면, Consol Log -> Slack, Discord, Telegram Log 전송의 순서대로 발전해 왔다. 하지만 직접 로그를 자동으로 Messenger로 보내는 로직을 구현해 본 사람이라면 아래와 같은 불편한 점을 느꼈을 것이라고 생각한다.
- 슬랙, 디스코드, 텔레그램 등으로 로그를 전송하는 것도 좋지만 해당 기능은 기본적으로 특정 인원만 메시지를 받을 수 있고,
아무래도 모바일로도 알람이 가는 만큼 무분별하게 전송하게 된다면.... 알람이 매우 울릴 것이므로 Warn, Error 등 정말 중요한 정보들 위주로만 받게 된다. - 또한, 해당 플랫폼에 전송된 메시지는 해당 플랫폼에 종속되어 있기에 해당 플랫폼에서 직접 삭제해야 한다.
- 로그의 포맷이 일정하고, 해당 로그들을 프로그래밍으로 분석하려 할 때 슬랙, 디스코드, 텔레그램으로 전송하였다면 해당 채팅 기록들을 불러오는 기능이 있을지도 모를뿐더러, 3개의 플랫폼에 대한 설계 및 코딩을 해야 한다.
3. File과 Database
그렇다면 이러한 문제점을 해결하려면 어떻게 해야 할까? File 혹은 Database로 설계 및 구현하면 된다.
File로 저장하게 되면 자연스럽게 알람과 용량의 압박에서 벗어나 info 레벨같이 많은 로그를 저장할 수 있고, 일상적인 로그를 많이 저장하는 만큼 해당 로그들을 분석해 사용자 분석이 가능해지고 좋은 방향으로 프로그램을 업데이트할 수 있다. 또한 로그를 중요한 자산으로서 저장하고 공유해야 한다면 Database에 저장하면 된다. 또한 Spring batch, scheduler를 사용하여 일정 기간이 지난 로그는 자동으로 삭제하는 로직을 구현할 수 있다.
이러한 방식들의 장단점을 보면 알 수 있듯이, File은 서버에서 일어나는 모든 로그, Database는 모든 로그까지는 아니지만 공유할 필요성이 있는 어느 정도 중요한 레벨 이상의 로그, 모바일 메신저로서의 전송은 서버에서 일어난 매우 위험한 로그(비정상적 접근 등)로 사용하면 되지 않을까?
4. 구현하기
4-1. File
build.gradle
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Logback을 직접 추가해 주어도 되지만 어차피 lombok은 반 필수이므로 lombok으로 사용했습니다. lombok 안에 Logback이 들어있습니다.
properties
logging.config=classpath:logback-spring.xml
resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Info</level>
</filter>
<encoder>
<pattern>%d %-5level %logger{35} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./log/local/%d{yyyy-MM-dd}/server.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d %-5level %logger{35} - %msg%n</Pattern>
</encoder>
</appender>
<root level="Info">
<appender-ref ref="Console"/>
<appender-ref ref="file"/>
</root>
</configuration>
maxHistory: 최대 보관 주기
totalSizeCap: 파일의 크기
이 정도까지만 하고 프로그램을 실행시켜도 바로 로그가 저장됩니다.
쉽죠? 파일 저장은 xml 파일을 이해하고, 파일 패턴 및 보관 주기 설정 등만 이해하면 Logback 자체가 구현이 잘 되어있어서 어렵지 않습니다. 사실상 로그 설정만 해주면 된다고 보시면 됩니다.
4-2. Database
AWS의 RDS로 MySQL 서버를 생성했고 DBeaver라는 데이터베이스 관리 도구 툴로 DB 서버에 접속하여 테스트용 데이터베이스를 만들어 볼게요.
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
jpa, mysql 관련 의존성을 추가했습니다.
properties: DB 관련 Datasource 정보를 넣어줬습니다.
spring.datasource.url=jdbc:mysql://mydbdbdbdbdbdb:3306/log-test
spring.datasource.username=admin
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create
logging.config=classpath:logback-spring.xml
처음 실행 시 ddl-auto=create 옵션을 넣어주어야 테이블이 자동으로 생성됩니다.
resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Info</level>
</filter>
<encoder>
<pattern>%d %-5level %logger{35} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./log/local/%d{yyyy-MM-dd}/server.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="MYSQL_DB" class="com.example.logbackfiledatabase.MySqlAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Error</level>
</filter>
</appender>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d %-5level %logger{35} - %msg%n</Pattern>
</encoder>
</appender>
<root level="Info">
<appender-ref ref="Console"/>
<appender-ref ref="file"/>
<appender-ref ref="MYSQL_DB" />
</root>
</configuration>
MYSQL_DB가 추가되었으며, Error Log가 발생했을 때 MySqlAppender를 사용하여 DB에 저장하는 방식입니다.
아무래도 실제 서비스 중에 많은 로그가 데이터베이스로 전송된다면 부하를 많이 받겠죠?
그래서, Error 로그만 저장을 하고 Error 로그들만 의도적으로 생성해 보려고 합니다.
Log 발생 시 적용되는 Appender입니다.
package com.example.logbackfiledatabase;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Slf4j
@Getter
@Setter
@Component
public class MySqlAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements ApplicationContextAware {
private static LogRepository logRepository;
@Override
protected void append(ILoggingEvent eventObject) {
Log entity = new Log(eventObject.getFormattedMessage());
logRepository.save(entity);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext.getAutowireCapableBeanFactory().getBean(LogRepository.class) != null) {
logRepository = (LogRepository) applicationContext.getAutowireCapableBeanFactory().getBean(LogRepository.class);
}
}
}
Repository입니다.
package com.example.logbackfiledatabase;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface LogRepository extends JpaRepository<Log, Long> {
}
Log 테이블을 표현해 줄 Entity입니다.
package com.example.logbackfiledatabase;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Getter
@NoArgsConstructor
public class Log extends Timestamped{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*로그*/
@Column
private String log;
public Log(String log) {
this.log = log;
}
}
Timestamped.java
package com.example.logbackfiledatabase;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class Timestamped {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
private LocalDateTime modifiedAt;
}
시간 관련 엔티티 자동 적용을 위해 @EnableJpaAuditing를 적용해 주세요.
마지막으로 테스트용 컨트롤러입니다.
package com.example.logbackfiledatabase;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController {
@RequestMapping("test")
public void errLogTest() {
log.error("에러 로그1");
log.error("에러 로그2");
log.error("에러 로그3");
log.error("에러 로그4");
log.error("에러 로그5");
}
}
프로젝트 실행 후 테이블이 생성이 되었나 확인을 한 뒤에,
localhost:8080/test로 연결 시, 의도한 것과 같이 결과가 잘 나왔습니다.