Alert

์ด ๊ธ€์€ Claude Code์˜ ๋„์›€์„ ๋ฐ›์•„ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค

์•Œ์•„๋ณผ ๋‚ด์šฉ

  1. ์™œ print๊ฐ€ ์•„๋‹ˆ๋ผ logging์ธ๊ฐ€
  2. ๋กœ๊ทธ ๋ ˆ๋ฒจ๊ณผ ์‹ค๋ฌด ๊ธฐ์ค€
  3. logging ๋ชจ๋“ˆ์˜ ๊ตฌ์„ฑ ์š”์†Œ
  4. ์‹ค์ „ ์ฝ”๋“œ ์Šค๋‹ˆํŽซ
  5. ํ”ํ•œ ์‹ค์ˆ˜์™€ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค
  6. ์šด์˜ ํ™˜๊ฒฝ์—์„œ์˜ ๋กœ๊น…

Reference


์™œ print๊ฐ€ ์•„๋‹ˆ๋ผ logging์ธ๊ฐ€

print์˜ ํ•œ๊ณ„
  • print()๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋””๋ฒ„๊น… ๋„๊ตฌ์ด์ง€๋งŒ ์‹ค๋ฌด์—์„œ๋Š” ๊ธˆ๋ฐฉ ํ•œ๊ณ„์— ๋ถ€๋”ชํž˜
ํ•ญ๋ชฉprint()logging
์ถœ๋ ฅ ๋Œ€์ƒstdout๋งŒ์ฝ˜์†”, ํŒŒ์ผ, ๋„คํŠธ์›Œํฌ, DB ๋“ฑ ์ž์œ ๋กญ๊ฒŒ
์‹ฌ๊ฐ๋„ ๊ตฌ๋ถ„โŒ ์ „๋ถ€ ๋™์ผโœ… DEBUG ~ CRITICAL 5๋‹จ๊ณ„
์šด์˜ ์ค‘ ๋„๊ธฐ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ง€์›Œ์•ผ ํ•จ๋ ˆ๋ฒจ๋งŒ ์˜ฌ๋ฆฌ๋ฉด ๋จ (์ฝ”๋“œ ์ˆ˜์ • ๋ถˆํ•„์š”)
์‹œ๊ฐ„/์œ„์น˜ ์ •๋ณด์ง์ ‘ ๋„ฃ์–ด์•ผ ํ•จ์ž๋™ ํฌํ•จ (์‹œ๊ฐ„, ํŒŒ์ผ๋ช…, ์ค„ ๋ฒˆํ˜ธ)
ํŒŒ์ผ ์ €์žฅ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ํ•„์š”FileHandler๋กœ ๋ฐ”๋กœ ๊ฐ€๋Šฅ
๋ฉ€ํ‹ฐ๋ชจ๋“ˆ์–ด๋””์„œ ์ฐ์—ˆ๋Š”์ง€ ์ถ”์  ์–ด๋ ค์›€๋กœ๊ฑฐ ์ด๋ฆ„์œผ๋กœ ๋ชจ๋“ˆ๋ณ„ ๊ตฌ๋ถ„
print vs logging ๋น„๊ต
# print โ€” ์šด์˜ ์ค‘ ๋„๋ ค๋ฉด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•จ
print(f"[{datetime.now()}] user_id=42 login success")  # ์ง์ ‘ ํฌ๋งทํŒ…
 
# logging โ€” ๋ ˆ๋ฒจ๋งŒ ๋ฐ”๊พธ๋ฉด ์ถœ๋ ฅ ์ œ์–ด ๊ฐ€๋Šฅ
logger.info("user_id=%d login success", 42)  # ํฌ๋งทํŒ… + ์‹œ๊ฐ„ + ์œ„์น˜ ์ž๋™

์–ธ์ œ print๋ฅผ ์จ๋„ ๋˜๋Š”๊ฐ€

  • ์ผํšŒ์„ฑ ์Šคํฌ๋ฆฝํŠธ, Jupyter ๋…ธํŠธ๋ถ์—์„œ์˜ ๋น ๋ฅธ ํ™•์ธ
  • ๊ทธ ์™ธ ํŒŒ์ผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ชจ๋“  ์ฝ”๋“œ์—์„œ๋Š” logging ์‚ฌ์šฉ ๊ถŒ์žฅ

๋กœ๊ทธ ๋ ˆ๋ฒจ (Log Level)

5๋‹จ๊ณ„ ์‹ฌ๊ฐ๋„
๋ ˆ๋ฒจ์ˆซ์ž์˜๋ฏธ์‹ค๋ฌด ๊ธฐ์ค€
DEBUG10๊ฐœ๋ฐœ ์ค‘ ์ƒ์„ธ ์ถ”์  ์ •๋ณด๋ณ€์ˆ˜ ๊ฐ’, ๋ถ„๊ธฐ ์ง„์ž…, SQL ์ฟผ๋ฆฌ ๋“ฑ โ€” ์šด์˜์—์„œ๋Š” ๊บผ๋‘ 
INFO20์ •์ƒ์ ์ธ ๋™์ž‘ ํ™•์ธ์„œ๋น„์Šค ์‹œ์ž‘/์ข…๋ฃŒ, ์ž‘์—… ์™„๋ฃŒ, ์š”์ฒญ ์ฒ˜๋ฆฌ ๋“ฑ
WARNING30๋‹น์žฅ ๋ฌธ์ œ๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ฃผ์˜ ํ•„์š”๋””์Šคํฌ 80% ๋„๋‹ฌ, deprecated API ํ˜ธ์ถœ, ์žฌ์‹œ๋„ ๋ฐœ์ƒ
ERROR40๊ธฐ๋Šฅ์ด ์‹คํŒจํ–ˆ์ง€๋งŒ ์„œ๋น„์Šค๋Š” ๊ณ„์†API ํ˜ธ์ถœ ์‹คํŒจ, ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ, ํƒ€์ž„์•„์›ƒ
CRITICAL50์„œ๋น„์Šค ์ž์ฒด๊ฐ€ ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ๋Š” ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜DB ์—ฐ๊ฒฐ ๋ถˆ๊ฐ€, ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ, ์„ค์ • ํŒŒ์ผ ๋ˆ„๋ฝ
  • ๋กœ๊ฑฐ์™€ ํ•ธ๋“ค๋Ÿฌ ๊ฐ๊ฐ ๋ ˆ๋ฒจ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ๋‘˜ ๋‹ค ํ†ต๊ณผํ•œ ๋ฉ”์‹œ์ง€๋งŒ ์ถœ๋ ฅ๋จ
  • ๋ ˆ๋ฒจ์€ ์ˆซ์ž ๋น„๊ต : ์„ค์ • ๋ ˆ๋ฒจ ์ด์ƒ์ธ ๋ฉ”์‹œ์ง€๋งŒ ํ†ต๊ณผ
๋ ˆ๋ฒจ๋ณ„ ์‚ฌ์šฉ ์˜ˆ์‹œ
import logging
logger = logging.getLogger(__name__)
 
# DEBUG โ€” ๊ฐœ๋ฐœ ์ค‘์—๋งŒ ํ™•์ธํ•  ์ƒ์„ธ ์ •๋ณด
logger.debug("query=%s params=%s", sql, params)
 
# INFO โ€” ์ •์ƒ ๋™์ž‘์˜ ์ด์ •ํ‘œ
logger.info("์„œ๋น„์Šค ์‹œ์ž‘ port=%d", 8080)
logger.info("๋ฐฐ์น˜ ์ž‘์—… ์™„๋ฃŒ rows=%d elapsed=%.2fs", count, elapsed)
 
# WARNING โ€” ์•„์ง ๊ดœ์ฐฎ์ง€๋งŒ ๊ณง ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ
logger.warning("๋””์Šคํฌ ์‚ฌ์šฉ๋Ÿ‰ %d ์ดˆ๊ณผ ์‹œ ์•Œ๋ฆผ ๋ฐœ์ƒ", usage)
logger.warning("API ์‘๋‹ต ์ง€์—ฐ %.1fs โ€” ํƒ€์ž„์•„์›ƒ ๊ธฐ์ค€ 5s", response_time)
 
# ERROR โ€” ํŠน์ • ๊ธฐ๋Šฅ์ด ์‹คํŒจ
logger.error("ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ: %s", filepath, exc_info=True)
logger.error("์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹คํŒจ status=%d", response.status_code)
 
# CRITICAL โ€” ์„œ๋น„์Šค ์ž์ฒด๊ฐ€ ์œ„ํ—˜
logger.critical("DB ์—ฐ๊ฒฐ ๋ถˆ๊ฐ€ โ€” ์„œ๋น„์Šค๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค")

exc_info=True

  • ์—๋Ÿฌ ๋กœ๊ทธ์— ์Šคํƒ ํŠธ๋ ˆ์ด์Šค(Traceback) ๋ฅผ ์ž๋™์œผ๋กœ ํฌํ•จ์‹œํ‚ด
  • logger.exception("๋ฉ”์‹œ์ง€")๋Š” logger.error("๋ฉ”์‹œ์ง€", exc_info=True)์˜ ์ถ•์•ฝํ˜•
ํ™˜๊ฒฝ๋ณ„ ๋ ˆ๋ฒจ ์„ค์ • ๊ฐ€์ด๋“œ
ํ™˜๊ฒฝ๊ถŒ์žฅ ๋ ˆ๋ฒจ์ด์œ 
๋กœ์ปฌ ๊ฐœ๋ฐœDEBUG์ตœ๋Œ€ํ•œ ๋งŽ์€ ์ •๋ณด๋กœ ๋””๋ฒ„๊น…
๊ฐœ๋ฐœ ์„œ๋ฒ„ (dev/staging)DEBUG ๋˜๋Š” INFOํ…Œ์ŠคํŠธ ์ค‘ ์ƒ์„ธ ์ถ”์  ํ•„์š”
์šด์˜ ์„œ๋ฒ„ (production)INFO ๋˜๋Š” WARNING๋ถˆํ•„์š”ํ•œ ๋กœ๊ทธ๋กœ ๋””์Šคํฌ/์„ฑ๋Šฅ ๋‚ญ๋น„ ๋ฐฉ์ง€

