티스토리 뷰
백엔드 로그는 어떻게 남겨야 할까? FastAPI · Spring Boot · Node.js에서 구조화된 로깅 시작하는 법
octo54 2026. 6. 4. 10:17백엔드 로그는 어떻게 남겨야 할까? FastAPI · Spring Boot · Node.js에서 구조화된 로깅 시작하는 법
한 줄 요약
운영에서 진짜 도움이 되는 로그는 console.log()를 여기저기 찍는 로그가 아니라, 레벨이 있고, 검색 가능하고, 요청 단위로 묶이고, JSON처럼 구조화된 로그입니다. Python 표준 logging은 유연한 로깅 시스템을 제공하고, Spring Boot는 기본적으로 콘솔 로그와 파일 로그를 지원하며, Spring Boot 3.4부터는 구조화된 로깅 포맷도 지원합니다. Node.js 쪽은 console이 가장 기본이지만, 운영용으론 Pino처럼 JSON 중심 구조화 로그 도구를 쓰는 쪽이 훨씬 낫습니다. (Python documentation)
이 글에서 다루는 내용
- 왜 백엔드 로그가 운영에서 제일 먼저 아쉬워지는지
- console.log()로는 왜 금방 한계가 오는지
- 로그 레벨은 어떻게 나눠야 하는지
- 구조화된 로깅(JSON 로그)은 왜 중요한지
- FastAPI, Spring Boot, Node.js에서 바로 실행 가능한 최소 코드
- 검색에 잘 걸리도록 질문형 제목, 첫 문단 정답 요약, 정의 문장, 실행 코드, FAQ 구조를 반영한 글 구성 원칙
백엔드 로그란?
백엔드 로그는 애플리케이션이 실행되는 동안 일어난 일을 기록한 텍스트 또는 구조화된 데이터입니다.
예를 들면 이런 것들이죠.
- 서버 시작
- 요청 수신
- DB 연결 실패
- 외부 API 오류
- 사용자 행동 추적
- 비동기 작업 완료
Python 표준 logging 문서는 로깅을 “애플리케이션과 라이브러리를 위한 유연한 이벤트 로깅 시스템”이라고 설명합니다. Spring Boot도 내부적으로 로깅을 사용하고, 기본적으로 콘솔 출력과 선택적 파일 출력을 지원한다고 설명합니다. (Python documentation)
조금 더 현실적으로 말하면,
로그는 운영 중에 “무슨 일이 있었는지”를 나중에 다시 찾기 위한 기록입니다.
왜 개발할 땐 괜찮은데 운영 가면 로그가 아쉬워질까
처음엔 다들 이렇게 시작하죠.
print("user created")
console.log("payment success")
System.out.println("done");
이게 나쁜 건 아니에요.
진짜 초기엔 저도 이렇게 많이 했어요. 빨리 보기 좋고, 머리 안 써도 되니까요.
근데 운영 들어가면 바로 한계가 보입니다.
- 이 로그가 에러인지 정보인지 구분이 안 됨
- 어느 요청에서 나온 로그인지 모름
- 사용자 ID나 주문 ID로 검색이 안 됨
- 로그 수집 시스템에 넣어도 분석이 어려움
- 문자열 포맷이 제각각이라 필터링이 안 됨
그래서 운영 로그는 보통 이 기준을 갖추는 쪽으로 갑니다.
- 로그 레벨이 있다
- 일관된 포맷이 있다
- 검색 가능한 필드가 있다
- 요청/작업 단위로 묶을 수 있다
Spring Boot 3.4의 structured logging 소개도 바로 이 문제를 풀기 위해 “well-defined, machine-readable format”, 즉 기계가 읽기 쉬운 형식의 로그가 필요하다고 설명합니다. Pino도 Node.js에서 JSON 중심 구조화 로그를 강하게 밀고 있고, child logger와 transport 같은 기능을 제공합니다. (Home)
로그 레벨은 어떻게 나눠야 할까
이건 진짜 기본기인데, 꽤 자주 섞입니다.
보통은 이렇게 봅니다.
- DEBUG: 개발 중 자세한 내부 흐름
- INFO: 정상적인 주요 이벤트
- WARNING: 이상 징후지만 서비스는 계속 동작
- ERROR: 요청/기능 실패
- CRITICAL 또는 FATAL: 서비스 지속이 어려운 심각한 문제
Python logging도 기본 레벨 체계를 그대로 제공합니다. Spring Boot는 underlying log implementation을 열어두지만 보통 Logback을 통해 이런 레벨 기반 로깅을 씁니다. Pino도 trace, debug, info, warn, error, fatal 같은 로그 레벨을 제공합니다. (Python documentation)
제 기준으로 아주 단순하게 정리하면 이렇습니다.
- 정상 흐름: info
- 디버깅용 자세한 값: debug
- 예외 처리: error
초반엔 이것만 지켜도 로그 품질이 꽤 달라집니다.
구조화된 로깅이란?
구조화된 로깅은 로그를 “문자열 한 줄”이 아니라 필드가 있는 데이터로 남기는 방식입니다.
대표적으로 JSON 로그가 많죠.
예를 들면 이런 겁니다.
{
"level": "info",
"message": "payment created",
"orderId": "ord_123",
"userId": "u_456"
}
이런 로그의 장점은 명확합니다.
- orderId=ord_123 으로 검색 가능
- userId 기준 집계 가능
- 레벨별 필터링 가능
- 로그 수집 시스템에서 파싱 쉬움
Spring Boot 3.4 structured logging 블로그도 JSON 같은 구조화 포맷은 로그 관리 시스템에서 검색과 분석에 유리하다고 설명합니다. Pino도 기본 철학 자체가 JSON logger이고, transports는 별도 스레드나 프로세스로 돌리는 걸 권장합니다. (Home)
즉 한 줄로 말하면,
운영 로그는 사람이 읽기 쉬운 것보다, 검색과 필터링이 쉬운 게 더 중요할 때가 많습니다.
요청 ID, 사용자 ID 같은 컨텍스트는 왜 중요할까
이건 로그가 “검색 가능한가”의 핵심이에요.
예를 들어 새벽 3시에 에러가 났다고 해봅시다.
로그는 있는데 요청이 수천 개예요. 그러면 뭘 기준으로 찾아야 할까요?
보통 여기서 필요한 게 이런 값들입니다.
- request_id
- user_id
- order_id
- job_id
- trace_id
Python logging cookbook은 LoggerAdapter를 써서 contextual information, 즉 문맥 정보를 로그에 함께 넣는 방법을 설명합니다. Pino는 child logger로 특정 bindings를 붙여 일관된 필드를 계속 물고 갈 수 있다고 문서에 나옵니다. Spring 쪽은 structured logging과 함께 추후 MDC나 trace 연결로 발전하기 좋습니다. (Python documentation)
이게 진짜 중요합니다.
그냥 "payment failed"보다
{
"message": "payment failed",
"orderId": "ord_123",
"userId": "u_456",
"requestId": "req_789"
}
이쪽이 훨씬 운영에 도움이 됩니다.
그럼 지금 당장 뭘 바꾸면 좋을까
저는 보통 이 순서로 추천합니다.
- print, console.log, System.out.println부터 줄이기
- 로깅 라이브러리로 통일
- 레벨 구분하기
- 요청/작업 ID 같은 공통 필드 넣기
- 운영에선 JSON 로그 고려하기
이 순서가 좋은 이유는,
처음부터 ELK나 Loki, OpenSearch 같은 큰 얘기로 가면 오히려 부담만 커지기 때문이에요.
1) FastAPI에서 Python 표준 logging으로 시작하기
Python 표준 logging은 이미 충분히 유연합니다.
공식 문서도 logger, handler, formatter 같은 구성요소를 가진 유연한 이벤트 로깅 시스템이라고 설명합니다. 또 LoggerAdapter로 문맥 정보를 붙일 수 있다고 cookbook에서 설명합니다. (Python documentation)
설치 방법
FastAPI 자체만 있으면 됩니다.
pip install fastapi uvicorn
예제 코드
# main.py
import logging
from fastapi import FastAPI, Request
from uuid import uuid4
# 기본 로거 설정
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s request_id=%(request_id)s message=%(message)s",
)
logger = logging.getLogger("app")
app = FastAPI()
class RequestIdAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs):
extra = kwargs.get("extra", {})
kwargs["extra"] = {"request_id": extra.get("request_id", "-")}
return msg, kwargs
@app.middleware("http")
async def add_request_logging(request: Request, call_next):
request_id = str(uuid4())
request.state.request_id = request_id
request_logger = RequestIdAdapter(logger, {"request_id": request_id})
request_logger.info(f"request started path={request.url.path}")
response = await call_next(request)
request_logger.info(f"request finished status_code={response.status_code}")
response.headers["X-Request-Id"] = request_id
return response
@app.get("/health")
def health(request: Request):
request_logger = RequestIdAdapter(
logger,
{"request_id": getattr(request.state, "request_id", "-")},
)
request_logger.info("health check called")
return {"ok": True}
실행 방법
uvicorn main:app --reload
FastAPI에서 핵심 감각
여기서 중요한 건 두 가지예요.
첫째, print() 대신 표준 logging을 쓴다는 것.
둘째, 요청마다 request_id를 붙인다는 것.
Python 공식 cookbook도 LoggerAdapter로 contextual information을 넣는 방식을 설명하고 있습니다. 이 정도만 해도 운영 로그 품질이 꽤 달라져요. (Python documentation)
그리고 솔직히 말하면, FastAPI에선 처음부터 structlog 같은 걸 안 붙여도 됩니다.
표준 logging만 제대로 써도 많이 좋아져요.
2) Spring Boot에서 로깅 설정과 구조화된 로그 시작하기
Spring Boot는 기본적으로 로깅을 잘 깔아줍니다.
공식 문서 기준으로 Spring Boot는 내부적으로 Commons Logging을 사용하고, 기본 설정은 console output과 선택적 file output을 제공합니다. 스타터를 쓰면 보통 Logback이 함께 들어옵니다. (Home)
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.3'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
기본 로깅 예제
// DemoController.java
package com.example.backend.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class DemoController {
private static final Logger log = LoggerFactory.getLogger(DemoController.class);
@GetMapping("/health")
public Object health() {
log.info("health check called");
return java.util.Map.of("ok", true);
}
@GetMapping("/error-sample")
public Object errorSample() {
try {
throw new IllegalStateException("sample exception");
} catch (Exception e) {
log.error("error happened in /error-sample", e);
return java.util.Map.of("ok", false);
}
}
}
application.yml
spring:
application:
name: backend-series-spring-logging
logging:
level:
root: INFO
com.example.backend: DEBUG
Spring Boot 문서는 로그 레벨을 설정 프로퍼티로 바꿀 수 있다고 설명합니다. (Home)
구조화된 로깅(JSON) 예시
Spring Boot 3.4부터는 structured logging 포맷을 공식 지원합니다. Spring 블로그는 JSON 기반 structured logging을 직접 설명하고, reference logging 문서와 연결합니다. (Home)
spring:
application:
name: backend-series-spring-logging
logging:
level:
root: INFO
structured:
format:
console: ecs
Spring Boot에서 핵심 감각
Spring Boot는 그냥 log.info()만 써도 시작점이 꽤 좋습니다.
그리고 운영 로그를 진짜 검색 가능하게 만들고 싶으면, 최근 버전에선 structured logging 지원이 꽤 매력적이에요. JSON 로그를 별도 라이브러리 덕지덕지 없이 좀 더 공식적인 방식으로 시작할 수 있으니까요. (Home)
개인적으로 Spring은 “기본 로그 환경이 이미 잘 갖춰져 있다”는 느낌이 강합니다.
3) Node.js에서 Pino로 구조화된 로깅 시작하기
Node.js 공식 console은 가장 기본적인 디버깅 콘솔입니다. 공식 문서도 node:console이 간단한 debugging console을 제공한다고 설명합니다. 그런데 운영용 로깅으로 가면 구조화와 성능 측면에서 Pino 쪽이 훨씬 많이 쓰입니다. Pino 공식 사이트는 very low overhead JavaScript logger라고 설명하고, child loggers, redaction, transports 같은 기능을 제공합니다. 또 저장소 문서에서는 로그 처리(transports)를 워커 스레드나 별도 프로세스로 분리하는 걸 권장합니다. (Node.js)
설치 방법
npm install express pino
예제 코드
// server.js
import express from "express";
import pino from "pino";
import crypto from "node:crypto";
const logger = pino({
level: process.env.LOG_LEVEL || "info",
});
const app = express();
app.use(express.json());
app.use((req, res, next) => {
const requestId = crypto.randomUUID();
req.log = logger.child({
requestId,
method: req.method,
path: req.path,
});
req.log.info("request started");
res.on("finish", () => {
req.log.info({ statusCode: res.statusCode }, "request finished");
});
next();
});
app.get("/health", (req, res) => {
req.log.info("health check called");
res.json({ ok: true });
});
app.get("/error-sample", (req, res) => {
try {
throw new Error("sample exception");
} catch (error) {
req.log.error({ err: error }, "error happened in /error-sample");
res.status(500).json({ ok: false });
}
});
app.listen(3000, () => {
logger.info("server started on port 3000");
});
실행 방법
node server.js
예상 로그 형태
Pino는 기본적으로 JSON 로그를 남깁니다.
{"level":30,"time":1750000000000,"pid":12345,"hostname":"my-host","msg":"server started on port 3000"}
Node.js에서 핵심 감각
여기서 중요한 건 logger.child(...) 입니다.
Pino 문서도 child logger는 bindings를 모든 로그 라인에 붙이는 방식이라고 설명합니다. 즉 requestId, userId 같은 걸 요청 단위로 묶기에 정말 좋습니다. (GitHub)
그리고 운영에선 console.log()보다 JSON 로그가 훨씬 찾기 쉽습니다.
솔직히 이 차이는 로그가 쌓일수록 너무 커져요.
console.log()는 정말 버려야 할까
완전히 버려야 한다고까지는 말 못 하겠어요.
로컬에서 빠르게 확인할 때는 여전히 편합니다. Node 공식 문서도 console은 simple debugging console이라고 설명합니다. (Node.js)
다만 제 기준은 이렇습니다.
- 로컬 임시 디버깅: console.log() 가능
- 서비스 코드 영구 로그: 로깅 라이브러리 사용
- 운영 로그: 구조화된 로그 권장
즉 “지금 눈앞에서 확인”과 “나중에 운영에서 검색”은 다른 문제예요.
어떤 로그를 남겨야 하고, 어떤 건 줄여야 할까
이것도 중요합니다.
로그를 많이 남기는 게 항상 좋은 건 아니거든요.
남기면 좋은 로그
- 요청 시작/종료
- 주요 상태 변화
- 외부 API 호출 실패
- 예외 발생
- 비동기 작업 완료/실패
- 보안 관련 이벤트(로그인 실패 등)
조심해야 할 로그
- 비밀번호
- access token / refresh token
- 카드 정보
- 주민번호 같은 민감정보
- DB 결과 전체 dump
Pino는 redaction 기능도 제공한다고 공식 사이트에 나옵니다. 즉 민감 필드를 마스킹하는 방향으로 발전시키기 좋습니다. (getpino.io)
이건 진짜 중요합니다.
운영 로그는 디버깅 도구이기도 하지만, 동시에 유출 위험이 있는 저장소이기도 하니까요.
요청 ID 하나만 붙여도 로그 품질이 확 달라진다
이건 제가 정말 추천하는 최소 개선 포인트예요.
요청마다 request_id 하나만 넣어도 이런 게 가능해집니다.
- 같은 요청에서 나온 로그만 모아서 보기
- 에러 난 요청 추적
- 프론트 에러와 백엔드 로그 연결
- APM이나 tracing으로 확장
Python cookbook의 LoggerAdapter, Pino의 child logger, Spring의 structured logging 방향 모두 결국 이런 “문맥 필드”를 잘 붙이기 위한 토대라고 볼 수 있습니다. (Python documentation)
초반엔 딱 이것만 먼저 해도 좋아요.
- request_id
- user_id 가능하면
- job_id 비동기 작업이면
실무에서 자주 하는 실수
1. print, console.log, System.out.println가 서비스 전역에 남아 있음
이건 금방 정리가 안 됩니다.
조금 귀찮아도 로깅 API로 통일하는 게 낫습니다.
2. 에러 로그인데 stack trace가 없음
이러면 진짜 원인을 찾기 어렵습니다.
예외 로그는 에러 객체를 같이 남기는 습관이 중요합니다.
3. 문자열만 길게 이어붙임
검색과 필터링이 안 됩니다.
구조화된 필드 방식이 훨씬 낫습니다.
4. 민감정보를 그대로 로그에 남김
운영 사고로 이어질 수 있습니다.
5. 로그 레벨이 다 info
그러면 운영할수록 중요한 로그와 잡음이 섞입니다.
실무에서 주의할 점
1. 개발 로그와 운영 로그는 목적이 다릅니다
개발에선 사람이 읽기 쉬운 로그가 좋고, 운영에선 검색과 집계가 잘 되는 로그가 좋습니다. Spring structured logging 블로그도 바로 이 차이를 설명합니다. (Home)
2. JSON 로그는 운영에서 특히 강합니다
OpenSearch, ELK, Loki, Datadog 같은 데 넣으면 JSON 로그가 훨씬 유리합니다.
3. 로그 처리 자체가 앱 성능을 잡아먹지 않게 하세요
Pino는 low overhead를 강조하고, transports는 워커 스레드/별도 프로세스로 돌리라고 권장합니다. (getpino.io)
4. 설정으로 로그 레벨을 바꿀 수 있게 두세요
Spring Boot는 외부 설정으로 로그 레벨을 조정할 수 있고, Node와 FastAPI도 환경변수 기반으로 충분히 할 수 있습니다. (Home)
5. “검색 가능한 로그”를 먼저 생각하세요
AI 검색에 잘 걸리는 개발 글 가이드도 결국 구조화, 명확한 질문, 바로 답이 보이는 문장을 강조합니다. 로그도 비슷해요. 나중에 찾기 쉬운 형태가 중요합니다.
FAQ
Q. FastAPI에서는 표준 logging만 써도 충분한가요?
네, 꽤 오래 충분합니다. Python 표준 logging 자체가 유연한 이벤트 로깅 시스템이고, LoggerAdapter로 컨텍스트도 붙일 수 있습니다. 처음부터 무거운 로깅 스택으로 갈 필요는 없습니다. (Python documentation)
Q. Spring Boot는 별도 로깅 라이브러리 설치가 꼭 필요한가요?
대부분의 경우 아닙니다. spring-boot-starter-web만 써도 로깅 스타터가 함께 들어오고, 기본 콘솔 로그와 설정 변경이 가능합니다. 최근 버전에선 structured logging도 공식적으로 지원합니다. (Home)
Q. Node.js에서는 그냥 console.log로 시작하면 안 되나요?
로컬 디버깅엔 괜찮지만, 운영용으론 한계가 빨리 옵니다. Node 공식 console은 simple debugging 용도에 가깝고, Pino 같은 구조화 logger가 운영엔 훨씬 낫습니다. (Node.js)
Q. 구조화된 로깅은 꼭 JSON이어야 하나요?
꼭 그렇진 않지만, JSON이 가장 많이 쓰이고 로그 수집 시스템과 잘 맞습니다. Spring Boot structured logging도 JSON 계열 포맷을 중심으로 설명합니다. (Home)
Q. 요청 ID는 꼭 붙여야 하나요?
강력 추천입니다. 이거 하나만으로도 에러 추적, 요청별 흐름 분석, 운영 디버깅이 훨씬 쉬워집니다. Python LoggerAdapter, Pino child logger 모두 이런 컨텍스트 부착에 잘 맞습니다. (Python documentation)
핵심 요약
- 운영에 도움이 되는 로그는 레벨과 구조가 있는 로그입니다. (Python documentation)
- FastAPI는 Python 표준 logging과 LoggerAdapter만 잘 써도 시작점이 좋습니다. (Python documentation)
- Spring Boot는 기본 로깅 지원이 강하고, 최근엔 structured logging도 공식 지원합니다. (Home)
- Node.js는 console보다 Pino 같은 JSON logger가 운영엔 훨씬 낫습니다. (Node.js)
- 요청 ID, 사용자 ID, 작업 ID 같은 컨텍스트를 붙이면 로그 품질이 확 올라갑니다. (Python documentation)
- 검색에 잘 걸리는 개발 글은 질문형 제목, 첫 문단 정답, 코드, FAQ, 명확한 소제목 구조가 강합니다.
출처
- Python 공식 문서 — logging module. (Python documentation)
- Python 공식 문서 — Logging Cookbook / LoggerAdapter. (Python documentation)
- Spring Boot 공식 문서 — Logging. (Home)
- Spring Boot 공식 문서 — Logging how-to. (Home)
- Spring 공식 블로그 — Structured logging in Spring Boot 3.4. (Home)
- Node.js 공식 문서 — console. (Node.js)
- Pino 공식 사이트. (getpino.io)
- Pino 공식 저장소 — transports & log processing. (GitHub)
- 글 구성 참고: 업로드하신 “AI 검색에 잘 걸리는 글” 가이드.
백엔드개발, FastAPI, SpringBoot, Nodejs, Express, 로깅, 구조화된로깅, JSON로그, Pino, 백엔드시리즈
'study > 백엔드' 카테고리의 다른 글
- Total
- Today
- Yesterday
- Next.js
- 개발블로그
- seo 최적화 10개
- nodejs
- SpringBoot
- rag
- 주니어개발자
- PostgreSQL
- node.js
- kotlin
- Express
- nextJS
- 생성형AI
- Prisma
- 웹개발
- NestJS
- 쿠버네티스
- JAX
- 백엔드개발
- REACT
- SEO최적화
- Python
- 딥러닝
- DevOps
- JWT
- flax
- fastapi
- llm
- CI/CD
- LangChain
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |

