실무에서는 Systemm.out.println()과 같은 시스템 콘솔 보다는 필요한 정보를 출력하거나 디버깅 없이 별도의 logging 라이브러리를 사용한다. 이를 통해 디버깅이나 타임프탬프 등 정해진 양식에 맞춰 화면에 띄우거나 파일 로그를 남길 수 있다.
실제 서비스 중인 애플리케이션의 콘솔을 보는 데 어려움
어플리케이션이 실행 중일 때 일어난 일의 기록
Logging : 로그를 남기는 행위
Logger : 로그를 작성하기 위해 사용하는 객체
문제가 발생했을 때 로그 파일을 통해 문제의 원인과 발생 지점을 추적 가능
Log는 Spring Boot 에 내장되어 있음
//@Slf4j
@RestController
public class TestController {
private static final Logger log = LoggerFactory.getLogger(TestController.class);
@GetMapping("/log")
public void logTest() {
log.trace("A TRACE message");
log.debug("A DEBUG message");
log.info("A INFO message");
log.warn("A WARN message");
log.error("A ERROR message");
}
url 접속
로그가 남는다.
로그에 남길 정보의 중요도에 따라 나눈 단계이다. 아래로 갈수록 높은 단계이다.
trace : 아주 사소한 변화까지 로그로 남긴다. 가장 낮은 단계이다.
debug : 디버깅에 도움이 될 변화들을 로그로 남긴다.
info : 프로그램을 실행하는 동안의 변화들을 정보 제공을 위해 로그로 남긴다.
warn : 프로그램이 실행 중일 때 현재 시점에서는 문제가 발생하지 않았지만, 차후에 문제가 생길 수도 있다는 경고 목적으로 로그를 남긴다.
error : 서비스가 정상적으로 요청을 처리하지 못했음을 알리기 위해 로그를 남긴다.
멈춘것처럼 보이지만 보여줄 로그가 없는 것이다.
무수한 로그들이 뜨게 된다.
특정 파일에 로그를 남기거나, 특정 날짜까지만 기록하는 등 로그의 형식을 제어하기 위해 Logback을 사용한다.
resources 패키지에 logback-spring.xml 추가
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOGS" value="./logs" />
<appender name="Console"
class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%cyan(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1}): %msg%n%throwable
</Pattern>
</layout>
</appender>
<appender name="File" class="ch.qos.logback.core.FileAppender">
<file>${LOGS}/file-log.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1} [%t] %m%n</Pattern>
</encoder>
</appender>
<appender name="RollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOGS}/rolling-file-log.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1} [%t] %m%n</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGS}/archived/rolling-file-log-%d{yyyy-MM-dd_HH-mm-ss}.%i.log
</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>10</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- LOG everything at INFO level -->
<root level="info">
<appender-ref ref="RollingFile" />
<appender-ref ref="Console" />
</root>
<!-- LOG "org*" at WARN level -->
<logger name="org" level="warn" additivity="false">
<appender-ref ref="Console" />
<appender-ref ref="File" />
</logger>
<!-- LOG "com.example.contents*" at TRACE level -->
<logger name="com.example.contents" level="trace" additivity="false">
<appender-ref ref="RollingFile" />
<appender-ref ref="Console" />
<appender-ref ref="File" />
</logger>
</configuration>
ContentsApplication 재 실행
로그의 형식이 바뀐다.
logs 패키지와 log 파일이 생성된다.
xml 파일의 아래 부분을 주석처리
<Pattern>
코드에 “로그남기기” 를 추가하면
다시 실행했을 때 “로그남기기”가 추가된걸 볼 수 있다.
appender
: 로그를 받아 작성해주는 역할을 하는 클래스이다.
ConsoleAppender는 콘솔에 로그를 남긴다.
FileAppender는 파일에 로그를 남긴다.
RollingFileAppender는 특정 조건에 따라서 파일 갯수, 크기, 총 용량을 제한하며 로그를 남긴다.
Pattern, Layout, Encoder
: 패턴 내용을 해석한 후, <layout>
은 문자열로 변환하고, <encoder>
는 파일로 작성 가능한 Byte Array로 변환합니다.
%d
: 로그가 작성된 날짜%p
: 로그 레벨%c
: 로그 남긴 스레드%m
: 로그 메시지%n
: 개행 문자%d{yyyy-MM-dd_HH-mm-ss}
처럼 파일 이름의 패턴을 지정할 수가 있는데, 이처럼 파일 이름에 남기는 시간 구분이 초 단위라면 파일도 초단위로 새로 생성되게 된다.
ContentsApplication
@Slf4j
@SpringBootApplication
public class ContentsApplication {
public static void main(String[] args) {
SpringApplication.run(ContentsApplication.class, args);
Runnable logRunnable = () -> log.info("log test {}", LocalDateTime.now());
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(logRunnable, 0, 500, TimeUnit.MILLISECONDS);
}
}
매 초마다 로그 생성이 된다.
개발, 테스트, 운영 등 환경에 따라 서버나 설정 값, 동작이 다를 수 있다. 그럴 때 사용할 수 있는 기능으로 어플리케이션 실행 시 기능 마다 다른 설정을 할 수 있는 기능이다.
application-{profile}.yaml ← 이와 같은 형식으로 yaml 파일을 생성한다.
spring:
profiles:
default: dev # application-dev.yaml을 기본으로 사용
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
mvc:
# /static/ 으로 시작하는 요청에 대해 정적 파일 서빙
static-path-pattern: /static/**
web:
resources:
# 정적 파일 탐색 장소
static-locations: file:media/,classpath:/static
logging:
level:
root: info
config: file:logback-spring.xml
spring:
datasource:
url: jdbc:sqlite:db.sqlite
driver-class-name: org.sqlite.JDBC
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.community.dialect.SQLiteDialect
defer-datasource-initialization: true
sql:
init:
mode: always
@Profiles
: 프로필을 구분하여 Bean을 로드할 수 있는 어노테이션이다.@ActiveProfiles
: 테스트 수행 시 어떤 profile을 사용할 것인지 정해주는 어노테이션이다.application-test.yaml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: password
jpa:
database: h2
database-platform: org.hibernate.dialect.H2Dialect
logging:
level:
root: debug
UserControllerTests
@ActiveProfiles("test")
@ExtendWith(MockitoExtension.class)
public class UserControllerTests {
@Mock
private UserService userService;
출처 멋사 5기 백엔드 위키 6팀 식스센스
백엔드 위키 작성
멘토님께서 오전수업 내용만 위키작성을 부탁하셔서 이게 웬떡이냐 하고 신이났다. 오후 수업부턴 서프라이즈라는 떡밥을 남긴 채 사라지셨다. 무슨 서프라이즈일까 큰 기대는 하지 않았지만 어쨌든 팀원들과 ㄱㅇㄷ을 외치며 한 주의 마무리가 훈훈하게 끝나나 싶었는데 갑자기 뜬 공지 내용은 이러했다.
"오늘부터 3일간 개인 미션 프로젝트 수행"
이게 무슨 소린가. 갑자기 프로젝트라고? 내용은 중고 거래 플랫폼(당근마켓 같은거) API를 만들어보라는 개인 미션이었다. 아니 나 지금 할 줄 아는거 숨쉬기가 전부인데 이게 뭔일이람. 할튼 일단 만들라고 하니까 무슨 수를 써서라도 만들어야겠지. 요구사항이 지금까지 배운걸 만드는 것과 비슷하긴 했지만 꽤 오랜 시간을 써야 할 것 같다. 나 어떡하냐