엔지니어링

Elastic RUM(Real User Monitoring) 한 잔

Rum으로 만든 멋진 칵테일 한 잔을 생각나게 했다면 죄송합니다. 제가 말하는 RUM이 여러분이 갈망하는 럼주가 아니라서 실망하셨을지 모르겠네요. 하지만 Elastic RUM도 럼주만큼 훌륭합니다! 한 번 음미해 볼까요? 이 블로그에서 다루는 세부 사항을 모두 살펴보려면 시간이 좀 걸릴 것이라는 점을 미리 말씀드립니다!

RUM이란 무엇입니까?

Elastic Real User Monitoring은 웹 브라우저와의 사용자 상호 작용을 캡처하고 성능 관점에서 웹 애플리케이션의 ‘실제 사용자 경험’에 대한 상세한 보기를 제공합니다. Elastic의 RUM 에이전트는 JavaScript 에이전트입니다. 즉, 모든 JavaScript 기반 애플리케이션을 지원합니다. RUM은 애플리케이션에 대한 귀중한 통찰력을 제공할 수 있습니다. RUM의 일반적인 이점은 다음과 같습니다.

  • RUM 성능 데이터는 병목 지점을 식별하고 사이트의 성능 문제가 방문자의 경험에 어떤 영향을 미치는지 확인하는 데 도움이 됩니다.
  • RUM에서 캡처한 사용자 에이전트 정보는 고객이 가장 많이 사용하는 브라우저, 디바이스 및 플랫폼을 식별하므로 정보를 기반으로 애플리케이션을 최적화할 수 있습니다.
  • RUM의 개별 사용자 성능 데이터는 위치 정보와 함께 전 세계 웹 사이트의 지역별 성능을 이해하는 데 도움이 됩니다.
  • RUM은 애플리케이션의 SAL(서비스 수준 협약)에 대한 통찰력과 측정 기능을 제공합니다.

Elastic APM을 사용하여 RUM 시작하기

이 블로그에서는 React 프론트엔드와 Spring Book 백엔드로 구성된 간단한 웹 애플리케이션을 계측하는 전체 프로세스를 단계별로 안내합니다. RUM 에이전트를 사용하기가 얼마나 쉬운지 확인할 수 있습니다. 또한, Elastic APM이 어떻게 프론트엔드와 백엔드 성능 정보를 연결하여 전체적인 분산 추적 보기를 제공하는지도 확인할 수 있습니다. 좀 더 자세히 알아보려면 Elastic APM 및 분산 추적에 대한 개요를 다룬 이전 블로그 게시물을 참조하시기 바랍니다.

Elastic APM Real User Monitoring을 사용하려면 APM 서버가 설치된 Elastic Stack이 있어야 합니다. 물론 APM 서버가 설치된 최신 Elastic Stack을 컴퓨터에 로컬로 다운로드하여 설치할 수 있습니다. 그러나 가장 쉬운 방법은 Elastic Cloud 체험판 계정을 생성하고 몇 분 만에 클러스터를 가동하는 것입니다. APM은 기본 I/O 최적화 템플릿에 활성화되어 있습니다. 이제부터는 클러스터가 준비되어 있다고 가정하겠습니다.

샘플 애플리케이션

우리가 계측하려는 애플리케이션은 React 프론트엔드와 인 메모리 차량 데이터베이스에 대한 API 액세스를 제공하는 Spring Boot 백엔드로 구성된 간단한 차량 데이터베이스 애플리케이션입니다. 이 애플리케이션은 일부러 단순하게 유지합니다. 첫 단계부터 시작하여 자세한 계측 단계를 보여줌으로써 여러분이 동일한 단계에 따라 자체 애플리케이션을 계측할 수 있도록 하고자 합니다.

React 프론트엔드와 Spring 백엔드로 구성된 간단한 애플리케이션

