티스토리 뷰

반응형

운영에서 진짜 차이 나는 백엔드 기본기 — 로깅과 요청 추적(Request ID / Trace ID) 구현하기 (FastAPI · Spring Boot · Node.js)

백엔드 만들다 보면 어느 순간부터 이런 일이 생겨요.
에러는 났는데, 로그가 너무 많아서 어느 요청에서 터진 건지 도통 못 찾겠는 거죠. 같은 시각에 사용자 요청이 수십 개, 수백 개 들어오면 로그 줄만 봐서는 진짜 막막합니다. 그래서 대부분의 프레임워크는 요청 전후에 공통 처리를 넣을 수 있는 미들웨어나 필터를 제공하고, Spring Boot 쪽은 MDC, FastAPI는 middleware, Express는 middleware 패턴으로 이 문제를 풀게 됩니다. (FastAPI)

저는 이 시점을 꽤 중요하게 봐요.
전역 예외 처리까지는 “API가 깔끔하다”는 느낌이었다면, 로깅과 요청 추적이 들어가는 순간부터는 “아, 이거 운영할 생각이 있구나” 쪽으로 넘어갑니다. Spring Boot는 로그 라인에 MDC 값을 넣는 방법을 공식 문서에서 직접 설명하고 있고, Actuator의 tracing 문서는 Micrometer Tracing 자동 구성을 제공한다고 안내합니다. Express는 에러 처리와 마찬가지로 미들웨어 조합이 기본이고, FastAPI는 요청과 응답 전후를 가로채는 middleware를 공식적으로 지원합니다. (Home)

이번 글에서는 세 가지를 같이 잡겠습니다.

  • 요청마다 고유한 request_id 만들기
  • 로그에 request_id를 함께 남기기
  • 응답 헤더에도 X-Request-Id 넣기
  • 요청 시작/종료 시간 로그 남기기
  • 다음 단계의 distributed tracing으로 확장 가능한 구조 만들기

여기서 먼저 구분하고 갈 게 있어요.
request_id는 지금 서비스에서 요청 하나를 찾기 위한 최소 식별자에 가깝고, trace_id는 여러 서비스로 흘러가는 요청을 전체적으로 추적할 때 더 많이 씁니다. Spring Boot의 tracing 문서도 Micrometer Tracing을 통해 이런 분산 추적 구성을 다루고 있습니다. 이번 글은 먼저 “단일 서비스 운영 기본기”로서 request_id부터 구현하고, 다음 단계로 trace_id로 확장하는 감각까지 연결해볼게요. (Home)


왜 Request ID가 필요할까

예를 들어 어떤 사용자가 “결제가 안 돼요”라고 문의했다고 해볼게요.
시간만 가지고 로그를 찾기 시작하면, 비슷한 시각의 다른 요청 로그까지 다 섞입니다. 그런데 모든 로그 줄에 같은 request_id가 찍혀 있으면 검색 한 번으로 그 요청의 흐름을 거의 다 볼 수 있어요.

이게 생각보다 엄청 큽니다.

  • 어떤 API로 들어왔는지
  • 어떤 서비스 함수까지 갔는지
  • 얼마나 걸렸는지
  • 어디서 예외가 났는지
  • 응답이 몇 번 상태코드로 끝났는지

이게 한 줄기처럼 잡혀요. Spring Boot는 MDC 값을 로그 패턴에 섞어 출력할 수 있다고 공식 문서에서 설명하고, FastAPI는 middleware로 요청 전후 공통 작업을 넣을 수 있으며, Express 쪽 morgan 문서도 요청 로깅용 미들웨어를 포맷 기반으로 제공한다고 안내합니다. (Home)


이번 글에서 맞출 공통 규칙

세 스택 모두 아래 규칙으로 갑니다.

  1. 클라이언트가 X-Request-Id를 보내면 그 값을 우선 사용
  2. 없으면 서버가 새로 생성
  3. 응답 헤더에 X-Request-Id를 다시 내려줌
  4. 요청 시작 로그와 응답 종료 로그를 남김
  5. 로그에 method, path, status, duration_ms 포함
  6. 서비스 내부 로그에도 같은 request id를 남길 수 있게 설계