logging ๋ชจ๋“ˆ์˜ ๊ตฌ์„ฑ ์š”์†Œ

์ „์ฒด ํ๋ฆ„
๊ตฌ์„ฑ ์š”์†Œ์—ญํ• ๋น„์œ 
Logger๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ „๋‹ฌโ€๊ธฐ์žโ€ โ€” ๊ธฐ์‚ฌ๋ฅผ ์ž‘์„ฑ
Handler๋ฉ”์‹œ์ง€๋ฅผ ์–ด๋””๋กœ ๋ณด๋‚ผ์ง€ ๊ฒฐ์ •โ€๋ฐฐ๋‹ฌ๋ถ€โ€ โ€” ์‹ ๋ฌธ์‚ฌ/๋ฐฉ์†ก๊ตญ/์›น์— ์ „๋‹ฌ
Formatter๋ฉ”์‹œ์ง€์˜ ์ถœ๋ ฅ ํ˜•์‹์„ ๊ฒฐ์ •โ€ํŽธ์ง‘์žโ€ โ€” ๊ธฐ์‚ฌ์˜ ๋ ˆ์ด์•„์›ƒ ๊ฒฐ์ •
Filter์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฑธ๋Ÿฌ๋ƒ„โ€ํŽธ์ง‘์žฅโ€ โ€” ๊ฒŒ์žฌ ์—ฌ๋ถ€ ํŒ๋‹จ
๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ํ๋ฆ„
logger.info("๋ฉ”์‹œ์ง€")
    โ”‚
    โ–ผ
[Logger] ๋ ˆ๋ฒจ ํ™•์ธ (INFO >= ์„ค์ • ๋ ˆ๋ฒจ?)
    โ”‚ Yes
    โ–ผ
[Filter] ํ•„ํ„ฐ ํ†ต๊ณผ?
    โ”‚ Yes
    โ–ผ
[Handler] ๋ ˆ๋ฒจ ํ™•์ธ + ์ถœ๋ ฅ ๋Œ€์ƒ ๊ฒฐ์ •
    โ”‚
    โ–ผ
[Formatter] ํ˜•์‹ ์ ์šฉ โ†’ ์ตœ์ข… ์ถœ๋ ฅ
Logger โ€” ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฐ์ฒด
Logger ๊ธฐ๋ณธ ์‚ฌ์šฉ
import logging
 
# ๋กœ๊ฑฐ ์ƒ์„ฑ โ€” ๋ณดํ†ต ๋ชจ๋“ˆ ์ด๋ฆ„(__name__)์„ ์‚ฌ์šฉ
logger = logging.getLogger(__name__)
# __name__ = "my_app.utils.db" ๊ฐ™์€ ํ˜•ํƒœ
# โ†’ ๋กœ๊ทธ์— ์–ด๋А ๋ชจ๋“ˆ์—์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์ž๋™ ๊ธฐ๋ก
 
# ๋กœ๊ฑฐ๋Š” ๊ณ„์ธต์  โ€” ์ (.)์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค
# "my_app" โ† "my_app.utils" โ† "my_app.utils.db"
# ์ž์‹ ๋กœ๊ฑฐ์˜ ๋ฉ”์‹œ์ง€๋Š” ๋ถ€๋ชจ๋กœ ์ „ํŒŒ (propagate=True๊ฐ€ ๊ธฐ๋ณธ)
Handler โ€” ๋กœ๊ทธ๋ฅผ ์–ด๋””๋กœ ๋ณด๋‚ด๋Š”๊ฐ€
Handler์ถœ๋ ฅ ๋Œ€์ƒ์šฉ๋„
StreamHandler์ฝ˜์†” (stdout/stderr)๊ฐœ๋ฐœ ์ค‘ ์‹ค์‹œ๊ฐ„ ํ™•์ธ
FileHandlerํŒŒ์ผ๊ธฐ๋ณธ์ ์ธ ํŒŒ์ผ ๋กœ๊น…
RotatingFileHandlerํŒŒ์ผ (ํฌ๊ธฐ ๊ธฐ์ค€ ํšŒ์ „)๋กœ๊ทธ ํŒŒ์ผ์ด ๋ฌดํ•œํžˆ ์ปค์ง€๋Š” ๊ฒƒ ๋ฐฉ์ง€
TimedRotatingFileHandlerํŒŒ์ผ (์‹œ๊ฐ„ ๊ธฐ์ค€ ํšŒ์ „)๋‚ ์งœ๋ณ„ ๋กœ๊ทธ ํŒŒ์ผ ๊ด€๋ฆฌ
QueueHandlerํ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์Šค ์•ˆ์ „ํ•œ ๋กœ๊น…
SysLogHandler์‹œ์Šคํ…œ ๋กœ๊ทธLinux syslog ์—ฐ๋™
SMTPHandler์ด๋ฉ”์ผCRITICAL ์—๋Ÿฌ ์‹œ ์•Œ๋ฆผ ๋ฉ”์ผ
ํ•˜๋‚˜์˜ ๋กœ๊ฑฐ์— ์—ฌ๋Ÿฌ Handler ๋ถ€์ฐฉ
import logging
 
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)       # ๋กœ๊ฑฐ ์ž์ฒด๋Š” DEBUG๋ถ€ํ„ฐ ์ˆ˜์šฉ
 