노트북에서 원하는 위치에 CarApp이라는 디렉터리를 생성합니다. 그런 다음 프론트엔드 및 백엔드 애플리케이션을 모두 해당 디렉터리에 복제합니다.

git clone https://github.com/adamquan/carfront.git
git clone https://github.com/adamquan/cardatabase.git

보시는 것처럼 애플리케이션은 매우 간단합니다. React 프론트엔드에는 몇 가지 구성 요소가 있고 백엔드 Spring Boot 애플리케이션에도 몇 가지 클래스만 있습니다. 프론트엔드와 백엔드에 대한 GitHub의 지침에 따라 애플리케이션을 구축하고 실행합니다. 다음과 같은 항목이 보일 것입니다. 이러한 항목에 대해 검색하고, 차량을 필터링하고, CRUD 옵션을 수행할 수 있습니다.

간단한 React 사용자 인터페이스

이제 애플리케이션이 실행 중이므로 RUM 에이전트를 사용하여 계측을 진행할 준비가 되었습니다.

RUM을 통해 즉시 사용 가능한 풍부한 계측

먼저 RUM 에이전트를 설치하고 구성합니다. 여기에는 두 가지 방법이 있습니다.

  1. 서버 측 애플리케이션의 경우 RUM 에이전트를 종속성으로 설치하고 초기화할 수 있습니다.

    npm install @elastic/apm-rum --save
        
  2. HTML 구성으로 RUM 에이전트를 설치합니다.

    <script src="https://unpkg.com/@elastic/apm-rum@4.0.1/dist/bundles/elastic-apm-rum.umd.min.js">
    </script>
    <script>
      elasticApm.init({
        serviceName: 'carfront',
        serverUrl: 'http://localhost:8200',
        serviceVersion: '0.90'
      })
    </script>
        

프론트엔드가 React 애플리케이션이므로 첫 번째 접근 방식을 사용하겠습니다. index.js와 동일한 디렉터리에 다음 코드가 포함된 rum.js라는 파일이 있습니다.

import { init as initApm } from '@elastic/apm-rum'
var apm = initApm({
 // 필요한 서비스 이름을 설정합니다(허용되는 문자: a~z, A~Z, 0~9, -, _ 및 공백).
 serviceName: 'carfront',
 // 애플리케이션의 버전을 설정합니다.
 // APM 서버에서 올바른 소스 맵을 찾는 데 사용됩니다.
 serviceVersion: '0.90',
 // 사용자 정의 APM 서버 URL을 설정합니다(기본: http://localhost:8200)
 serverUrl: 'https://aba7c3d90b0b4820b05b0a9df44c096d.apm.us-central1.gcp.cloud.es.io:443',
 // distributedTracingOrigins: ['http://localhost:8080'],
})
export default apm;

이것이 RUM 에이전트를 초기화하는 데 필요한 전부입니다! 다음은 일부 구성에 대한 간단한 설명입니다.

  1. 서비스 이름: 서비스 이름을 설정해야 합니다. 이름은 APM UI에서 애플리케이션을 나타냅니다. 의미 있는 이름을 지정하십시오.
  2. 서비스 버전: 애플리케이션의 버전을 말합니다. 이 버전 번호는 APM 서버에서 올바른 소스 맵을 찾는 데도 사용됩니다. 소스 맵은 나중에 자세히 설명하겠습니다.
  3. 서버 URL: APM 서버 URL을 말합니다. RUM 에이전트가 인터넷의 최종 사용자 브라우저에서 APM 서버 URL로 데이터를 보고하므로 APM 서버 URL은 일반적으로 퍼블릭 인터넷에서 액세스할 수 있습니다.
  4. distributedTracingOrigins는 나중에 다루겠습니다.