참고로 HTTP 헤더 이름은 대소문자를 구분하지 않습니다. MDN도 HTTP 헤더를 case-insensitive name으로 설명하고 있고, HTTP/2 이상에서는 소문자로 보일 수 있다고 안내합니다. 그러니까 코드에서 x-request-id로 읽히더라도 이상한 게 아닙니다. (MDN)


1) FastAPI에서 로깅과 Request ID 구현하기

반응형

FastAPI는 공식 문서에서 middleware를 요청 전후에 동작하는 함수로 설명합니다. 또 FastAPI는 Starlette 위에 만들어져 있기 때문에 Starlette의 Request 객체와 ASGI middleware를 활용할 수 있습니다. Starlette 쪽에는 third-party middleware 목록에 request ID 관련 middleware도 소개되어 있지만, 이번 글은 원리를 이해하기 좋게 직접 최소 구현으로 갑니다. (FastAPI)

추천 구조

fastapi-backend/
├── app/
│   ├── api/
│   │   └── demo.py
│   ├── core/
│   │   └── logging_config.py
│   ├── middleware/
│   │   └── request_logging.py
│   ├── services/
│   │   └── demo_service.py
│   └── main.py
└── requirements.txt

requirements.txt

fastapi
uvicorn[standard]

app/core/logging_config.py

import logging


LOG_FORMAT = (
    "%(asctime)s | %(levelname)s | request_id=%(request_id)s | "
    "%(name)s | %(message)s"
)


class RequestIdFilter(logging.Filter):
    def filter(self, record: logging.LogRecord) -> bool:
        if not hasattr(record, "request_id"):
            record.request_id = "-"
        return True


def setup_logging() -> None:
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)

    if root_logger.handlers:
        for handler in root_logger.handlers:
            handler.addFilter(RequestIdFilter())
            handler.setFormatter(logging.Formatter(LOG_FORMAT))
        return

    console_handler = logging.StreamHandler()
    console_handler.addFilter(RequestIdFilter())
    console_handler.setFormatter(logging.Formatter(LOG_FORMAT))
    root_logger.addHandler(console_handler)

app/middleware/request_logging.py

import logging
import time
from uuid import uuid4

from fastapi import Request


logger = logging.getLogger("app.request")


async def request_logging_middleware(request: Request, call_next):
    request_id = request.headers.get("x-request-id") or str(uuid4())
    request.state.request_id = request_id

    start = time.perf_counter()

    logger.info(
        "request started method=%s path=%s",
        request.method,
        request.url.path,
        extra={"request_id": request_id},
    )

    response = await call_next(request)

    duration_ms = round((time.perf_counter() - start) * 1000, 2)
    response.headers["X-Request-Id"] = request_id

    logger.info(
        "request completed method=%s path=%s status=%s duration_ms=%s",
        request.method,
        request.url.path,
        response.status_code,
        duration_ms,
        extra={"request_id": request_id},
    )

    return response

app/services/demo_service.py

import logging
from fastapi import Request


logger = logging.getLogger("app.service.demo")


def run_demo_logic(request: Request) -> dict:
    request_id = getattr(request.state, "request_id", "-")

    logger.info(
        "business logic executed",
        extra={"request_id": request_id},
    )

    return {
        "message": "hello from fastapi",
        "request_id": request_id,
    }

app/api/demo.py

from fastapi import APIRouter, Request

from app.services.demo_service import run_demo_logic

router = APIRouter(prefix="/api/demo", tags=["demo"])


@router.get("")
def get_demo(request: Request):
    return run_demo_logic(request)

app/main.py

from fastapi import FastAPI

from app.api.demo import router as demo_router
from app.core.logging_config import setup_logging
from app.middleware.request_logging import request_logging_middleware

setup_logging()

app = FastAPI(title="backend-series-fastapi-logging")
app.middleware("http")(request_logging_middleware)
app.include_router(demo_router)

실행

uvicorn app.main:app --reload

테스트

curl -i http://127.0.0.1:8000/api/demo

또는 직접 request id를 넣어서 테스트:

curl -i -H "X-Request-Id: my-test-id-001" http://127.0.0.1:8000/api/demo

기대 로그 예시

2026-03-26 11:10:01,120 | INFO | request_id=my-test-id-001 | app.request | request started method=GET path=/api/demo
2026-03-26 11:10:01,121 | INFO | request_id=my-test-id-001 | app.service.demo | business logic executed
2026-03-26 11:10:01,122 | INFO | request_id=my-test-id-001 | app.request | request completed method=GET path=/api/demo status=200 duration_ms=1.84

