NHN Cloud Log & Crash 적용

한민기·2024년 8월 4일

Spring

목록 보기
4/8

MSA와 같이 여러 서버를 운영할 때 각각의 log들을 확인하기 위해서는 각 서버에 들어가 로그를 봐야하는 과정들이 있다.
나는 그래서 이러한 과정을 좀 더 간편하게 할 수 없을까 고민하던 중 log & crash를 알게 되었다.

Log & crash란?

클라이언트와 서버의 로그를 수집하여 사용자가 원하는 로그를 검색하고 조회할 수 있으며, 모바일 앱에서 발생하는 크래시 리포트를 수집하고 분석하여 크래시 발생 원인에 대한 다양한 정보를 제공한다.

주요 사용

  • Apache, Tomcat 등의 웹 로그가 많이 쌓이는 경우
  • 서버 로그를 서버에 접속하지 않고 봐야 하는 경우
  • 사용자에게 배포한 앱에서 발생한 로그를 한곳에서 취합하여 봐야 하는 경우
  • 서비스에서 발생한 오류 로그의 추이를 확인해야 하는 경우

간단히 설명하자면 분산되어 있는 log를 한곳에 취합하여 볼 수 있고 로그를 검색 및 조회 분석할 수 있다.

NHN Cloud Log & crash 사용 방법

NHN Cloud Log & crash 설명

nhn cloud log & crash는 API 형식으로 log를 주고 받는다.
분산되어 있는 log를 정해진 형태에 맞춰 보낸다.

url 주소

JSON과 HTTP로 Log & Crash 수집 서버에 로그를 전송할 때는 다음 주소를 사용해다 한다.

  • Log & Crash: api-logncrash.nhncloudservice.com
  • Method of Delivery: POST
  • URI: /v2/log
  • Content-Type: "application/json"

형식

{
    "projectName": "__앱키__",
    "projectVersion": "1.0.0",
    "logVersion": "v2",
    "body": "This log message come from HTTP client.",
    "logSource": "http",
    "logType": "nelo2-log",
    "host": "localhost"
}

항목에 대한 설명

Log Search를 위한 파라미터

projectName: string, 필수
    [in] 앱키.

projectVersion: string, 필수
    [in] 버전. 사용자 지정 가능. "A~Z, a~z, 0~9,-._"만 포함.

body: string, 옵션
    [in] 로그 메시지.

logVersion: string, 필수
    [in] 로그 포맷 버전. "v2".

logSource: string, 옵션
    [in] 로그 소스. Log Search에서 필터링을 위해 사용. 정의되지 않으면 "http".

logType: string, 옵션
    [in] 로그 타입. Log Search에서 필터링을 위해 사용. 정의되지 않으면 "log".

host: string, 옵션
    [in] 로그를 보내는 단말의 주소. 정의되지 않으면 수집 서버에서 peer-address를 사용해 자동으로 채움.
    
sendTime; string, 옵션
    [in] 단말이 보낸 시간. 입력 시 Unix timestamp로 입력.

logLevel; string, 옵션
    [in] Syslog 이벤트용.

UserBinaryData; string, 옵션
    [in] 로그 검색 화면에서 [다운로드|보기] 링크 표시, base64 인코딩된 값을 담아 전송.

UserTxtData; string, 옵션
    [in] 로그 검색 화면에서 [다운로드|보기] 링크 표시, base64 인코딩된 값을 담아 전송.

txt*; string, 옵션
    [in] 필드 이름이 txt로 시작하는 필드(txtMessage, txt_description 등)는 text 필드로 저장. 로그 검색 화면에서 필드값의 일부 문자열로 검색(full text search) 가능. 필드의 크기는 1MB로 제한됨.

long*; long, 옵션
    [in] 필드 이름이 long으로 시작하는 필드(longElapsedTime, long_elapsed_time 등)는 long 타입 필드로 저장됨. 로그 검색 화면에서 long 타입 range 검색 가능.

double*; double, 옵션
    [in] 필드 이름이 double로 시작하는 필드(doubleAvgScore, double_avg_score 등)는 double 타입 필드로 저장됨. 로그 검색 화면에서 double 타입 range 검색 가능.
    

적용 방법

저는 Spring의 logback의 Appender를 통하여 RestTemple을 사용해 API를 전송하였습니다.

1. 먼저 NHN Cloud Console에서 Log & Crash 접속후 AppKey를 알아본다.