Elastic APM 백엔드 에이전트에 익숙한 사용자는 APM 토큰이 왜 여기에 전달되지 않았는지 궁금할 수 있습니다. 그것은 바로 RUM 에이전트가 비밀 APM 토큰을 사용하지 않기 때문입니다. 토큰은 백엔드 에이전트에만 사용됩니다. 프론트엔드 코드는 공개되기 때문에 비밀 토큰이 추가적인 보안을 제공하지 않습니다.

애플리케이션이 로드될 때 이 JavaScript 파일을 로드하고 사용자 정의 계측을 수행하려는 위치에 포함시킵니다. 일단은 사용자 정의 계측을 전혀 사용하지 않고 어떤 결과가 나오는지 보겠습니다. 이를 위해서는 index.jsrum.js를 포함하기만 하면 됩니다. 그러면 index.js 파일이 rum.js를 가져오고 페이지 로드 이름을 설정합니다. 페이지 로드 이름을 설정하지 않으면 APM UI에 페이지 로드가 “알 수 없음”으로 표시되며 이는 그리 직관적이지 않습니다. index.js는 다음과 같은 형태입니다.

import apm from './rum'
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
apm.setInitialPageLoadName("Car List")
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();

애플리케이션에 대한 트래픽을 일부 생성합니다. Kibana에 로그인하고 APM UI를 클릭합니다. “carfront”라는 서비스가 나열되어 있어야 합니다. 서비스 이름을 클릭하면 트랜잭션 페이지로 이동합니다. 기본 “page-load” 목록이 표시되고 “car list” 페이지 로드가 표시됩니다. 그렇지 않은 경우, 시간 선택기에서 “Last 15 minutes”로 시간 범위를 선택하십시오. “car list” 링크를 클릭하면 다음과 같이 브라우저 상호 작용에 대한 폭포 보기가 표시됩니다.

Elastic APM의 샘플 트랜잭션

RUM 에이전트가 기본적으로 얼마나 많은 정보를 캡처하는지 놀라셨나요? timeToFirstByte, domInteractive, domCompletefirstContentfulPaint와 같은 마커에 특히 주의하시기 바랍니다. 검은 점 위에 마우스를 올리면 이름을 확인할 수 있습니다. 이러한 마커는 콘텐츠 검색 및 이러한 콘텐츠의 브라우저 렌더링에 대한 자세한 정보를 제공합니다. 또한, 브라우저의 리소스 로딩에 대한 모든 성능 데이터에 주목하시기 바랍니다. 사용자 정의 계측 없이도 RUM 에이전트를 초기화하기만 하면 이러한 모든 상세한 성능 메트릭을 즉시 얻을 수 있습니다! 성능 문제가 있는 경우 이러한 메트릭을 사용하면 문제의 원인이 백엔드 속도 저하인지, 네트워크 속도 저하 또는 클라이언트 브라우저 속도 저하인지 손쉽게 파악할 수 있습니다. 정말 인상적이네요!

기억을 상기할 필요가 있는 분들을 위해 웹 성능 메트릭에 대해 간단히 설명하겠습니다. React와 같은 최신 웹 애플리케이션 프레임워크의 경우, 이러한 메트릭은 React의 비동기 특성으로 인해 웹 페이지의 ‘정적’ 부분만 나타낼 수 있습니다. 나중에 보게 되겠지만, 예를 들어 domInteractive 후에도 동적 콘텐츠가 로드되고 있을 수 있습니다.

  • timeToFirstByte는 브라우저가 정보를 요청한 후 웹 서버로부터 요청한 정보의 첫 바이트를 수신할 때까지 대기하는 시간입니다. 이는 네트워크와 서버 측 처리 속도가 조합된 것입니다.
  • domInteractive는 사용자 에이전트가 현재 문서의 준비 상태를 “interactive”로 설정하기 직전의 시간입니다. 이는 브라우저가 모든 HTML 구문 분석을 완료했으며 DOM 구성이 완료되었음을 의미합니다.
  • domComplete는 사용자 에이전트가 현재 문서 준비 상태를 “complete”로 설정하기 직전의 시간입니다. 즉, 페이지 및 관련 모든 하위 리소스(예: 이미지)의 다운로드가 완료되고 준비되었음을 의미합니다. 로딩 스피너가 가동을 멈췄습니다.
  • firstContentfulPaint는 브라우저가 DOM에서 콘텐츠의 첫 비트를 렌더링하는 시간입니다. 이는 페이지가 실제로 로드되고 있다는 피드백을 제공하기 때문에 사용자에게 중요한 이정표입니다.