FastAPI에서 기억할 포인트

FastAPI 쪽은 request.state가 꽤 실용적입니다.
요청 수명주기 동안 필요한 값을 거기에 넣어두고, 라우터나 서비스에서 Request를 통해 꺼내 쓰면 돼요. 공식 문서도 필요할 때 FastAPI 아래의 Starlette Request 객체를 직접 사용할 수 있다고 설명합니다. 다만 Request를 직접 많이 다루는 패턴은 자동 검증/문서화와는 결이 다르기 때문에, 이런 공통 인프라성 값에만 쓰는 게 보통 좋습니다. (FastAPI)

그리고 여기서는 uuid4()를 썼는데, 핵심은 “충돌 가능성이 사실상 매우 낮은 요청 식별자”를 만드는 겁니다. 나중에 프록시나 API 게이트웨이에서 X-Request-Id를 이미 넣어주고 있다면 서버는 그 값을 그대로 이어받는 편이 더 좋습니다. 요청이 여러 계층을 통과해도 같은 ID로 묶을 수 있으니까요. 이 흐름은 Starlette/FastAPI의 middleware 구조와도 잘 맞습니다. (FastAPI)


2) Spring Boot에서 로깅과 Request ID 구현하기

Spring Boot 쪽은 이 주제에서 특히 강합니다.
공식 문서가 MDC 값을 로그 패턴에 넣는 방법을 직접 보여주고 있고, Spring Framework는 OncePerRequestFilter를 통해 요청마다 한 번만 실행되는 필터를 만들 수 있다고 설명합니다. 또 Spring Security 문서도 커스텀 필터를 OncePerRequestFilter로 만드는 패턴을 안내합니다. (Home)

추천 구조

springboot-backend/
├── src/main/java/com/example/backend/
│   ├── BackendApplication.java
│   ├── controller/
│   │   └── DemoController.java
│   ├── filter/
│   │   └── RequestLoggingFilter.java
│   └── service/
│       └── DemoService.java
└── src/main/resources/
    └── application.yml

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'
}

tasks.named('test') {
    useJUnitPlatform()
}

src/main/resources/application.yml

spring:
  application:
    name: backend-series-spring-logging

server:
  port: 8080

logging:
  pattern:
    level: "request_id:%X{request_id} %5p"

위 설정의 포인트는 logging.pattern.level입니다.
Spring Boot 공식 문서가 바로 이 방식으로 MDC 값을 로그에 넣는 예를 보여줍니다. %X{request_id}가 있으면 현재 스레드의 MDC에 들어 있는 request_id가 로그 라인에 같이 찍힙니다. (Home)

src/main/java/com/example/backend/BackendApplication.java

package com.example.backend;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BackendApplication {
    public static void main(String[] args) {
        SpringApplication.run(BackendApplication.class, args);
    }
}

src/main/java/com/example/backend/filter/RequestLoggingFilter.java

package com.example.backend.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.UUID;

@Component
public class RequestLoggingFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
    private static final String REQUEST_ID_HEADER = "X-Request-Id";
    private static final String MDC_KEY = "request_id";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
        String requestId = request.getHeader(REQUEST_ID_HEADER);
        if (requestId == null || requestId.isBlank()) {
            requestId = UUID.randomUUID().toString();
        }

        long start = System.currentTimeMillis();

        MDC.put(MDC_KEY, requestId);
        response.setHeader(REQUEST_ID_HEADER, requestId);

        try {
            log.info("request started method={} path={}", request.getMethod(), request.getRequestURI());

            filterChain.doFilter(request, response);

            long durationMs = System.currentTimeMillis() - start;
            log.info(
                    "request completed method={} path={} status={} duration_ms={}",
                    request.getMethod(),
                    request.getRequestURI(),
                    response.getStatus(),
                    durationMs
            );
        } finally {
            MDC.clear();
        }
    }
}

src/main/java/com/example/backend/service/DemoService.java

package com.example.backend.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class DemoService {

    private static final Logger log = LoggerFactory.getLogger(DemoService.class);

    public Map<String, Object> getDemo() {
        log.info("business logic executed");
        return Map.of("message", "hello from spring boot");
    }
}