2. 요청을 보낼 Requeset를 정의한다.

@Setter
@Getter
public class LogCrashRequest {
	String projectName;
	String projectVersion;
	String logVersion;
	String body;
	String sendTime;
	String logSource;
	String logType;
    String host;

	private static final String DEFAULT_PROJECT_NAME = "APIKey 입력";
	private static final String DEFAULT_PROJECT_VERSION = "1.0.0";
	private static final String DEFAULT_LOG_VERSION = "v2";
	private static final String DEFAULT_LOG_SOURCE = "http";
	private static final String DEFAULT_LOG_TYPE = "nelo2-http";
	private static final String DEFAULT_HOST = "localhost";
    
	public LogCrashRequest(String body) {
		this.body = body;
		this.projectName = DEFAULT_PROJECT_NAME;
		this.projectVersion = DEFAULT_PROJECT_VERSION;
		this.logVersion = DEFAULT_LOG_VERSION;
		this.logSource = DEFAULT_LOG_SOURCE;
		this.logType = DEFAULT_LOG_TYPE;
		this.host = DEFAULT_HOST;
	}
}

각 항목은 각자의 프로젝트에 맞게 설정해주시면 됩니다.

3. Appender 정의

@Setter
@Slf4j
public class LogCrashAppender extends AppenderBase<ILoggingEvent> {
	private static final ObjectMapper objectMapper = new ObjectMapper();
	private static final RestTemplate restTemplate = new RestTemplate();

	private String url;

	@Override
	protected void append(ILoggingEvent iLoggingEvent) {

		LogCrashRequest request = new LogCrashRequest(iLoggingEvent.getFormattedMessage());

		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON);
		try {
			String str = objectMapper.writeValueAsString(request);
			HttpEntity<String> body = new HttpEntity<>(str, headers);
			restTemplate.postForEntity(url, body, String.class);
		} catch (JsonProcessingException e) {
			throw new RuntimeException(e);
		}
	}
}
log 내용을 body로 넣어 restTemplate을 통해 보낸다.

4. logback-spring.xml 설정


        <appender name="NHN_LOG_CRASH_APPENDER" class= "logcarshAppender의 패키지 위치.LogCrashAppender">
            <url>https://api-logncrash.cloud.toast.com/v2/log</url>
        </appender>

        <appender name="ASYNC_NHN_LOG_CRASH_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n</pattern>
            </encoder>
            <appender-ref ref="NHN_LOG_CRASH_APPENDER"/>
        </appender>

<root level="INFO">
            <appender-ref ref="ASYNC_NHN_LOG_CRASH_APPENDER" />
        </root>

logcarshAppender의 패키지 위치.LogCrashAppender 이 부분은 각자에 맞게 패키치 위치를 맞추시면 됩니다,
level은 INFO 로 설정했습니다.

이전 logback-spring과 총합본

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <springProperty scope="context" name="LOG_DIR" source="log.directory" />
    <timestamp key="BY_DATE" datePattern="yyyy-MM-dd" />

    <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="prod">
        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

        <appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_DIR}/error/error-${BY_DATE}.log</file>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>ERROR</level>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/backup/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>100MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>3GB</totalSizeCap>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>

        <appender name="NHN_LOG_CRASH_APPENDER" class="com.nhnacademy.bookstore.global.appender.LogCrashAppender">
            <url>https://api-logncrash.cloud.toast.com/v2/log</url>
        </appender>

        <appender name="ASYNC_NHN_LOG_CRASH_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n</pattern>
            </encoder>
            <appender-ref ref="NHN_LOG_CRASH_APPENDER"/>
        </appender>

        <appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_DIR}/info/info-${BY_DATE}.log</file>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/backup/info/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>100MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>3GB</totalSizeCap>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>

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

제가 이전 프로젝트를 할때 사용한 logback-spring.xml 파일입니다.
dev에서는 콘솔에서만 로그가 보이도록 하고
prod에서는 콘솔, 파일로 저장(info, error), log & crash 사용

정리

  • log crash는 API를 통해 분리되어있는 환경의 로그들을 모아서 검색 및 조회를 할 수 있다.
  • Appedner 기능을 통해서 로그에 관련된 내용을 처리할 수 있다.
  • RestTemple으로 API 요청을 주고 받을 수 있다.
profile
백엔드 개발자

0개의 댓글