유연한 사용자 정의 계측

RUM 에이전트는 방금 본 것처럼 브라우저 상호 작용에 대한 자세한 계측을 제공합니다. 필요한 경우 사용자 정의 계측을 수행할 수도 있습니다. 예를 들어, React 애플리케이션은 SPA(싱글 페이지 애플리케이션)이고 자동차를 삭제해도 “페이지 로드”가 트리거되지 않으므로 RUM은 기본적으로 자동차 삭제에 대한 성능 데이터를 캡처하지 않습니다. 이러한 경우에 사용자 정의 트랜잭션을 사용할 수 있습니다.

현재 릴리즈(APM Real User Monitoring JavaScript 에이전트 4.x)에서는 사용자가 페이지 로드를 트리거하지 않는 AJAX 호출 및 SPA(싱글 페이지 애플리케이션) 호출에 대한 트랜잭션을 수동으로 작성해야 합니다. JSF와 같은 일부 프레임워크에서는 JavaScript를 거의 제어할 수 없습니다. 따라서 AJAX 요청을 시작하는 버튼 클릭에 대한 트랜잭션을 수동으로 생성하는 것은 불가능합니다. 개발자가 AJAX 요청을 직접 제어할 수 있더라도 대규모 애플리케이션을 계측하는 데는 많은 노력이 필요할 것입니다. 현재 지원되지 않는 경우 이러한 요청에 대한 트랜잭션을 자동으로 생성하도록 RUM 에이전트를 개선할 계획입니다. 따라서 개발자가 프로그래밍 방식으로 추적 로직을 애플리케이션에 추가하지 않아도 자동 계측에서 애플리케이션의 훨씬 더 많은 부분을 처리할 수 있게 됩니다.

프론트엔드 애플리케이션의 "New Car" 버튼을 사용하면 새로운 차량을 데이터베이스에 추가할 수 있습니다. 새로운 차량 추가에 대한 성능을 캡처할 수 있는 코드를 계측하겠습니다. 구성 요소 디렉터리에서 Carlist.js 파일을 엽니다. 다음 코드가 표시됩니다.

// 새로운 차량 추가
addCar(car) {
    // 사용자 정의 트랜잭션 생성
    var transaction = apm.startTransaction("Add Car", "Car");
    apm.addTags(car);
    fetch(SERVER_URL + 'api/cars',
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(car)
        })
        .then(res => this.fetchCars())
        .catch(err => console.error(err))
}
fetchCars = () => {
    fetch(SERVER_URL + 'api/cars')
        .then((response) => response.json())
        .then((responseData) => {
            this.setState({
                cars: responseData._embedded.cars,
            });
        })
        .catch(err => console.error(err));
        // 응답 콜백 완료 시 현재 트랜잭션 종료
        var transaction = apm.getCurrentTransaction()
        if (transaction) transaction.end()
}

이 코드는 “Car” 유형의 “Add Car”라는 새로운 트랜잭션을 생성했습니다. 그런 다음 상황별 정보를 제공하기 위해 차량과의 트랜잭션에 태그를 지정했습니다. 응답 콜백 완료 시 트랜잭션이 종료되었습니다.

애플리케이션 웹 UI에서 새로운 차량을 추가합니다. Kibana에서 APM UI를 클릭합니다. “Add Car” 트랜잭션이 표시되어야 합니다. “Filter by type” 드롭다운에서 “Car”를 선택합니다. 기본적으로 “page-load” 트랜잭션이 표시됩니다.