src/main/java/com/example/backend/controller/DemoController.java

package com.example.backend.controller;

import com.example.backend.service.DemoService;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class DemoController {

    private final DemoService demoService;

    public DemoController(DemoService demoService) {
        this.demoService = demoService;
    }

    @GetMapping("/api/demo")
    public Map<String, Object> demo() {
        Map<String, Object> result = new HashMap<>(demoService.getDemo());
        result.put("request_id", MDC.get("request_id"));
        return result;
    }
}

실행

./gradlew bootRun

테스트

curl -i http://127.0.0.1:8080/api/demo

또는

curl -i -H "X-Request-Id: spring-test-001" http://127.0.0.1:8080/api/demo

기대 로그 예시

2026-03-26T11:15:10.001+09:00  INFO request_id:spring-test-001 --- [nio-8080-exec-1] c.e.b.filter.RequestLoggingFilter : request started method=GET path=/api/demo
2026-03-26T11:15:10.005+09:00  INFO request_id:spring-test-001 --- [nio-8080-exec-1] c.e.b.service.DemoService          : business logic executed
2026-03-26T11:15:10.009+09:00  INFO request_id:spring-test-001 --- [nio-8080-exec-1] c.e.b.filter.RequestLoggingFilter : request completed method=GET path=/api/demo status=200 duration_ms=8

Spring Boot에서 기억할 포인트

OncePerRequestFilter는 이름 그대로 요청당 한 번 실행되도록 설계된 베이스 클래스입니다. Spring Framework Javadoc과 레퍼런스는 이 필터가 request dispatch 기준으로 동작하고, async/error dispatch 관여 여부도 제어할 수 있다고 설명합니다. 이게 request id처럼 “요청 단위 공통 처리”를 넣을 때 아주 잘 맞아요. (Home)

그리고 Spring Boot에서 진짜 핵심은 MDC예요.
MDC는 스레드 컨텍스트에 값을 넣고 로그 패턴에서 그 값을 꺼내 쓰는 방식이라, 서비스 레이어에서 그냥 log.info(...)만 찍어도 같은 요청의 request_id가 자동으로 붙습니다. 공식 문서도 MDC를 로그 레벨 패턴에 넣는 예시를 보여줍니다. 이 방식이 Spring 쪽 운영 경험을 꽤 편하게 만들어줘요. (Home)

추가로, Spring Boot는 더 나아가 Actuator tracing으로 Micrometer Tracing을 붙일 수 있습니다. 지금 글은 request id 최소 구현이지만, 여러 서비스 호출까지 엮고 싶으면 다음 단계는 그쪽입니다. (Home)


3) Node.js(Express)에서 로깅과 Request ID 구현하기

Express는 원래 미들웨어 중심이라 이런 작업을 붙이기 편합니다.
공식 가이드도 morgan 같은 요청 로깅 미들웨어를 소개하고 있고, 일반 미들웨어와 에러 미들웨어 구조를 명확히 안내합니다. 이번 글에서는 원리 이해를 위해 morgan 없이 직접 구현하고, 마지막에 morgan으로 확장하는 포인트만 짚겠습니다. (expressjs.com)

추천 구조

node-backend/
├── src/
│   ├── middleware/
│   │   └── request-logger.js
│   ├── routes/
│   │   └── demo.route.js
│   ├── services/
│   │   └── demo.service.js
│   └── server.js
└── package.json

package.json

{
  "name": "backend-series-node-logging",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "node src/server.js"
  },
  "dependencies": {
    "express": "^5.1.0"
  }
}

src/middleware/request-logger.js

import crypto from "node:crypto";

function nowMs() {
  return Number(process.hrtime.bigint()) / 1_000_000;
}

export function requestLogger(req, res, next) {
  const requestId = req.header("x-request-id") || crypto.randomUUID();
  const startMs = nowMs();

  req.requestId = requestId;
  res.setHeader("X-Request-Id", requestId);

  console.log(
    `[request started] request_id=${requestId} method=${req.method} path=${req.originalUrl}`
  );

  res.on("finish", () => {
    const durationMs = (nowMs() - startMs).toFixed(2);

    console.log(
      `[request completed] request_id=${requestId} method=${req.method} path=${req.originalUrl} status=${res.statusCode} duration_ms=${durationMs}`
    );
  });

  next();
}