# ์ฝ˜์†” โ€” INFO ์ด์ƒ๋งŒ ์ถœ๋ ฅ
console = logging.StreamHandler()
console.setLevel(logging.INFO)
 
# ํŒŒ์ผ โ€” DEBUG ์ด์ƒ ์ „๋ถ€ ๊ธฐ๋ก (์ƒ์„ธ ๋กœ๊ทธ)
file = logging.FileHandler("debug.log", encoding="utf-8")
file.setLevel(logging.DEBUG)
 
# ๊ฐ™์€ ๋ฉ”์‹œ์ง€๊ฐ€ ์ฝ˜์†”์—๋Š” INFO๋ถ€ํ„ฐ, ํŒŒ์ผ์—๋Š” DEBUG๋ถ€ํ„ฐ ๊ธฐ๋ก๋จ
logger.addHandler(console)
logger.addHandler(file)
Formatter โ€” ์ถœ๋ ฅ ํ˜•์‹ ์ •์˜
Formatter ์ฃผ์š” ํ•„๋“œ
# ์ž์ฃผ ์“ฐ๋Š” ํฌ๋งท ํ•„๋“œ
fmt = logging.Formatter(
    "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
# ์ถœ๋ ฅ ์˜ˆ์‹œ:
# 2026-04-05 14:30:22 [INFO] my_app.api:45 - ์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ
ํ•„๋“œ์„ค๋ช…์˜ˆ์‹œ
%(asctime)s์‹œ๊ฐ„2026-04-05 14:30:22
%(levelname)s๋ ˆ๋ฒจ ์ด๋ฆ„INFO, ERROR
%(name)s๋กœ๊ฑฐ ์ด๋ฆ„my_app.utils
%(module)s๋ชจ๋“ˆ๋ช…utils
%(funcName)sํ•จ์ˆ˜๋ช…process_request
%(lineno)d์ค„ ๋ฒˆํ˜ธ45
%(message)s๋กœ๊ทธ ๋ฉ”์‹œ์ง€์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ
%(process)dํ”„๋กœ์„ธ์Šค ID12345
%(thread)d์Šค๋ ˆ๋“œ ID140234
Filter โ€” ์กฐ๊ฑด๋ถ€ ํ•„ํ„ฐ๋ง
Filter ์˜ˆ์‹œ
class OnlyErrorFilter(logging.Filter):
    """ERROR ๋ ˆ๋ฒจ๋งŒ ํ†ต๊ณผ์‹œํ‚ค๋Š” ํ•„ํ„ฐ"""
    def filter(self, record):
        return record.levelno == logging.ERROR
 
# ์—๋Ÿฌ ์ „์šฉ ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ
error_handler = logging.FileHandler("error.log")
error_handler.addFilter(OnlyErrorFilter())
logger.addHandler(error_handler)
Propagation โ€” ๋ถ€๋ชจ ๋กœ๊ฑฐ๋กœ์˜ ์ „ํŒŒ
์ „ํŒŒ ๋™์ž‘๊ณผ ์ค‘๋ณต ์ถœ๋ ฅ ๋ฐฉ์ง€
# ๋กœ๊ฑฐ ๊ณ„์ธต:  root โ† my_app โ† my_app.api
 
# root ๋กœ๊ฑฐ์— ์ฝ˜์†” ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์žˆ๊ณ 
logging.basicConfig(level=logging.INFO)
 
# my_app.api ๋กœ๊ฑฐ์—๋„ ์ฝ˜์†” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ถ€์ฐฉํ•˜๋ฉด
api_logger = logging.getLogger("my_app.api")
api_logger.addHandler(logging.StreamHandler())
 
# โ†’ ๋ฉ”์‹œ์ง€๊ฐ€ 2๋ฒˆ ์ถœ๋ ฅ๋จ! (์ž๊ธฐ ํ•ธ๋“ค๋Ÿฌ + root๋กœ ์ „ํŒŒ)
 
# ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 1: ์ „ํŒŒ ๋„๊ธฐ
api_logger.propagate = False
 
# ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 2: ์ž์‹ ๋กœ๊ฑฐ์—๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ถ™์ด์ง€ ์•Š๊ณ  root๋งŒ ์„ค์ •

์‹ค์ „ ์ฝ”๋“œ ์Šค๋‹ˆํŽซ

1. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์„ค์ • โ€” basicConfig
์†Œ๊ทœ๋ชจ ์Šคํฌ๋ฆฝํŠธ์— ์ ํ•ฉ
import logging
 
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
 
logger = logging.getLogger(__name__)
logger.info("์ž‘์—… ์‹œ์ž‘")
logger.warning("๋””์Šคํฌ ์‚ฌ์šฉ๋Ÿ‰ 85%%")
# 2026-04-05 14:30:22 [INFO] ์ž‘์—… ์‹œ์ž‘
# 2026-04-05 14:30:22 [WARNING] ๋””์Šคํฌ ์‚ฌ์šฉ๋Ÿ‰ 85%

basicConfig ์ฃผ์˜์‚ฌํ•ญ

  • ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ ๊ฐ€๋Šฅ โ€” ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ๋ถ€ํ„ฐ๋Š” ๋ฌด์‹œ๋จ
  • ์ด๋ฏธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์„ค์ •๋œ ์ƒํƒœ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ํšจ๊ณผ ์—†์Œ
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ์—์„œ๋Š” ํ˜ธ์ถœํ•˜์ง€ ๋ง ๊ฒƒ (์•ฑ ์ง„์ž…์ ์—์„œ๋งŒ)
2. ํฌ๊ธฐ ๊ธฐ์ค€ ํšŒ์ „ ๋กœ๊ทธ โ€” RotatingFileHandler
ํŒŒ์ผ์ด ๋ฌดํ•œํžˆ ์ปค์ง€๋Š” ๊ฒƒ ๋ฐฉ์ง€
import logging
from logging.handlers import RotatingFileHandler
 
handler = RotatingFileHandler(
    filename="app.log",
    maxBytes=5 * 1024 * 1024,   # 5MB ์ดˆ๊ณผ ์‹œ ๋กค์˜ค๋ฒ„
    backupCount=3,               # app.log.1 ~ app.log.3 ๋ณด์กด
    encoding="utf-8"
)
 
logging.basicConfig(
    level=logging.INFO,
    handlers=[handler, logging.StreamHandler()],
    format="%(asctime)s %(levelname)s %(name)s: %(message)s"
)
 
logger = logging.getLogger(__name__)
logger.info("์„œ๋น„์Šค ์‹œ์ž‘")
ํŒŒ์ผ ํšŒ์ „ ๋™์ž‘
app.log        โ† ํ˜„์žฌ ๋กœ๊ทธ (5MB ์ดˆ๊ณผ ์‹œ)
app.log.1      โ† ์ง์ „ ๋กœ๊ทธ
app.log.2      โ† ๊ทธ ์ „ ๋กœ๊ทธ
app.log.3      โ† ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ๋กœ๊ทธ (์ดํ›„ ์‚ญ์ œ)
3. ์‹œ๊ฐ„ ๊ธฐ์ค€ ํšŒ์ „ ๋กœ๊ทธ โ€” TimedRotatingFileHandler
๋‚ ์งœ๋ณ„ ๋กœ๊ทธ ํŒŒ์ผ ๊ด€๋ฆฌ
import logging
from logging.handlers import TimedRotatingFileHandler
 
handler = TimedRotatingFileHandler(
    filename="access.log",
    when="midnight",    # ๋งค์ผ ์ž์ •์— ํšŒ์ „
    interval=1,         # 1์ผ ์ฃผ๊ธฐ
    backupCount=14,     # 2์ฃผ์น˜ ๋ณด๊ด€
    encoding="utf-8"
)
handler.suffix = "%Y-%m-%d"   # access.log.2026-04-05 ํ˜•ํƒœ
 
logging.basicConfig(
    level=logging.INFO,
    handlers=[handler],
    format="%(asctime)s %(levelname)s %(message)s"
)
when ๊ฐ’ํšŒ์ „ ์ฃผ๊ธฐ
S๋งค์ดˆ
M๋งค๋ถ„
H๋งค์‹œ๊ฐ„
D๋งค์ผ
midnight๋งค์ผ ์ž์ •
W0 ~ W6๋งค์ฃผ ์›”~์ผ
4. JSON ๊ตฌ์กฐํ™” ๋กœ๊ทธ
๋กœ๊ทธ ๋ถ„์„ ์‹œ์Šคํ…œ(ELK ๋“ฑ)๊ณผ ์—ฐ๋™ํ•  ๋•Œ ํ•„์ˆ˜
import logging, json, datetime, sys
 
class JsonFormatter(logging.Formatter):
    """๋กœ๊ทธ ๋ ˆ์ฝ”๋“œ๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ์ง๋ ฌํ™”"""
    def format(self, record):
        return json.dumps({
            "ts": datetime.datetime.utcfromtimestamp(record.created).isoformat() + "Z",
            "lvl": record.levelname,
            "msg": record.getMessage(),
            "logger": record.name,
            "lineno": record.lineno
        }, ensure_ascii=False)
 
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
 
logger = logging.getLogger("json")
logger.setLevel(logging.INFO)
logger.addHandler(handler)
 
logger.info("user_login user_id=42")
# {"ts": "2026-04-05T05:30:22Z", "lvl": "INFO", "msg": "user_login user_id=42", ...}

์™œ JSON ๋กœ๊ทธ๋ฅผ ์“ฐ๋Š”๊ฐ€

  • ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋กœ๊ทธ๋ฅผ ์‚ฌ๋žŒ์ด ์ง์ ‘ ์ฝ๊ธฐ๋ณด๋‹ค ELK(Elasticsearch + Logstash + Kibana), Datadog, CloudWatch ๊ฐ™์€ ๋„๊ตฌ๋กœ ์ˆ˜์ง‘ยท๊ฒ€์ƒ‰
  • ์ด๋Ÿฐ ๋„๊ตฌ๋“ค์€ JSON ํ˜•ํƒœ์˜ ๊ตฌ์กฐํ™”๋œ ๋กœ๊ทธ๋ฅผ ํŒŒ์‹ฑํ•˜๊ธฐ ํ›จ์”ฌ ์‰ฌ์›€
  • ํ‰๋ฌธ ๋กœ๊ทธ: grep์œผ๋กœ ์ฐพ์•„์•ผ ํ•จ / JSON ๋กœ๊ทธ: ํ•„๋“œ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ ๊ฐ€๋Šฅ
5. dictConfig โ€” ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ ์„ค์ •
์„ค์ •์„ ์ฝ”๋“œ์™€ ๋ถ„๋ฆฌํ•  ๋•Œ ์‚ฌ์šฉ
import logging.config
 
LOG_CFG = {
    "version": 1,
    "disable_existing_loggers": False,   # ๊ธฐ์กด ๋กœ๊ฑฐ ์œ ์ง€
    "formatters": {
        "std": {
            "format": "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d โ€” %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "std",
            "level": "INFO"
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": "service.log",
            "maxBytes": 10 * 1024 * 1024,   # 10MB
            "backupCount": 5,
            "formatter": "std",
            "encoding": "utf-8"
        }
    },
    "root": {
        "level": "INFO",
        "handlers": ["console", "file"]
    },
    "loggers": {
        "sqlalchemy.engine": {
            "level": "WARNING"    # ๋„ˆ๋ฌด ์ƒ์„ธํ•œ SQL ๋กœ๊ทธ ์–ต์ œ
        }
    }
}
 
logging.config.dictConfig(LOG_CFG)

basicConfig vs dictConfig

  • basicConfig : ์†Œ๊ทœ๋ชจ ์Šคํฌ๋ฆฝํŠธ, ๋น ๋ฅธ ์„ค์ •
  • dictConfig : ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ์ด์ชฝ์œผ๋กœ ์ „ํ™˜
  • ์‹ค๋ฌด์—์„œ๋Š” YAML/JSON ํŒŒ์ผ์— ์„ค์ •์„ ๋ถ„๋ฆฌํ•˜๊ณ  dictConfig๋กœ ๋กœ๋“œํ•˜๋Š” ํŒจํ„ด์ด ์ผ๋ฐ˜์ 
6. LoggerAdapter โ€” ์š”์ฒญ๋ณ„ ์ปจํ…์ŠคํŠธ ์ž๋™ ์ฃผ์ž…
API ์„œ๋ฒ„์—์„œ ์š”์ฒญ ID๋ฅผ ๋ชจ๋“  ๋กœ๊ทธ์— ์ž๋™ ํฌํ•จ
import logging, uuid
 
logger = logging.getLogger("api")
logger.setLevel(logging.INFO)
 
class RequestAdapter(logging.LoggerAdapter):
    """์š”์ฒญ ID๋ฅผ ์ž๋™ ์ฃผ์ž…ํ•˜๋Š” ์–ด๋Œ‘ํ„ฐ"""
    def process(self, msg, kwargs):
        return f"[req={self.extra['req_id']}] {msg}", kwargs
 
# ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ์–ด๋Œ‘ํ„ฐ ์ƒ์„ฑ
req_id = uuid.uuid4().hex[:8]
req_logger = RequestAdapter(logger, {"req_id": req_id})
 
req_logger.info("์š”์ฒญ ์‹œ์ž‘")
req_logger.info("DB ์กฐํšŒ ์™„๋ฃŒ rows=15")
req_logger.error("์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ", exc_info=True)
# [req=a3f2c1b8] ์š”์ฒญ ์‹œ์ž‘
# [req=a3f2c1b8] DB ์กฐํšŒ ์™„๋ฃŒ rows=15
# [req=a3f2c1b8] ์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ (+ Traceback)
7. ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์Šค ์•ˆ์ „ ๋กœ๊น… โ€” QueueHandler
์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋™์‹œ์— ๋กœ๊ทธ๋ฅผ ์“ธ ๋•Œ
from multiprocessing import Process, Queue
import logging, logging.handlers, os
 
def worker(q, n):
    """์ž์‹ ํ”„๋กœ์„ธ์Šค โ€” ํ ํ•ธ๋“ค๋Ÿฌ๋งŒ ๋ถ€์ฐฉ"""
    root = logging.getLogger()
    root.setLevel(logging.INFO)
    root.addHandler(logging.handlers.QueueHandler(q))
    root.info("child-%d pid=%d ์ž‘์—… ์‹œ์ž‘", n, os.getpid())
 
if __name__ == "__main__":
    q = Queue(-1)
 
    # ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์—์„œ ํ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ์‹ค์ œ ํŒŒ์ผ ์“ฐ๊ธฐ ๋‹ด๋‹น
    listener = logging.handlers.QueueListener(
        q,
        logging.FileHandler("mp.log", encoding="utf-8"),
        logging.StreamHandler()
    )
    listener.start()
 
    ps = [Process(target=worker, args=(q, i)) for i in range(4)]
    for p in ps: p.start()
    for p in ps: p.join()
 
    listener.stop()

๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์Šค์—์„œ FileHandler๋ฅผ ์ง์ ‘ ์“ฐ๋ฉด ์•ˆ ๋˜๋Š” ์ด์œ 

  • ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๊ฐ™์€ ํŒŒ์ผ์— ๋™์‹œ์— ์“ฐ๋ฉด ๋กœ๊ทธ๊ฐ€ ๋’ค์„ž์ด๊ฑฐ๋‚˜ ์†์ƒ๋  ์ˆ˜ ์žˆ์Œ
  • QueueHandler + QueueListener ํŒจํ„ด์œผ๋กœ ํ•œ ํ”„๋กœ์„ธ์Šค๋งŒ ํŒŒ์ผ์— ์“ฐ๊ฒŒ ํ•ด์•ผ ์•ˆ์ „
8. ์ปฌ๋Ÿฌ ์ฝ˜์†” โ€” colorlog
ํ„ฐ๋ฏธ๋„์—์„œ ๋ ˆ๋ฒจ๋ณ„ ์ƒ‰์ƒ ๊ตฌ๋ถ„
import logging, colorlog   # uv add colorlog
 
formatter = colorlog.ColoredFormatter(
    "%(log_color)s%(levelname)-8s%(reset)s %(blue)s%(message)s",
    log_colors={
        "DEBUG": "cyan",
        "INFO": "green",
        "WARNING": "yellow",
        "ERROR": "red",
        "CRITICAL": "bold_red"
    }
)
 
handler = colorlog.StreamHandler()
handler.setFormatter(formatter)
 
logger = colorlog.getLogger("color")
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
 
logger.debug("์ƒ์„ธ ์ถ”์  ์ •๋ณด")
logger.info("์„œ๋น„์Šค ์‹œ์ž‘")
logger.warning("๋””์Šคํฌ ๊ฑฐ์˜ ๊ฐ€๋“ ์ฐธ")
logger.error("ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ")
logger.critical("์‹œ์Šคํ…œ ๋‹ค์šด")

ํ”ํ•œ ์‹ค์ˆ˜์™€ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์‹ค์ˆ˜ 1: ๋ฌธ์ž์—ด ํฌ๋งทํŒ…์„ ์ง์ ‘ ํ•˜๋Š” ๊ฒƒ
โŒ ๋‚˜์œ ์˜ˆ
# f-string์œผ๋กœ ์ง์ ‘ ํฌ๋งทํŒ… โ€” ๋กœ๊ทธ ๋ ˆ๋ฒจ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒ ๋ฌธ์ž์—ด ์ƒ์„ฑ ๋น„์šฉ ๋ฐœ์ƒ
logger.debug(f"query result: {expensive_query()}")
 
# โœ… ์ข‹์€ ์˜ˆ โ€” ๋กœ๊ฑฐ์˜ ์ง€์—ฐ ํฌ๋งทํŒ… ์‚ฌ์šฉ
# DEBUG ๋ ˆ๋ฒจ์ด ๊บผ์ ธ์žˆ์œผ๋ฉด ํฌ๋งทํŒ… ์ž์ฒด๋ฅผ ํ•˜์ง€ ์•Š์Œ (์„ฑ๋Šฅ ์ด์ )
logger.debug("query result: %s", expensive_query())
์‹ค์ˆ˜ 2: ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ basicConfig ํ˜ธ์ถœ
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ์—์„œ์˜ ์˜ฌ๋ฐ”๋ฅธ ํŒจํ„ด
# โŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ „์—ญ ๋กœ๊น… ์„ค์ •์„ ๊ฑด๋“œ๋ฆผ
logging.basicConfig(level=logging.DEBUG)
 
# โœ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋กœ๊ฑฐ๋งŒ ์ƒ์„ฑํ•˜๊ณ , ์„ค์ •์€ ์•ฑ์—๊ฒŒ ๋งก๊น€
logger = logging.getLogger(__name__)
# ํ•ธ๋“ค๋Ÿฌ๋„ ๋ถ™์ด์ง€ ์•Š์Œ โ€” ์•ฑ์ด ์•Œ์•„์„œ ์„ค์ •
์‹ค์ˆ˜ 3: ๋ชจ๋“  ๊ณณ์—์„œ root ๋กœ๊ฑฐ ์‚ฌ์šฉ
๋กœ๊ฑฐ ์ด๋ฆ„์„ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ์ด์œ 
# โŒ root ๋กœ๊ฑฐ โ€” ์–ด๋””์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๊ตฌ๋ถ„ ๋ถˆ๊ฐ€
logging.info("์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
# 2026-04-05 [INFO] root: ์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ
 
# โœ… ๋ชจ๋“ˆ๋ณ„ ๋กœ๊ฑฐ โ€” ์ถœ์ฒ˜๊ฐ€ ๋ช…ํ™•
logger = logging.getLogger(__name__)
logger.info("์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
# 2026-04-05 [INFO] my_app.api.views: ์š”์ฒญ ์ฒ˜๋ฆฌ ์™„๋ฃŒ
๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ์š”์•ฝ
๊ทœ์น™์„ค๋ช…
๋กœ๊ฑฐ ์ด๋ฆ„์€ __name__ ์‚ฌ์šฉ๋ชจ๋“ˆ๋ณ„ ๊ตฌ๋ถ„ + ๊ณ„์ธต์  ๊ด€๋ฆฌ
ํฌ๋งทํŒ…์€ %s ๋ฐฉ์‹์ง€์—ฐ ํ‰๊ฐ€๋กœ ์„ฑ๋Šฅ ์ด์ 
exc_info=True ํ™œ์šฉ์—๋Ÿฌ ๋กœ๊ทธ์— Traceback ์ž๋™ ํฌํ•จ
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํ•ธ๋“ค๋Ÿฌ ๋ถ™์ด์ง€ ์•Š์Œ์„ค์ •์€ ์•ฑ ์ง„์ž…์ ์—์„œ๋งŒ
์šด์˜์—์„œ๋Š” INFO ์ด์ƒDEBUG ๋กœ๊ทธ๋Š” ๋””์Šคํฌ/์„ฑ๋Šฅ ๋ถ€๋‹ด
๊ตฌ์กฐํ™” ๋กœ๊ทธ(JSON) ๊ณ ๋ ค๋กœ๊ทธ ์ˆ˜์ง‘ ์‹œ์Šคํ…œ๊ณผ ์—ฐ๋™ ์šฉ์ด

์šด์˜ ํ™˜๊ฒฝ์—์„œ์˜ ๋กœ๊น…

๋กœ๊ทธ ์ˆ˜์ง‘ ์•„ํ‚คํ…์ฒ˜
์ผ๋ฐ˜์ ์ธ ์šด์˜ ๋กœ๊ทธ ํ๋ฆ„
์•ฑ ์„œ๋ฒ„ (Python)
    โ”‚ logging โ†’ JSON ํŒŒ์ผ
    โ–ผ
๋กœ๊ทธ ์ˆ˜์ง‘๊ธฐ (Filebeat, Fluentd ๋“ฑ)
    โ”‚ ํŒŒ์ผ์„ ์‹ค์‹œ๊ฐ„ ๊ฐ์‹œํ•˜์—ฌ ์ „์†ก
    โ–ผ
๋กœ๊ทธ ์ €์žฅ์†Œ (Elasticsearch, CloudWatch, Datadog ๋“ฑ)
    โ”‚
    โ–ผ
์‹œ๊ฐํ™”/๊ฒ€์ƒ‰ (Kibana, Grafana ๋“ฑ)
    โ†’ ๋Œ€์‹œ๋ณด๋“œ, ์•Œ๋ฆผ, ๊ฒ€์ƒ‰
์šด์˜ ๋กœ๊น… ์ฒดํฌ๋ฆฌ์ŠคํŠธ
ํ•ญ๋ชฉํ™•์ธ ์‚ฌํ•ญ
๋กœ๊ทธ ํšŒ์ „RotatingFileHandler ๋˜๋Š” logrotate ์„ค์ • โ€” ๋””์Šคํฌ ๊ฐ€๋“ ์ฐธ ๋ฐฉ์ง€
์ธ์ฝ”๋”ฉencoding="utf-8" โ€” ํ•œ๊ธ€ ๊นจ์ง ๋ฐฉ์ง€
๋ฏผ๊ฐ ์ •๋ณด๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ, ๊ฐœ์ธ์ •๋ณด๊ฐ€ ๋กœ๊ทธ์— ํฌํ•จ๋˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ
๋กœ๊ทธ ๋ ˆ๋ฒจ์šด์˜์€ INFO ์ด์ƒ โ€” DEBUG๋Š” ํ•„์š”ํ•  ๋•Œ๋งŒ ์ž„์‹œ๋กœ
๊ตฌ์กฐํ™”JSON ํฌ๋งท โ€” ๋กœ๊ทธ ์ˆ˜์ง‘ ์‹œ์Šคํ…œ๊ณผ์˜ ์—ฐ๋™ ์šฉ์ด
ํƒ€์ž„์กดUTC ์‚ฌ์šฉ ๊ถŒ์žฅ โ€” ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋ฆฌ์ „์— ์žˆ์„ ๋•Œ ํ˜ผ๋ž€ ๋ฐฉ์ง€