Elastic APM에서 유형별로 필터링

“Add Car” 트랜잭션 링크를 클릭합니다. 사용자 정의 트랜잭션인 “Add Car”의 성능 정보가 표시됩니다.

'차량' 유형으로 필터링된 Elastic APM의 트랜잭션 샘플

“Tags” 탭을 클릭합니다. 우리가 추가한 태그가 표시됩니다. 태그와 로그는 APM 추적에 유용한 상황별 정보를 추가합니다.

Elastic APM에서 유형 태그별로 탐색

이것이 바로 쉽고 강력한 사용자 정의 계측을 수행하는 데 필요한 전부입니다! 자세한 내용은 API 설명서를 참조하세요.

Kibana를 통한 강력한 사용자 정의 APM 시각화

Elastic APM은 에이전트가 캡처한 모든 APM 데이터를 즉시 시각화할 수 있도록 큐레이션된 APM UI 및 기본 제공 APM 대시보드를 제공합니다. 자체 사용자 정의 시각화를 생성할 수도 있습니다. 예를 들어 RUM 에이전트가 캡처한 사용자 IP 및 사용자 에이전트 데이터는 고객에 대한 매우 풍부한 정보를 나타냅니다. 사용자 IP 및 사용자 에이전트의 모든 정보를 사용하여 지도에서 웹 트래픽의 오리진과 고객이 사용하는 운영 체제 및 브라우저를 보여주는 다음와 같은 시각화를 만들 수 있습니다. 수집 노드 파이프라인을 사용하여 APM 데이터를 보강하고 변환할 수 있습니다. 이 모든 정보를 통해 애플리케이션을 더욱 지능적으로 최적화할 수 있습니다.

Kibana 대시보드에서 Elastic APM 데이터 시각화

분산 추적을 통해 전체적인 상황 파악

또한, 웹 브라우저에서 백엔드 데이터베이스까지 전체 트랜잭션을 한눈에 볼 수 있도록 백엔드 Spring Boot 애플리케이션도 계측할 예정입니다. Elastic APM 분산 추적을 통해 이를 수행할 수 있습니다.

RUM 에이전트에서 분산 추적 구성

분산 추적은 RUM 에이전트에서 기본적으로 활성화됩니다. 그러나 동일한 오리진에 대한 요청만 포함합니다. 교차 오리진 요청을 포함하려면 distributedTracingOrigins 구성 옵션을 설정해야 합니다. 그리고 다음 섹션에서 설명하겠지만, 백엔드 애플리케이션에서 CORS 정책도 설정해야 합니다.

이 애플리케이션의 경우 http://localhost:3000에서 프론트엔드를 제공합니다. http://localhost:8080에 수행된 요청을 포함하려면 React 애플리케이션에 distributedTracingOrigins 구성을 추가해야 합니다. 이 작업은 rum.js 내부에서 수행됩니다. 코드는 이미 포함되어 있으므로 주석 처리를 제거하기만 하면 됩니다.

var apm = initApm({
  ...
  distributedTracingOrigins: ['http://localhost:8080']
})

이렇게 하면 에이전트에 분산 추적 HTTP 헤더(elastic-apm-traceparent)를 http://localhost:8080에 수행된 요청에 추가하라고 지시할 수 있습니다.

서버 측에서 기본 계측 기능을 사용하려면 Java 에이전트를 다운로드하고 해당 에이전트로 애플리케이션을 시작해야 합니다. 다음은 제가 백엔드 Spring Boot 애플리케이션을 실행하도록 Eclipse 프로젝트를 구성한 방법입니다. 여러분은 자체 APM URL 및 APM 토큰을 사용하여 이를 구성해야 합니다.