src/services/demo.service.js

export function getDemoMessage(requestId) {
  console.log(
    `[business logic] request_id=${requestId} message=business logic executed`
  );

  return {
    message: "hello from nodejs",
    request_id: requestId,
  };
}

src/routes/demo.route.js

import { Router } from "express";
import { getDemoMessage } from "../services/demo.service.js";

const router = Router();

router.get("/api/demo", (req, res) => {
  const result = getDemoMessage(req.requestId);
  res.json(result);
});

export default router;

src/server.js

import express from "express";
import demoRouter from "./routes/demo.route.js";
import { requestLogger } from "./middleware/request-logger.js";

const app = express();
const PORT = 3000;

app.use(express.json());
app.use(requestLogger);
app.use(demoRouter);

app.listen(PORT, () => {
  console.log(`server running on http://localhost:${PORT}`);
});

실행

npm install
npm run dev

테스트

curl -i http://127.0.0.1:3000/api/demo

또는

curl -i -H "X-Request-Id: node-test-001" http://127.0.0.1:3000/api/demo

기대 로그 예시

[request started] request_id=node-test-001 method=GET path=/api/demo
[business logic] request_id=node-test-001 message=business logic executed
[request completed] request_id=node-test-001 method=GET path=/api/demo status=200 duration_ms=2.17

Node.js에서 기억할 포인트

여기서 ID 생성에 crypto.randomUUID()를 썼습니다. Node 공식 문서는 node:crypto 모듈이 안정적인 핵심 모듈이라고 설명하고 있고, API 목록에도 crypto.randomUUID()가 포함되어 있습니다. 실무에서 UUID 하나 만들려고 별도 패키지부터 깔 필요가 없는 경우가 많아요. (Node.js)

그리고 Express에서는 요청 종료 시점을 잡을 때 res.on("finish") 패턴이 꽤 많이 쓰입니다. 공식 문서가 이 이벤트 자체를 자세히 설명하진 않더라도, Express의 핵심이 미들웨어 체인이라는 점 때문에 이런 식으로 요청 시작/종료 로깅을 붙이기 좋습니다. morgan도 결국 같은 목적의 요청 로깅 미들웨어입니다. 포맷을 커스터마이즈하고 싶으면 morgan 쪽으로 확장하는 게 나쁘지 않습니다. (expressjs.com)


세 스택을 나란히 놓고 보면

셋 다 결국 같은 문제를 풉니다.
“한 요청의 로그를 하나로 묶자.”

그런데 느낌은 꽤 달라요.

FastAPI

  • middleware 구현이 매우 직관적
  • request.state에 값을 담아두기 편함
  • 단일 앱에서 빠르게 붙이기 좋음 (FastAPI)

Spring Boot

  • OncePerRequestFilter + MDC 조합이 강력함
  • 서비스 레이어 로그까지 자동으로 request id를 태우기 좋음
  • Micrometer Tracing으로 확장 경로가 명확함 (Home)

Node.js

  • 구현 자유도가 높고 미들웨어 붙이기 쉬움
  • 대신 로그 포맷과 규칙을 직접 잘 정해야 함
  • crypto.randomUUID()로 기본 구현이 간단함 (expressjs.com)

여기서 한 단계 더 가면 Trace ID다

이쯤에서 많이들 헷갈리는 게 있어요.
“request id면 충분한 거 아냐?”

단일 서비스에서는 꽤 충분합니다.
근데 구조가 이렇게 바뀌면 얘기가 달라져요.

  • API 서버 → 인증 서버
  • API 서버 → 결제 서버
  • API 서버 → 알림 서버
  • API 서버 → 메시지 큐 → 워커
  • API Gateway → 여러 마이크로서비스

이때는 서비스 경계를 넘어 같은 요청 흐름을 따라가야 하니까 trace_id, span_id 같은 개념이 더 중요해집니다. Spring Boot tracing 문서는 바로 이 방향으로 Micrometer Tracing을 안내합니다. 그래서 지금 request id를 잘 심어두는 건 그냥 임시방편이 아니라, 나중에 tracing으로 넘어가기 위한 발판이기도 해요. (Home)


실무에서 같이 해두면 좋은 것

1. 응답 헤더에 반드시 다시 내려주기

