그동안 프론트엔드에서 발생하는 콘솔 에러등의 에러는 개발자 테스트나 QA를 통해 확인하는게 전부였어요. 백엔드의 에러만 파악하는 것으로는 사용자의 경험을 더 좋게 만드는데 한계가 있었기에 프론트엔드에서 발생하는 에러도 확인하고 개선하고 싶었어요.
프론트엔드에서 에러가 발생하면 에러 이벤트를 백엔드에서 로그를 저장하는 방식을 생각해봤어요. 다만 프론트의 에러를 백엔드에서 의존한다는게 좀 어색하기도 했고, 로그 저장을 위한 클라우드 워치 로그 그룹 생성, 구현 코드 등 할게 좀 있어보였어요.
그리고 백엔드를 호출하는 코드와 관련해서 에러가 생기면 놓치는 부분이 있을 수 있어요.
AWS Cognito
인증을 통해 프론트에서 직접 클라우드 워치로 직접 로그를 쌓는 것도 가능해보였어요. 하지만 모든 사용자에게 Cognito 인증을 시키는건 오버스펙이었고, 인증을 안하고 하자니 공개된 pool ID 를 통해 악의적인 데이터 전송이 가능해서 문제가 있을거라 판단했어요.
처음엔 프론트에서 비인증된 Cognito를 통해 S3에 접근하는 로직이 있어서 고려해봤던건데, put object
권한만 열려있어서 데이터 유출을 막았고, S3 permission CORS AllowedOrigins 설정으로 외부 접근을 차단되어 있었습니다. 로그를 보내는건 이런 차단 기능은 제공하지 않아 별도로 구현해야할거 같았고, 오버스펙으로 판단했어요.
외부 전문 로깅 서비스를 이용하면 보다 많은 정보를 쉽게 얻을 수 있겠지만, 현재 프론트엔드 로깅을 확인하는 단계는 그정도로 전문성을 요구하는 단계는 아니었어요. 일단 에러를 잡아본 적이 없으니 로깅을 해보고, 필요성이 얼마나 되는지 판단하는 단계라고 볼 수 있어요.
Sentry도 무료 요금제가 있긴 하지만, 한명 밖에 모니터링을 하지 못하고 기능도 제한적이라 일단 보류했어요. 다른 방안을 못찾으면 테스트로 써봐도 좋을 것 같다고 생각했어요.
AWS 인프라를 사용하는 환경에서 RUM은 가장 매력적인 옵션이었어요. 외부 서비스처럼 고정비용이 있는건 아니고, 이벤트 건당 비용과 로그 저장, 쿼리 비용 정도가 들고, 추가 옵션을 통해 클라우드 워치 로그에도 저장할 수 있어서 관리하기 편할 것 같았어요.
그리고 간단한 대시보드도 제공해서 테스트해보기 적당한 툴이라고 판단했어요.
실제 사용자의 웹 애플리케이션 경험을 실시간으로 모니터링하는 서비스예요. 다음과 같은 핵심 기능을 활용할 수 있어요:
CloudWatch RUM은 웹 애플리케이션 성능에 미치는 영향이 최소화되도록 설계되었으며, 비동기 로딩과 효율적인 데이터 전송 방식을 사용하고 있어요. 요금은 수신된 RUM 이벤트 수에 따라 부과되며, 페이지 뷰, JavaScript 오류, HTTP 오류 등이 이벤트로 간주 돼요.
CloudWatch RUM 문서 / aws-rum-web github
RUM 설정을 위해서는 프론트엔드 코드를 수정할 수 있어야하고, AWS RUM 설정 권한이 있어야해요.
rum:CreateAppMonitor
, rum:GetResourcePolicy
과 같은 권한들이 필요해요. 설정하면서 부족한 IAM 권한들을 채워가면서 작업하세요.
*IAM 은 AWS 내 각 툴에서 어떤 권한을 쓸 수 있는지 관리하는 기능이예요.
특정 클라이언트를 위한 모니터 그룹을 App Monitor
라고 하며, Cloudwatch > Application Signals > RUM
에서 설정 할 수 있어요.
App monitor의 이름과 모니터링 할 클라이언트의 도메인 정보를 입력해요. xxx.my-service-app.com 처럼 명확히 입력할 수도 있고, 여러 서브 도메인의 정보를 한 번에 모으고자 하면 예시처럼 * 를 활용해 해당 패턴을 가진 모든 도메인을 대상으로 삼을 수도 있어요. 참고로 로컬에서 개발하는 환경의 이벤트도 모니터링 할 수 있어요.
어떤 이벤트를 수집할 지 선택해요. Performance telemetry
는 성능 관련 수치를 확인하기 위한 이벤트인데, 페이지 로딩 시간 같은 것들이 포함 돼요. 그런데 너무 많은 이벤트가 잡혀서 이벤트 건 당 과금을 하는 RUM에 많은 돈을 지불하게 될 수도 있어요…! 그래서 전 옵션에서 뺐구요, 만약 성능을 측정하려는 목적으로 사용하신다면 뒤쪽 설정에 있는 Session samples
를 조정해서 좀 더 적은 샘플로 이벤트를 받아보는 것도 좋다고 생각해요.
JavaScript errors
는 우리가 확인하고 싶었던 콘솔에 잡혔던 에러들을 수집해주는 옵션이예요. 아래의 raw event 형식으로 이벤트가 쌓이게 돼요.
{
"event_timestamp": 1745907666000,
"event_type": "com.amazon.rum.js_error_event",
"event_id": "xxx",
"event_version": "1.0.0",
"log_stream": "2025-4-29T15",
"application_id": "xxxx",
"application_version": "1.0.0",
"metadata": {
"version": "1.0.0",
"browserLanguage": "ko",
"browserName": "Chrome",
"browserVersion": "135.0.0.0",
"osName": "Mac OS",
"osVersion": "10.15.7",
"deviceType": "desktop",
"platformType": "web",
"domain": "xxx.co",
"aws:client": "arw-module",
"aws:clientVersion": "1.22.1",
"countryCode": "KR",
"subdivisionCode": "11"
},
"user_details": {
"userId": "xxx",
"sessionId": "xxx"
},
"event_details": {
"version": "1.0.0",
"type": "ReferenceError",
"message": "originFilter is not defined",
"stack": "ReferenceError: originFilter is not defined\n at VueComponent xxx"
}
}
unhandled error 는 모두 잡히게 되며, 코드 스니펫 설정에서 무시하고 싶은 에러를 설정할 수 있어요. stack trace 길이는 디폴트 1,000자로 제한되고 있고, 이것도 코드 스니펫에서 설정할 수 있어요.
다만 압축, 난독화 된 소스 기준으로 스택이 기록되기 때문에 다소 보기 어려울 수 있는데요, source map 을 S3 에 저장한 후 URI 를 제공하면, 원본 기준으로 스택을 제대로 기록해준다고 해요. 아직은 필요성을 못 느껴서 작업은 안했어요. 선택한 이후의 데이터에 대해서만 매핑이 돼요.
HTTP errors
에서는 http request 에서 발생한(예를 들어 백엔드 요청에서 에러 응답) 에러를 수집하는 옵션이예요. 디폴트로 status code 2XX 응답을 제외한 모든 응답을 수집해요. 코드 스니펫 설정에서 허용/제외하고 싶은 url 을 정규식으로 설정할 수 있고, 2XX 응답도 포함하도록 설정할 수도 있어요.
💡Plugins 에 대한 세부 설정 정보는 aws-rum-web github에서 자세히 확인할 수 있어요. 특정 DOM element에 대한 이벤트를 수집하게 하는 옵션 설정도 포함되어있어요.
Custom events
는 RUM에서 설정한 이벤트 외에 커스텀한 이벤트를 받을지 여부를 결정하는 설정이예요. 코드 내에서 특정 이벤트를 만들어 수집하고 싶을 때 사용하면 돼요. 원하는 곳에 recordEvent
로 원하는 이벤트를 추가하면 돼요. 아래 예시는 Vue.js 에서 warning 에 대해 추가로 이벤트를 받아보고 싶을 때 작성한 코드입니다.
const awsRum = new AwsRum(xxx)
Vue.config.warnHandler = (message, vm, stack) => {
awsRum.recordEvent('warning', {
type: 'vue_warn',
url: window.location.href,
message,
stack,
})
}
커스텀 이벤트로 설정한 부분은 raw event
내 event_details
정보에서 데이터가 쌓이는걸 확인할 수 있어요.
{
"event_timestamp": 1745995364000,
"event_type": "warning",
"event_id": "xxx",
"event_version": "1.0.0",
"log_stream": "2025-4-30T15",
"application_id": "xxx",
"application_version": "1.0.0",
"metadata": {
xxx
},
"user_details": {
"userId": "xxx",
"sessionId": "xxx"
},
"event_details": {
"stack": "\n\nfound in\n\n---> <ChargeManagementList> at xxx",
"type": "vue_warn",
"message": "Error in v-on handler (Promise/async): \"TypeError: this.abc is not a function\"",
"url": "https://xxx"
}
}
더 자세한 내용은 문서를 참고해보세요!
쿠키
를 설정하면 랜덤으로 설정된 세션, 유저 ID 기반으로 해당 유저의 여정, 데이터를 수집하게 돼요. 만약 쿠키를 설정하지 않으면, 여러 이벤트를 발생시킨 유저가 한 유저로 판단되지 않아 실제 특정 유저에게 어떤 일이 일어났는지 파악하기 어렵게 돼요. 예를 들어 특정 에러가 여러번 발생했는데, 그 에러를 한 유저가 겪은 에러인지 아니면 여러 유저가 각각 한 번씩 겪은 에러인지 알 수 없어요. 그래서 전 쿠키 설정을 추천해요.
문서에서는 쿠키를 설정하지 않으면 수집할 수 없는 정보를 아래처럼 명시하고 있어요.
참고로 쿠키는 cwr_s
와 cws_u
로 설정됩니다.
Session samples
는 수집할 세션의 모수를 설정하는 곳 인데요. 디폴트는 100% 입니다. 퍼센트를 줄이면 데이터는 줄어들겠지만 이벤트 기반 과금이 되는 RUM 으로써는 비용을 줄일 수 있어요. 선호에 따라 선택하시면 될 것 같아요.
Data storage
는 이벤트 로그 데이터를 장기 보관하고 싶을 때 사용하는 설정이예요. 기본적으로 RUM은 30일간만 데이터를 보관해요. 이 이상 보관하고 싶다면 이 설정을 통해 Cloudwatch 에 로그그룹을 새로 생성해 저장할 수 있어요. 다만 로그 저장 및 보관에 대한 비용은 부과돼요.
로그 그룹이 생겨도 디폴트 보유기간은 30일이기에 더 늘리고 싶다면 로그그룹에서 retention setting
을 변경해야해요.
Resource Based Policy
는 특정 자원(여기서는 이번에 만들어지는 App Monitor 인 RUM)에 대한 접근 정책을 설정하는 옵션이예요. 이 옵션을 선택해서 정책을 만들어줘야 퍼블릭으로 열린 우리 서비스에서 이벤트를 전송할 수 있어요.
옵션을 선택하면 아래와 같은 정책이 자동으로 작성됩니다. 모든 클라이언트로부터 rum의 이벤트 전송을 허용한다는 정책이예요.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "rum:PutRumEvents",
"Resource": "arn:aws:rum:ap-northeast-2:xxx:appmonitor/",
"Principal": "*"
}
]
}
만약 내부 테스트나 일련의 이유로 특정 IP를 제한하고 싶다면, 아래처럼 Deny
설정을 추가하면 돼요.
이 문서를 참조해 보세요.
{
...
,
{
"Effect": "Deny",
"Action": "rum:PutRumEvents",
"Resource": "arn:aws:rum:region:accountID:appmonitor/app monitor name",
"Principal" : "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": "************"
}
}
}
...
}
AWS Cognito
를 이용해서 접근 권한을 설정하는 옵션인데요, 위의 Resource Based Policy
를 설정했다면 따로 설정하지 않아도 된다고 문서에서 설명은 하고 있어요. 추가로 권한제어를 하실 분은 사용하시면 될거 같고, 선택하지 않고 저장해도 위처럼 세번째 옵션이 선택되서 저장되어있더라고요.
이벤트를 수집할 페이지를 설정하는 옵션이예요. 디폴트로는 모든 페이지가 선택되어있고, 특정 페이지에서의 이벤트를 포함하거나 제외하려면 지정하면 돼요.
설정을 완료하고 나면 TypeScript, JavaScript, HTML 중 하나를 선택해 적용할 수 있는 코드 스니펫을 제공해줘요. HTML 을 사용할 경우 Ad blockers 한테 막힐 수도 있어서 추천하지 않는다고는 하네요.
코드 스니펫에도 세부 설정을 더 추가할 수 있는데, github의 설명을 참고해서 추가해보세요.
적용을 위해선 먼저 aws-rum-web
을 설치해줘요.
npm install aws-rum-web
설치 후에는 main.js
나 App.vue
처럼 프론트의 js가 실행되는 시작점에 코드 스니펫을 추가해주면 돼요.
App.vue
에 설정할 경우 mounted
훅 같은 곳에 위치하면 수집 시점이 빨라 적당한거 같아요.
아래와 같은 느낌이예요.
// App.vue
import { AwsRum } from 'aws-rum-web';
async created() {
initCloudwatchRum()
...
}
initCloudwatchRum() {
try {
const config = {
sessionSampleRate: 1,
identityPoolId: "ap-northeast-2:xxx",
endpoint: "https://dataplane.rum.ap-northeast-2.amazonaws.com",
telemetries: ["errors","http"],
allowCookies: true,
enableXRay: false,
sessionEventLimit: 0
};
const APPLICATION_ID = 'xxx';
const APPLICATION_VERSION = '1.0.0';
const APPLICATION_REGION = 'ap-northeast-2';
const awsRum = new AwsRum(
APPLICATION_ID,
APPLICATION_VERSION,
APPLICATION_REGION,
config
);
} catch (error) {
// Ignore errors thrown during CloudWatch RUM web client initialization
}
}
잘 설정 됐다면 개발자도구에서 주기적으로 이벤트가 발송되는걸 볼 수있어요.
발송 규칙, 주기에 좀 더 알아보자면 코드 스니펫에 설정된 config
와 관련이 있어요.
[이벤트가 발송이 안되던 이슈]
설정을 해도 이벤트가 불규칙하게 발송되거나 발송이 안되는 이슈가 있었어요. https://github.com/aws-observability/aws-rum-web/blob/main/docs/cdn_troubleshooting.md 참고해서 sessionEventLimit:0
적용하니 해결 아닌 해결은 됐는데, 애초에 최초에는 리밋이 차지 않았으니 이벤트 발송이 되어야하는데, 안되는 이유는 못 찾았어요. 그런데 다음날 옵션 빼고 하니 정상적으로 이벤트 발송…
다른 날에 또 다른 AppMonitor
를 생성해도 동일한 이슈가 생기는걸 보니 반영이 늦게 되는건가 싶긴 했어요.
테스트 하는게 아니라면 sessionEventLimit
옵션은 삭제해서 디폴트로 두는게 좋아요. 이 옵션 자체가 이벤트가 발생하면 바로 발송하겠다는 옵션이라 계속해서 request가 발생해 클라이언트에 무리를 줄 수 있어요.
일부 수집된 대시보드 데이터도 한 번 보여드릴게요.
[Performance]
[Errors]
[Http requests]
[Events]
이제 에러가 발생하면 수집이 되게는 했습니다. 하지만 수집만으로 끝나면 에러가 발생해도 모를 수 있어요. 이걸 알려줄 수 있는 알림을 만들어야해요.
알림 방식은 CloudWatch Alarm
을 사용했어요.
CloudWatch 에서는 특정 Metric의 수치가 지정한 임계치를 넘어서면 알람을 보낼 수 있는 Alarms
라는 기능을 제공합니다. RUM
은 JsErrorCount
, SessionDuration
, Http5xxCount
등 여러 Metric을 제공하고 있고, 현재 우리는 JS 에러를 잡기 위한 모니터링을 설정 중이므로 적절할 것으로 생각했어요.
CloudWatch > Alrams
에 들어가면 Create alarm
을 통해 알람을 생성하는 곳으로 접근할 수 있어요.
metric(수치, 지표)을 선택하는 곳에서 RUM > application_name 을 선택하면 여러 옵션이 나타납니다.
지금 우리에게 필요한 JsErrorCount
를 선택합니다.
구체적인 알람 조건을 설정합니다.
1 분간 총 카운트 수의 합이 1 이상인 경우 알람이 발동하도록 설정했어요.
수치가 알람 상태가 되거나 다시 정상 상태로 돌아가면 슬랙 chatbot 과 연결된 SNS에 이벤트를 발행해 슬랙이 가도록 설정했어요.
Lambda 액션도 설정할 수 있는데, 로그를 읽어서 메시지를 가공해서 보내고 싶으면 사용해도 될거 같아요. 이번에는 사용하지 않았어요.
설정 완료 후 에러 이벤트가 발생하면 슬랙에 위와 같이 메시지로 알람을 보내줍니다. 상태가 정상이 될 경우 빨간 표시를 정상인 초록 표시로 변경되는걸 기대했으나, 추가 이벤트가 쌓이지 않아 정상으로 변경된걸 인식하지 못해 정상으로 변경은 안해주는거 같고요.
알람 메시지 만으로는 실제 어떤 에러가 생겼는지 직접 로그를 찾아가야하는 불편함이 존재하긴 해요. Query logs
액션을 통해 로그를 바로 조회하는걸 구현하려고 하고 있긴 한데, 현재 이번에 만든 로그 그룹이 조회가 되지 않아 관련 설정부터 찾아봐야할거 같습니다.
비용에는 아래와 같은 항목들이 들어갈 것으로 예상 돼요.
아직 사용자도 적고, 이벤트 발행 허용량도 제한이 되어있어서 무한 에러가 발생하지 않는 이상 월 $1도 발생하지 않을 것으로 예상 됩니다.