49장. 로그와 장애 대응의 기초
서비스를 배포하고 나면 많은 초보자가 이렇게 생각합니다. "끝났다!"
하지만 운영의 세계에서는 그때부터 진짜 이야기가 시작됩니다. 사용자는 예상과 다르게 행동하고, 네트워크는 가끔 삐끗하고, 데이터는 늘 우리가 바라는 모양으로만 오지 않습니다. 어제까지 멀쩡하던 화면이 오늘 갑자기 하얗게 비어 보일 수도 있습니다. 이때 개발자를 살려주는 것이 바로 로그입니다.
로그는 서비스가 남기는 발자국입니다. 누가 들어왔는지, 어디서 실패했는지, 서버가 무슨 말을 남기고 멈췄는지, 프론트엔드가 어떤 에러를 만났는지를 보여줍니다. 배포는 서비스를 세상에 내보내는 일이고, 로그는 그 서비스가 세상에서 어떻게 살아가는지 알려주는 기록입니다.
이 장의 목표는 거창하지 않습니다. 대규모 장애 대응 전문가가 되는 것이 아니라, 작은 웹 서비스를 운영하는 초보자가 "문제가 생겼을 때 어디부터 봐야 하는지" 아는 상태가 되는 것입니다. 그 감각이 생기면 장애는 공포 영화가 아니라 추적 가능한 사건이 됩니다.
![[9부] 로그와 장애 대응의 기초](https://images.tilnote.io/pages/dbd79827-80ab-4791-9651-fd4bd7970f5d.webp)
![[9부] 로그와 장애 대응의 기초](https://images.tilnote.io/pages/267dad33-dc55-4796-9cbf-7afece5f4854.webp)
![[9부] 로그와 장애 대응의 기초](https://images.tilnote.io/pages/27035ab1-0214-46e3-9a63-ca4eab90b455.webp)
로그는 서비스의 블랙박스다
비행기에는 블랙박스가 있습니다. 사고가 나면 그 안의 기록을 바탕으로 원인을 추적합니다. 웹 서비스의 로그도 비슷합니다. 사용자가 "로그인이 안 돼요"라고 말해도, 로그가 없다면 우리는 거의 점쟁이처럼 추측만 해야 합니다. 반대로 로그가 있으면 실패한 API 요청, 에러 메시지, 시간대, 사용자 ID, 요청 경로를 따라가며 원인을 좁혀갈 수 있습니다.
운영에서 중요한 것은 "문제가 있었는가?"보다 "무슨 일이 있었는가?"를 복원하는 능력입니다. 보안 사고 대응 분야에서도 사고 대응은 준비, 탐지와 분석, 확산 방지와 복구, 사후 개선의 흐름으로 설명되는데, 이 전 과정의 중심에는 기록과 분석이 있습니다.1 작은 개인 프로젝트라도 이 흐름은 똑같이 적용됩니다. 다만 더 단순하게, 더 실용적으로 가져가면 됩니다.
초보자 관점에서 로그의 가장 큰 역할은 세 가지입니다.
문제를 재현하지 못해도 단서를 남긴다.
사용자가 겪은 실패를 개발자 시점으로 번역해 준다.
같은 문제가 다시 생기지 않도록 개선 포인트를 알려 준다.
즉, 로그는 단순한 출력문 모음이 아니라 운영의 기억장치입니다.
콘솔 출력만으로는 부족하다
개발할 때는 console.log()가 무척 편합니다. 값이 잘 들어오는지 확인하고, 함수가 실행됐는지 보고, 급할 때는 이것만큼 빠른 도구도 없습니다. 하지만 운영 환경에서는 이야기가 달라집니다. 사용자의 브라우저 콘솔에 찍힌 메시지는 우리가 자동으로 모으지 않으면 그대로 사라지고, 서버 콘솔에 흩어진 문자열은 검색과 집계가 어렵습니다. 그래서 운영 로그는 "눈으로 잠깐 보는 출력"이 아니라 "나중에 찾을 수 있는 기록"이어야 합니다.2
특히 초보자가 자주 하는 실수는 이런 것입니다. 오류가 났는데도 성공 경로만 기록하고, 에러 메시지는 남기지 않는 경우입니다. 또는 너무 많은 로그를 남겨서 정작 중요한 실패가 묻혀버리기도 합니다. 반대로 아무 로그도 안 남기면 장애가 났을 때 할 수 있는 말은 하나뿐입니다.
"이상하네요. 제 컴퓨터에서는 되는데요."
운영 환경의 로그는 최소한 다음 조건을 갖추는 것이 좋습니다.23
나중에 다시 찾을 수 있어야 한다.
어느 시점, 어느 요청, 어느 사용자와 관련 있는지 알 수 있어야 한다.
심각도에 따라 구분할 수 있어야 한다.
비밀번호나 토큰 같은 민감한 정보는 남기지 않아야 한다.
좋은 로그와 나쁜 로그
같은 문제를 기록하더라도 로그의 품질은 크게 다릅니다.
나쁜 로그는 이런 식입니다.
에러 발생
저장 실패
뭔가 이상함이 메시지는 사람이 봐도 답답하고, AI가 봐도 단서가 부족합니다.
좋은 로그는 실패의 맥락을 함께 남깁니다.
{
"level": "error",
"event": "post_create_failed",
"requestId": "req_7f23",
"userId": "user_102",
"route": "/api/posts",
"message": "Database insert failed",
"timestamp": "2026-04-23T10:30:00.000Z"
}이런 형태를 구조화 로그라고 합니다. 문자열 한 줄로 퉁치는 대신, 기계가 읽기 쉬운 키와 값 형태로 기록하는 방식입니다. 구조화 로그를 쓰면 나중에 requestId로 묶어서 찾거나, level=error만 따로 보거나, 특정 event가 얼마나 자주 발생하는지 집계하기 쉬워집니다.23
입문 단계에서는 복잡한 로그 시스템을 바로 붙일 필요는 없습니다. 다만 생각의 방향은 꼭 가져가면 좋습니다. "무슨 일이 있었는지 문장으로만 남기지 말고, 검색 가능한 정보로 남기자." 이 습관이 나중에 서비스가 커졌을 때 엄청난 차이를 만듭니다.
로그 레벨부터 정해 두자
로그가 많아질수록 중요한 것과 덜 중요한 것을 구분해야 합니다. 그래서 로그에는 보통 레벨을 붙입니다.23
debug: 개발 중 자세히 보는 정보info: 정상적인 흐름 기록warn: 당장 터지진 않았지만 이상 신호가 있는 상황error: 사용자가 영향을 받는 실패fatal또는critical: 서비스가 계속 동작하기 어려운 치명적 실패
초보자는 이 기준만 기억해도 충분합니다. 회원가입 성공은 info, 글 저장 재시도 후 성공은 warn, DB 저장 실패는 error, 서버가 아예 부팅되지 않는 상황은 fatal에 가깝습니다.
중요한 것은 레벨을 멋으로 붙이지 않는 것입니다. 사소한 것도 전부 error로 남기면 진짜 에러가 묻힙니다. 반대로 사용자가 실제로 실패했는데 info로 남기면 운영 중 놓치기 쉽습니다. 로그 레벨은 일종의 우선순위 표시입니다.
무엇을 로그로 남겨야 할까
입문자가 만드는 첫 풀스택 서비스에서는 모든 것을 다 기록할 필요는 없습니다. 대신 핵심 흐름 몇 군데를 정확히 남기면 충분합니다.
첫째, 인증 관련 흐름입니다. 회원가입, 로그인, 로그아웃, 권한 없음 같은 이벤트는 운영 중 가장 먼저 확인하게 됩니다. 단, 비밀번호와 토큰 원문은 절대 로그에 남기면 안 됩니다.2
둘째, 핵심 비즈니스 기능입니다. 이 책의 실습 서비스라면 글 작성, 수정, 삭제, 검색 같은 동작이 여기에 해당합니다. 예를 들어 글 작성 실패가 났다면 어떤 사용자였는지, 어느 API에서 실패했는지, DB 저장 직전이었는지 이후였는지를 남기면 추적이 쉬워집니다.
셋째, 외부 서비스 연결 지점입니다. Supabase 인증, 데이터베이스 쿼리, 파일 업로드, 메일 발송처럼 우리 코드 바깥과 연결되는 곳은 장애가 자주 생깁니다. 이때 성공 여부와 응답 상태를 기록해 두면 "우리 코드 문제인지, 외부 서비스 문제인지"를 빨리 나눌 수 있습니다.
넷째, 사용자에게 보이는 실패 화면입니다. 프론트엔드에서 하얀 화면이 뜨거나 버튼이 눌리지 않는 경우는 서버 로그만으로는 안 보일 수 있습니다. 브라우저에서 발생한 에러도 수집해야 전체 그림이 보입니다.24
프론트엔드 로그와 백엔드 로그는 다르게 본다
초보자는 종종 로그를 서버에서만 보면 된다고 생각합니다. 하지만 웹 서비스는 화면과 서버가 함께 움직입니다. 그래서 장애도 두 방향에서 봐야 합니다.
프론트엔드에서는 사용자의 브라우저 안에서 일어난 문제가 중요합니다. 자바스크립트 런타임 에러, 처리되지 않은 Promise 실패, 네트워크 요청 실패, 렌더링 중 예외가 대표적입니다. 이런 에러는 서버에 도착하기도 전에 화면을 망가뜨릴 수 있습니다. 그래서 브라우저 쪽에서는 전역 에러 핸들러와 비동기 실패 감지를 붙여 두는 방식이 널리 쓰입니다.24
백엔드에서는 API 요청과 응답, 데이터베이스 쿼리 실패, 인증 실패, 외부 API 타임아웃 같은 것이 핵심입니다. 이쪽은 보통 서버 로그에서 확인합니다. 언제 요청이 들어왔고, 어느 경로였고, 상태 코드가 무엇이었는지, 내부에서 어떤 예외가 났는지가 중요합니다.35
쉽게 말해 프론트엔드 로그는 "사용자 눈앞에서 무슨 일이 터졌는가"를 보여주고, 백엔드 로그는 "서버 뒤편에서 왜 그렇게 되었는가"를 보여줍니다. 둘 중 하나만 보면 반쪽짜리 수사입니다.
요청 ID 하나만 있어도 세상이 편해진다
운영에서 특히 유용한 습관이 요청 ID, 즉 requestId를 붙이는 것입니다. 사용자가 버튼을 한 번 눌렀을 때 브라우저, API, 데이터베이스 조회, 외부 서비스 호출이 연쇄적으로 이어질 수 있습니다. 이때 각 로그에 같은 요청 ID가 들어가 있으면 서로 다른 로그를 한 줄로 엮어 추적할 수 있습니다.35
예를 들어 사용자가 "글 저장 버튼을 눌렀는데 실패했다"고 말했습니다. 서버 로그에 같은 시간대의 error가 여러 개 찍혀 있다면 무엇이 그 사용자의 요청인지 구분하기 어렵습니다. 하지만 프론트엔드 에러 리포트에 requestId=req_7f23가 있고, API 로그에도 같은 ID가 있다면 바로 해당 요청의 흐름만 좁혀서 볼 수 있습니다.
작은 서비스에서는 거창한 분산 추적 시스템까지는 필요 없습니다. 다만 다음 정도는 해두면 좋습니다.
API 요청이 시작될 때 요청 ID를 만든다.
해당 요청의 로그마다 같은 ID를 넣는다.
가능하면 응답 헤더나 클라이언트 에러 리포트에도 그 ID를 연결한다.
이 습관 하나만으로 장애 분석 속도가 훨씬 빨라집니다.
민감한 정보는 로그에 남기지 말자
로그는 문제 해결에 도움이 되지만, 잘못 남기면 새로운 문제가 됩니다. 특히 비밀번호, 인증 토큰, 주민번호 같은 개인정보, 카드 정보, 전체 요청 본문은 무심코 기록했다가 큰 사고로 이어질 수 있습니다.23
초보자가 자주 하는 실수는 로그인 디버깅을 하다가 이런 식으로 남기는 것입니다.
console.log({ email, password })개발 중에는 잠깐 썼더라도, 이 코드가 운영에 들어가면 민감정보가 그대로 기록됩니다. 그래서 로그에는 필요한 정보만 남겨야 합니다. 예를 들어 로그인 실패를 확인하고 싶다면 비밀번호 대신 이메일 일부, 사용자 ID, 실패 원인, 시간 정도면 충분합니다.
정리하면 로그는 많이 남기는 것보다, 안전하게 남기는 것이 먼저입니다.
초보자를 위한 장애 대응 5단계
문제가 생기면 당황하기 쉽습니다. 하지만 순서를 정해 두면 생각보다 차분하게 대응할 수 있습니다. 보안 사고 대응 프레임워크에서도 준비, 탐지와 분석, 확산 방지와 복구, 사후 개선의 순서를 강조하는데, 초보자용 웹 서비스 운영에도 이 흐름을 간단히 적용할 수 있습니다.1
1단계: 증상을 기록한다
먼저 "무엇이 안 되는지"를 정확히 적습니다. 로그인 실패인지, 글 저장 실패인지, 특정 페이지만 깨지는지, 모든 사용자에게 공통인지 확인합니다. 가능하면 발생 시간, URL, 사용자 행동 순서를 남깁니다.
"사이트가 이상해요"는 너무 넓습니다. "4월 23일 오전 10시 31분, /posts/new 에서 저장 버튼 클릭 후 500 에러"는 훨씬 좋습니다.
2단계: 로그에서 같은 시간대 기록을 찾는다
사용자 제보 시간 전후로 프론트엔드와 백엔드 로그를 봅니다. 에러 메시지, 상태 코드, 실패한 함수 이름, 외부 API 응답 실패 같은 단서를 찾습니다. 이때 요청 ID가 있으면 훨씬 빠릅니다.35
3단계: 범위를 판단한다
한 명만 겪는지, 모든 사용자가 겪는지 구분합니다. 특정 브라우저만 문제인지, 모바일만 그런지, 로그인 사용자만 그런지도 봅니다. 클라이언트 에러는 브라우저나 확장 프로그램 영향도 받을 수 있고, 네트워크 환경 문제일 수도 있습니다.4
4단계: 더 큰 피해를 막는다
치명적인 장애라면 기능을 잠시 막거나, 문제 있는 배포를 되돌리거나, 잘못된 환경변수를 수정하는 쪽이 먼저일 수 있습니다. 완벽한 원인 분석보다 확산 방지가 우선일 때가 많습니다.1
5단계: 해결 후 기록을 남긴다
왜 일어났는지, 어떻게 찾았는지, 무엇을 고쳤는지 적어 둡니다. 다음에 같은 일이 생기면 이 기록이 미래의 나를 구합니다. 사후 정리는 귀찮지만, 운영 실력을 가장 빠르게 늘려 주는 습관입니다.1
자주 만나는 작은 장애 시나리오
이제 초보자가 실제로 자주 마주치는 장면을 떠올려 봅시다.
로그인은 되는데 글 작성만 실패하는 경우
이때는 인증 자체보다 권한이나 데이터 저장 문제를 의심해야 합니다. 서버 로그에서 글 작성 API의 상태 코드와 에러 메시지를 먼저 확인합니다. 사용자 ID는 있는데 게시글 테이블에 필요한 필드가 빠졌는지, 정책 설정이 맞지 않는지, DB 에러가 났는지 봅니다. 로그인 성공 로그와 글 작성 실패 로그를 이어 보면 흐름이 보입니다.
어떤 사용자는 화면이 하얗게 보이는 경우
이 경우 서버는 멀쩡한데 브라우저에서 자바스크립트 에러가 터졌을 가능성이 큽니다. 예를 들어 API 응답이 배열일 거라고 가정하고 map()을 호출했는데 실제로는 null이 온 경우, 프론트엔드에서 바로 깨질 수 있습니다. 이런 런타임 에러와 처리되지 않은 비동기 실패는 브라우저 쪽 에러 수집이 없으면 놓치기 쉽습니다.46
배포 직후 특정 기능만 500 에러가 나는 경우
새 환경변수 누락, 잘못된 API 키, 외부 서비스 연결 실패가 흔한 원인입니다. 특히 개발 환경에서는 되던 코드가 운영 환경에서만 깨지는 이유 중 하나가 실제 환경변수나 실제 데이터 형태가 다르기 때문입니다.6 배포 직후 문제라면 "방금 바뀐 것"부터 의심하는 습관이 중요합니다.
저장은 됐는데 사용자는 실패했다고 느끼는 경우
서버에서는 DB 저장이 성공했지만, 프론트엔드가 응답 처리 중 에러를 만나 성공 메시지를 못 보여주는 경우도 있습니다. 이럴 때는 서버 로그만 보면 정상처럼 보입니다. 그래서 브라우저 에러와 API 로그를 같이 봐야 합니다. 운영은 늘 양쪽 대조입니다.34
Next.js 서비스에서 특히 신경 쓸 것
이 책의 실습은 Next.js 기반으로 진행했습니다. Next.js에서는 서버 쪽 Route Handler, 서버 컴포넌트, 클라이언트 컴포넌트가 섞여 있기 때문에 "어디에서 에러가 났는지"를 구분하는 감각이 중요합니다.
서버 쪽에서는 구조화 로그를 남기고, 요청 ID와 사용자 관련 맥락을 함께 기록하는 것이 좋습니다. 클라이언트 쪽에서는 에러 경계, 전역 에러 수집, 네트워크 실패 처리를 통해 사용자에게는 친절한 메시지를 보여주고 개발자에게는 복구 가능한 단서를 남겨야 합니다.5
쉽게 말하면 Next.js 운영은 이렇게 생각하면 됩니다.
화면이 깨졌다면 클라이언트 로그를 보고, API가 실패했다면 서버 로그를 보고, 둘이 엇갈리면 요청 ID로 연결한다.
이 한 문장을 기억하면 절반은 성공입니다.
AI와 함께 로그를 읽는 법
로그를 보는 일은 초보자에게 특히 어렵습니다. 에러 메시지가 낯설고, 어디가 원인이고 어디가 결과인지 헷갈리기 때문입니다. 이때 Claude Code나 Codex 같은 도구를 잘 쓰면 로그 해석 속도를 크게 높일 수 있습니다.
다만 중요한 점이 있습니다. AI에게 "왜 안 돼?"라고만 묻지 말고, 상황과 목표를 함께 줘야 합니다. 예를 들어 이렇게 요청하는 편이 좋습니다.
Next.js + Supabase 프로젝트다. 게시글 생성 API에서 500 에러가 난다. 아래 로그를 보고 가능한 원인을 우선순위로 정리하고, 추가로 어떤 로그를 더 남겨야 하는지 제안해 줘.
이런 방식이 좋은 이유는 AI가 단순히 에러 문장을 번역하는 데서 끝나지 않고, 원인 후보와 다음 액션까지 제안할 수 있기 때문입니다. 실제로 AI 기반 디버깅 워크플로에서는 로그를 추가하고, 실행 결과를 다시 붙여 넣고, 원인을 점점 좁혀 가는 상호작용이 효과적이라고 많이 이야기합니다.78
초보자는 AI를 이렇게 활용하면 좋습니다.
로그를 붙여 넣고 핵심 에러 한 줄을 골라 달라고 요청한다.
같은 요청 흐름에서 어떤 로그가 부족한지 물어본다.
재현 경로를 단계별로 정리해 달라고 요청한다.
수정 전후에 어떤 테스트를 해야 하는지 물어본다.
단, 로그를 AI에 넣기 전에 토큰, 비밀번호, 개인정보는 지우거나 가려야 합니다. 운영 정보는 디버깅에 중요하지만, 민감정보는 여전히 민감정보입니다.
장애를 줄이는 운영 습관
장애 대응은 사고가 터진 뒤에만 하는 일이 아닙니다. 평소 습관이 장애의 크기를 줄입니다.
첫째, 핵심 기능마다 성공과 실패 로그를 균형 있게 남기세요. 실패만 보면 원인을 놓치고, 성공만 보면 문제를 놓칩니다.
둘째, 에러 메시지를 사용자용과 개발자용으로 나누세요. 사용자에게는 "문제가 발생했습니다. 잠시 후 다시 시도해 주세요."가 필요하고, 개발자에게는 구체적인 내부 원인이 필요합니다. 둘은 같은 문장이 아닙니다.
셋째, 배포 직후에는 꼭 핵심 흐름을 직접 테스트하세요. 홈 화면, 회원가입, 로그인, 글 작성, 수정, 삭제 정도만 점검해도 많은 문제를 초기에 잡을 수 있습니다.
넷째, 반복되는 장애는 체크리스트로 바꾸세요. "환경변수 확인 → API 응답 확인 → DB 정책 확인 → 브라우저 콘솔 확인" 같은 짧은 순서만 있어도 당황이 줄어듭니다.
다섯째, 작은 회고를 남기세요. 장애 대응이 끝난 뒤 5분만 투자해서 원인과 해결 과정을 적어 두면 다음 장애는 훨씬 짧아집니다.1
로그를 남기는 것은 사용자를 배려하는 일이다
운영을 처음 해 보면 로그와 장애 대응이 왠지 차갑고 기술적인 일처럼 느껴집니다. 하지만 본질은 사용자 경험을 지키는 일입니다. 사용자는 우리가 밤늦게 에러 로그를 읽었는지 모릅니다. 다만 "아까 안 되던 기능이 금방 복구됐네"라고 느낄 뿐입니다.
좋은 서비스는 장애가 전혀 없는 서비스가 아닙니다. 문제가 생겼을 때 빨리 알아차리고, 빠르게 좁히고, 조심스럽게 복구하고, 다시는 같은 곳에서 덜 넘어지는 서비스입니다. 배포는 서비스의 출발선이고, 로그는 그 이후의 길잡이입니다.
이 장에서 꼭 가져가야 할 핵심은 하나입니다.
문제가 생긴 뒤의 대응까지 포함해야 서비스가 완성된다.
이 감각이 생기면, 이제 당신은 "코드를 배포한 사람"을 넘어 "서비스를 운영하는 사람"이 됩니다.
참고
1Incident Response Fundamentals
2Best Practices for Error Logging in JavaScript
3Error Logging: A Developer's Guide to Client-Side and Server-Side Logging
4Debugging JavaScript in Production
5Debugging Next.js in production: logs and error tracking
6Console Errors in Production: Runtime JavaScript Failures in AI-Built Apps
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.