
서버를 가동하다가 문제가 발생했을 때
원인을 찾고 문제를 해결하기 위해서는
어디서 어떤 문제가 발생했는지 알아야 한다.
이를 위해 정상 작동과 그렇지 않은 상황에 대한
로그를 남겨 두는 것이 좋다.
우선 터미널에 로그를 찍는 것을 알아보자.
~/workspace$ mkdir log-tracing && cd log-tracing ~/workspace/log-tracing$ python3 -m venv venv ~/workspace/log-tracing$ source venv/bin/activate ~/workspace/log-tracing$ # 평소에 설치하던 것 외에 추가된 라이브러리를 놓치지 말자 ~/workspace/log-tracing$ pip install maturin fastapi uvicorn orjson ~/workspace/log-tracing$ maturin init ~/workspace/log-tracing$ # 선택지 중 기본값인 PyO3 선택 ~/workspace/log-tracing$ # Cargo.toml과 src/lib.rs가 자동 생성된다 ~/workspace/log-tracing$ # Python 코드는 직접 생성해 주어야 한다 ~/workspace/log-tracing$ mkdir app && touch app/main.py ~/workspace/log-tracing$ tree -I venv . ├── app │ └── main.py ├── Cargo.toml ├── pyproject.toml └── src └── lib.rs
Cargo.toml 파일을 열어 라이브러리 이름을 수정해 주겠다.
Rust에서 로그를 남기기 위해서는 tracing 크레이트를 사용해야 한다.
그리고 로그를 출력하고 필터링하기 위해 tracing-subscriber를 사용한다.
features = ["env-filter"] 를 명시해 주면
다시 컴파일할 필요 없이 환경변수만으로 필터를 조정할 수 있다.
유의미한 로그가 나오는 환경을 시뮬레이션하기 위해
병렬 처리를 위한 rayon도 사용한다.
Cargo.toml[package] name = "log-tracing" version = "0.1.0" edition = "2024" [lib] name = "rust_engine" crate-type = ["cdylib"] [dependencies] pyo3 = "0.28.0" rayon = "1.11" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Python에서 rust_engine 모듈을 가져올 때 최초로 한 번
로그에 어떤 스레드가 작업하는지 표시하도록 하며
EnvFilter 를 통해 환경변수에서 가져온 로그 수준을 반영하는
초기화 과정을 거친다.
#[instrument] 속성을 사용하면 함수 호출 시
자동으로 맥락(Span)을 생성하며
함수의 인자로 전달된 task_id 가 로그에 자동으로 찍힌다.
span!() 매크로를 사용하면
로그 수준에 따라 로그 앞에 부가적인 정보를 붙일 수도 있다.
지연 및 조건부 경고/에러에 대한 시뮬레이션을 작성한다.
src/lib.rsuse pyo3::prelude::*; use rayon::prelude::*; use tracing::{info, debug, warn, error, instrument, span, Level}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use std::time::Duration; fn init_tracing() { let filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info"); let _ = tracing_subscriber::registry() .with(fmt::layer().with_thread_ids(true)) .with(filter) .try_init(); } #[pyfunction] #[instrument(skip(events))] // 인자로 전달된 `events` 자체에 대한 건 생략 fn process_events_parallel(events: Vec<String>) -> PyResult<String> { info!("총 {}개의 이벤트 병렬 처리 시작", events.len()); let results: Vec<bool> = events .into_par_iter() .enumerate() .map(|(idx, event_name)| { let event_span = span!(Level::DEBUG, "event_worker", id = idx); let _enter = event_span.enter(); debug!("데이터 검증 중: {}", event_name); if event_name.contains("error") { error!("심각한 페이로드 발견: {}", event_name); false } else if event_name.len() > 10 { warn!("비정상적으로 긴 이벤트명 감지"); true } else { // 이벤트 처리에 걸리는 시간 시뮬레이션 std::thread::sleep(Duration::from_millis(10)); true } }) .collect(); let success_count = results.iter().filter(|&&r| r).count(); if success_count < results.len() { warn!("일부 작업이 실패했습니다. (성공: {} / 전체: {})", success_count, results.len()); } else { info!("{}개의 모든 이벤트가 성공적으로 처리되었습니다.", results.len()); } Ok(format!("처리 완료: {}건 성공", success_count)) } #[pymodule] fn rust_engine(m: &Bound<'_, PyModule>) -> PyResult<()> { init_tracing(); tracing::info!("Rust Engine 가동 및 Tracing 시스템 초기화 완료"); m.add_function(wrap_pyfunction!(process_events_parallel, m)?)?; Ok(()) }
Rust로 작성한 모듈을 불러오기 전에 환경 변수를 설정한다.
여기서 설정해주지 않을 경우 서버를 실행할 때
다음과 같이 환경 변수를 설정해 주어도 된다.~/workspace/log-tracing$ RUST_LOG=debug uvicorn app.main:app --reload
app/main.pyimport os from fastapi import FastAPI from fastapi.responses import ORJSONResponse # rust_engine을 로드하기 전에 로그 수준 설정 # debug: 각 스레드의 상세 log 확인 가능 # info: 요약 정보만 확인 가능 os.environ["RUST_LOG"] = "debug" import rust_engine class UTF8ORJSONResponse(ORJSONResponse): media_type = "application/json; charset=utf-8" app = FastAPI(default_response_class=UTF8ORJSONResponse) @app.get("/") def read_root(): return { "status": "200", "info": "서버 가동 중입니다." } @app.get("/process") def process(): test_events = [ "login_event", "purchase_event_with_long_name", "system_error_critical", "logout_event", "something" ] result = rust_engine.process_events_parallel(test_events) return { "status": "ok", "message": result }
Maturin 라이브러리를 통해 Rust 코드를 Python에서 호출 가능한 형태로 컴파일한다.
병렬 처리가 포함된 코드는 성능 최적화를 위해 --release 를 붙여 컴파일한다.
컴파일 후 pip list 명령어를 사용해 보면 Cargo.toml 파일에 작성한 패키지 이름을 확인할 수 있다.
uvicorn 라이브러리를 통해 FastAPI를 실행한다.
~/workspace/log-tracing$ maturin develop --release ~/workspace/log-tracing$ uvicorn app.main:app --reload
curl 명령어 또는 브라우저를 통해 다음과 같은 테스트를 해볼 수 있다.
http://127.0.0.1:8000/process로그 수준이 DEBUG일 때
~$ curl -i http://127.0.0.1:8000/process HTTP/1.1 200 OK date: Thu, 02 Apr 2026 23:21:58 GMT server: uvicorn content-length: 54 content-type: application/json; charset=utf-8 {"status":"ok","message":"처리 완료: 4건 성공"}%INFO: Started reloader process [65590] using StatReload 2026-04-02T23:21:55.889185Z INFO ThreadId(01) rust_engine: Rust Engine 가동 및 Tracing 시스템 초기화 완료 INFO: Started server process [65592] INFO: Waiting for application startup. INFO: Application startup complete. 2026-04-02T23:21:59.932540Z INFO ThreadId(02) process_events_parallel: rust_engine: 총 5개의 이벤트 병렬 처리 시작 2026-04-02T23:21:59.932686Z DEBUG ThreadId(16) event_worker{id=1}: rust_engine: 데이터 검증 중: purchase_event_with_long_name 2026-04-02T23:21:59.932693Z DEBUG ThreadId(13) event_worker{id=0}: rust_engine: 데이터 검증 중: login_event 2026-04-02T23:21:59.932686Z DEBUG ThreadId(15) event_worker{id=2}: rust_engine: 데이터 검증 중: system_error_critical 2026-04-02T23:21:59.932699Z DEBUG ThreadId(06) event_worker{id=3}: rust_engine: 데이터 검증 중: logout_event 2026-04-02T23:21:59.932721Z ERROR ThreadId(15) event_worker{id=2}: rust_engine: 심각한 페이로드 발견: system_error_critical 2026-04-02T23:21:59.932722Z WARN ThreadId(06) event_worker{id=3}: rust_engine: 비정상적으로 긴 이벤트명 감지 2026-04-02T23:21:59.932733Z WARN ThreadId(13) event_worker{id=0}: rust_engine: 비정상적으로 긴 이벤트명 감지 2026-04-02T23:21:59.932722Z WARN ThreadId(16) event_worker{id=1}: rust_engine: 비정상적으로 긴 이벤트명 감지 2026-04-02T23:21:59.932737Z DEBUG ThreadId(05) event_worker{id=4}: rust_engine: 데이터 검증 중: something 2026-04-02T23:21:59.945342Z WARN ThreadId(02) process_events_parallel: rust_engine: 일부 작업이 실패했습니다. (성공: 4 / 전체: 5) INFO: 127.0.0.1:49647 - "GET /process HTTP/1.1" 200 OK
로그 수준이 INFO일 때 혹은 로그 수준을 명시하지 않았을 때
~$ curl -i http://127.0.0.1:8000/process HTTP/1.1 200 OK date: Thu, 02 Apr 2026 23:22:29 GMT server: uvicorn content-length: 54 content-type: application/json; charset=utf-8 {"status":"ok","message":"처리 완료: 4건 성공"}%INFO: Started reloader process [65603] using StatReload 2026-04-02T23:22:27.262593Z INFO ThreadId(01) rust_engine: Rust Engine 가동 및 Tracing 시스템 초기화 완료 INFO: Started server process [65605] INFO: Waiting for application startup. INFO: Application startup complete. 2026-04-02T23:22:30.232762Z INFO ThreadId(02) process_events_parallel: rust_engine: 총 5개의 이벤트 병렬 처리 시작 2026-04-02T23:22:30.232913Z ERROR ThreadId(04) rust_engine: 심각한 페이로드 발견: system_error_critical 2026-04-02T23:22:30.232929Z WARN ThreadId(15) rust_engine: 비정상적으로 긴 이벤트명 감지 2026-04-02T23:22:30.232932Z WARN ThreadId(14) rust_engine: 비정상적으로 긴 이벤트명 감지 2026-04-02T23:22:30.232950Z WARN ThreadId(16) rust_engine: 비정상적으로 긴 이벤트명 감지 2026-04-02T23:22:30.245526Z WARN ThreadId(02) process_events_parallel: rust_engine: 일부 작업이 실패했습니다. (성공: 4 / 전체: 5) INFO: 127.0.0.1:49648 - "GET /process HTTP/1.1" 200 OK