-javaagent:/Users/aquan/Downloads/elastic-apm-agent-1.4.0.jar 
-Delastic.apm.service_name=cardatabase 
-Delastic.apm.application_packages=com.packt.cardatabase
-Delastic.apm.server_urls=https://aba7c3d90b0b4820b05b0a9df44c096d.apm.us-central1.gcp.cloud.es.io:443 
-Delastic.apm.secret_token=jeUWQhFtU9e5Jv836F

백엔드 데이터를 Elastic APM으로 보내도록 Eclipse 구성

이제 브라우저에서 차량 목록을 새로 고쳐 다른 요청을 생성합니다. Kibana APM UI로 이동하여 마지막 “car list” 페이지 로드를 확인합니다. 다음 스크린샷과 비슷한 화면이 보일 것입니다. 보시다시피, 브라우저의 클라이언트 측 성능 데이터와 JDBC 액세스를 포함한 서버 측 성능 데이터가 모두 하나의 분산 추적에서 정확하게 표시됩니다! 분산 추적의 부분별로 색상이 다른 것을 알 수 있습니다. 이는 서버 측에서 사용자 정의 계측을 수행할 필요가 없는, 에이전트로 애플리케이션을 시작하기만 하면 되는 기본 추적 기능임을 유념해 주세요. Elastic APM과 분산 추적의 힘을 느껴보십시오!

Elastic APM의 분산 추적(백엔드 및 프론트엔드)

위의 타임라인 시각화를 주의 깊게 보고 있는 독자라면 백엔드에서 데이터가 계속 제공되고 있는데 왜 “Car List” 페이지 로드 트랜잭션이 domInteractive 시간인 193ms로 끝나는지 궁금할 것입니다. 좋은 지적입니다! 이는 페치 호출이 기본적으로 비동기식이기 때문입니다. 브라우저는 웹 서버에서 제공되는 모든 ‘정적’ HTML 콘텐츠를 로드했기 때문에 193ms에 모든 HTML 구문 분석을 완료했고 DOM 구성이 완료되었다고 ‘생각’합니다. 반면, React는 여전히 백엔드 서버에서 비동기식으로 데이터를 로드하고 있습니다.

교차 오리진 리소스 공유(CORS)

RUM 에이전트는 분산 추적에서 퍼즐의 한 조각일 뿐입니다. 분산 추적을 사용하려면 다른 구성 요소도 올바르게 구성해야 합니다. 일반적으로 구성해야 하는 것 중 하나는 “악명 높은” CORS, 즉 교차 오리진 리소스 공유입니다! 프론트엔드 및 백엔드 서비스는 일반적으로 별도로 배포되기 때문입니다. 동일한 오리진 정책을 사용하는 경우 CORS를 올바르게 구성하지 않으면 다른 오리진에서 백엔드로의 프론트엔드 요청이 실패합니다. 기본적으로, CORS는 서버 측에서 다른 오리진에서 들어오는 요청이 허용되는지 확인하는 방법입니다. 교차 오리진 요청 및 이 프로세스가 필요한 이유에 대한 자세한 내용은 MDN 페이지에서 Cross-Origin Resource Sharing 섹션을 참조하십시오.

이것은 우리에게 무엇을 의미할까요? 두 가지의 의미가 있습니다.

  1. 앞에서 한 것과 같이 distributedTracingOrigins 구성 옵션을 설정해야 합니다.
  2. 이 구성을 사용하면 RUM 에이전트는 실제 HTTP 요청 전에 HTTP OPTIONS 요청을 전송하여 모든 헤더와 HTTP 메서드가 지원되고 오리진이 허용되는지 확인합니다. 특히 http://localhost:8080은 다음 헤더가 포함된 OPTIONS 요청을 받게 됩니다.

    Access-Control-Request-Headers: elastic-apm-traceparent
    Access-Control-Request-Method: [request-method]
    Origin: [request-origin]
        
    그리고 APM 서버는 다음 헤더와 200 상태 코드로 응답해야 합니다.

    Access-Control-Allow-Headers: elastic-apm-traceparent
    Access-Control-Allow-Methods: [allowed-methods]
    Access-Control-Allow-Origin: [request-origin]
        