사용자나 프론트 개발자가 문제 제보할 때 X-Request-Id 하나만 전달해도 조사 속도가 확 달라집니다. 헤더는 HTTP에서 요청/응답 양쪽이 추가 정보를 주고받는 기본 메커니즘이고, 이름은 대소문자를 구분하지 않습니다. (MDN)

2. 로그 포맷은 처음부터 고정하기

나중에 Kibana, Cloud Logging, Datadog, Loki 같은 곳으로 넘기면 결국 “필드가 얼마나 일관적인가”가 중요해져요.
처음부터 최소한 아래 필드는 맞추는 걸 추천합니다.

  • timestamp
  • level
  • request_id
  • logger/module
  • method
  • path
  • status
  • duration_ms

Spring Boot는 로그 패턴 커스터마이징을 공식 지원하고, Express/FastAPI는 코드로 직접 쉽게 맞출 수 있습니다. (Home)

3. 비즈니스 로그에도 request id 묻히기

request started / completed만 있으면 반쪽입니다.
실제 중요한 건 “그 요청 안에서 어떤 비즈니스 로직이 실행됐는지”예요. 그래서 서비스 레이어 로그에도 request id가 남아야 합니다. Spring의 MDC가 특히 이 부분에서 편하고, FastAPI/Express는 request 객체나 컨텍스트 값을 서비스로 전달하는 식으로 시작하는 경우가 많습니다. (Home)

4. 민감정보는 로그에 찍지 말기

이건 꼭 적고 싶었어요.
로그가 중요하다고 해서 토큰, 비밀번호, 주민번호, 카드정보 같은 걸 찍기 시작하면 그건 또 다른 사고예요. 공식 문서들이 직접 “민감정보를 로그에 찍지 마세요”라고 길게 외치진 않더라도, 운영 관점에서는 거의 상식에 가깝습니다. request id나 status, timing 같은 운영 정보 위주로 남기는 습관이 안전합니다.


이번 글 핵심 정리

이번 글의 핵심은 이겁니다.

로그는 많이 남기는 게 중요한 게 아니라, 한 요청을 끝까지 따라갈 수 있게 남기는 게 중요하다.

그래서 최소한 이 네 가지는 꼭 챙기면 좋습니다.

  • 요청마다 고유한 request_id
  • 응답 헤더 X-Request-Id
  • 요청 시작/종료 로그
  • 서비스 내부 로그와의 연결

이 정도만 해도 운영할 때 체감이 꽤 커요.
문의 하나 들어왔을 때, 예전에는 20분 걸리던 로그 추적이 2분으로 줄어드는 느낌… 진짜 있습니다.


다음 글 예고

다음 글에서는 이제 백엔드의 핵심으로 더 들어가 보겠습니다.

입력 검증(Validation)과 DTO/Schema 설계
FastAPI, Spring Boot, Node.js 기준으로 정리해볼게요.

여기서부터는

  • 요청값을 어디서 검증할지
  • 왜 엔티티를 그대로 받으면 안 되는지
  • DTO/Schema를 어떻게 나눠야 유지보수가 쉬운지

이런 게 본격적으로 나옵니다.

개인적으로는 로깅 다음에 validation이 와야, API가 진짜 단단해지기 시작한다고 느껴요.


출처

  • FastAPI 공식 문서 — Middleware (FastAPI)
  • FastAPI 공식 문서 — Advanced Middleware / ASGI Middleware (FastAPI)
  • FastAPI 공식 문서 — Using the Request Directly (FastAPI)
  • Starlette 공식 문서 — Middleware / Third-party Packages (starlette.io)
  • Spring Boot 공식 문서 — Logging (Home)
  • Spring Boot 공식 문서 — Tracing (Home)
  • Spring Framework 공식 문서/Javadoc — OncePerRequestFilter (Home)
  • Spring Security 공식 문서 — Filter Architecture / OncePerRequestFilter usage (Home)
  • Express 공식 문서 — Morgan middleware (expressjs.com)
  • Express 공식 문서 — Middleware resources / API reference (expressjs.com)
  • Node.js 공식 문서 — node:crypto / crypto.randomUUID() (Node.js)
  • MDN — HTTP Headers reference (MDN)


백엔드개발, FastAPI, SpringBoot, Nodejs, Express, 로깅, RequestID, TraceID, MDC, 백엔드시리즈

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
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
글 보관함
반응형