Spring Boot 애플리케이션의 MyCorsConfiguration 클래스가 바로 이러한 기능을 수행합니다. 이를 위해 Spring Boot를 구성하는 방법은 여러 가지가 있지만, 여기서는 필터 기반 접근 방식을 사용하고 있습니다. 모든 HTTP 헤더 및 HTTP 메서드와 더불어 모든 오리진의 요청을 허용하도록 서버 측 Spring Boot 애플리케이션을 구성하고 있습니다. 프로덕션 애플리케이션에서는 이를 공개하고 싶지 않을 수 있습니다.

@Configuration
public class MyCorsConfiguration {
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

마지막으로, RUM 에이전트의 또 하나의 강력한 기능인 소스 맵을 살펴보겠습니다. 소스 맵은 암호화된 축소 코드 대신 원래 소스 코드에서 오류가 발생한 위치를 정확하게 알려줌으로써 애플리케이션의 오류를 훨씬 쉽게 디버깅할 수 있게 해줍니다.

소스 맵으로 쉽게 디버깅

성능상의 이유로 프로덕션 배포에서 JavaScript 번들을 축소하는 것이 일반적입니다. 그러나 축소된 코드를 디버깅하는 것은 본질적으로 어렵습니다. 아래 스크린샷은 RUM 에이전트가 프로덕션 빌드에서 캡처한 오류 메시지를 보여줍니다. 보시다시피, 예외 스택 추적은 그다지 의미가 없습니다. 축소된 코드이기 때문입니다. 모든 오류 줄에는 2.5e9f7401.chunk.js와 같은 javascript 파일이 표시되며, 축소가 수행된 방식 때문에 항상 "줄 1"을 가리킵니다. 여기서 개발한 소스 코드를 정확하게 볼 수 있다면 좋지 않을까요?

Elastic APM의 축소된 코드는 소스 맵으로 해결 가능

바로 소스 맵이 도움이 됩니다. 사용자는 번들의 소스 맵을 생성하고 이를 APM 서버에 업로드할 수 있습니다. 그러면 APM 서버는 축소된 코드의 오류를 원래 소스 코드로 변환하고 훨씬 더 쉽게 이해할 수 있는 오류 메시지를 제공할 수 있습니다.

프론트엔드 React 애플리케이션에서 이를 수행하는 방법을 살펴보겠습니다. 애플리케이션을 위한 프로덕션 빌드를 빌드하고 소스 맵을 업로드하겠습니다. 다음 명령을 사용하면 프로덕션 빌드를 생성할 수 있습니다.

npm run build

빌드가 끝나면 다음 메시지가 표시됩니다.

The build folder is ready to be deployed.
You may serve it with a static server:
  serve -s build

프로덕션 빌드에 대한 자세한 내용은 여기 https://facebook.github.io/create-react-app/docs/production-build를 참조하십시오.

serve를 설치하지 않은 경우 이를 설치해야 합니다.

npm install -g serve

다음 명령을 사용하여 프로덕션 모드에서 React 애플리케이션을 제공합니다.

serve -s build

React 애플리케이션이 프로덕션 모드일 때는 Chrome용 React 개발자 도구 아이콘의 배경이 어두운색입니다. React 애플리케이션이 개발 모드일 때는 아이콘의 배경이 빨간색입니다. 실행하고 있는 것이 프로덕션 빌드인지 확인합니다.

이제 오류 버튼을 클릭하여 오류를 생성하고 Kibana APM UI에서 확인하면 이전 스크린샷에서처럼 축소된 오류 스택을 볼 수 있습니다.

소스 맵을 로드하고 그 마법을 확인해보죠! 소스 맵은 $APP-PATH/carfront/build/static/js 디렉터리에 생성됩니다. 이 디렉터리로 이동하면 3개의 JavaScript 파일에 대한 3개의 소스 맵이 있습니다. 다음 명령을 실행하여 이를 APM 서버로 업로드합니다. URL, 파일 이름 및 기타 파라미터를 애플리케이션 버전, 빌드 및 환경에 맞게 변경해야 합니다. 또한, APM 서버의 인증 토큰을 사용해야 합니다.

curl https://aba7c3d90b0b4820b05b0a9df44c096d.apm.us-central1.gcp.cloud.es.io:443/v1/rum/sourcemaps -X POST \
  -F sourcemap="@./main.b81677b7.chunk.js.map" \
  -F service_version="0.90" \
  -F bundle_filepath="http://localhost:5000/static/js/main.b81677b7.chunk.js" \
  -F service_name="carfront" \
  -H "Authorization: Bearer jeUWQhFtU9e5Jv836F"
curl https://aba7c3d90b0b4820b05b0a9df44c096d.apm.us-central1.gcp.cloud.es.io:443/v1/rum/sourcemaps -X POST \
  -F sourcemap="@./runtime~main.fdfcfda2.js.map" \
  -F service_version="0.90" \
  -F bundle_filepath="http://localhost:5000/static/js/runtime~main.fdfcfda2.js" \
  -F service_name="carfront" \
  -H "Authorization: Bearer jeUWQhFtU9e5Jv836F"
curl https://aba7c3d90b0b4820b05b0a9df44c096d.apm.us-central1.gcp.cloud.es.io:443/v1/rum/sourcemaps -X POST \
  -F sourcemap="@./2.5e9f7401.chunk.js.map" \
  -F service_version="0.90" \
  -F bundle_filepath="http://localhost:5000/static/js/2.5e9f7401.chunk.js" \
  -F service_name="carfront" \
  -H "Authorization: Bearer jeUWQhFtU9e5Jv836F"

서비스 버전은 문자열이며 React 애플리케이션에서 구성한 서비스 버전과 정확히 일치합니다. 이 예제에서는 service_version="0.90"으로 업로드하고 있으며, 애플리케이션의 서비스 버전이 "0.90"으로 설정되어 있습니다. service_version="0.9"(마지막 0 누락)로 소스 맵을 로드하면 작동하지 않습니다!

서비스 맵이 APM 서버에 로드되면 Dev Tools에서 다음 요청을 사용해 모든 소스 맵을 찾을 수 있습니다(버전에 따라 다를 수 있음).

GET apm-6.6.1-sourcemap/_search

다른 오류를 생성하고 APM UI 오류 탭에서 스택 추적을 다시 확인합니다. 다음 스크린샷과 같이 원래 소스 코드가 정확하게 반영된 스택 추적이 표시됩니다! 이제 훨씬 쉽게 문제를 디버깅하고 식별할 수 있습니다!

소스 맵을 구현한 후 이제 Elastic APM에 실제 코드가 표시됨

요약

이 블로그를 통해 Elastic RUM을 사용하면 애플리케이션을 쉽고 간단하게 계측할 수 있을 뿐만 아니라 그 기능이 매우 강력하다는 것을 확실하게 알게 되셨길 바랍니다. 다른 백엔드 서비스용 APM 에이전트와 함께 RUM은 분산 추적을 통해 최종 사용자 관점에서 애플리케이션 성능에 대한 전체적인 보기를 제공합니다.

다시 한번 말씀드리자면, Elastic APM을 시작하려면 Elastic APM 서버를 다운로드하여 로컬에서 실행하거나, Elastic Cloud 체험판 계정을 생성하고 몇 분 만에 클러스터를 가동할 수 있습니다.

언제나처럼 토론을 시작하거나 궁금한 점이 있으시면 Elastic APM 포럼에 문의하시기 바랍니다. 즐겁게 RUM을 활용하세요!