Alert
์ด ๊ธ์ Claude Code์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค
์์๋ณผ ๋ด์ฉ
- ์ print๊ฐ ์๋๋ผ logging์ธ๊ฐ
- ๋ก๊ทธ ๋ ๋ฒจ๊ณผ ์ค๋ฌด ๊ธฐ์ค
- logging ๋ชจ๋์ ๊ตฌ์ฑ ์์
- ์ค์ ์ฝ๋ ์ค๋ํซ
- ํํ ์ค์์ ๋ฒ ์คํธ ํ๋ํฐ์ค
- ์ด์ ํ๊ฒฝ์์์ ๋ก๊น
Reference
- Python Logging HOWTO Python Docs
- RotatingFileHandler Python Library
- colorlog GitHub
์ print๊ฐ ์๋๋ผ logging์ธ๊ฐ
print์ ํ๊ณ
print()๋ ๊ฐ์ฅ ์ฌ์ด ๋๋ฒ๊น ๋๊ตฌ์ด์ง๋ง ์ค๋ฌด์์๋ ๊ธ๋ฐฉ ํ๊ณ์ ๋ถ๋ชํ
| ํญ๋ชฉ | print() | logging |
|---|---|---|
| ์ถ๋ ฅ ๋์ | stdout๋ง | ์ฝ์, ํ์ผ, ๋คํธ์ํฌ, DB ๋ฑ ์์ ๋กญ๊ฒ |
| ์ฌ๊ฐ๋ ๊ตฌ๋ถ | โ ์ ๋ถ ๋์ผ | โ DEBUG ~ CRITICAL 5๋จ๊ณ |
| ์ด์ ์ค ๋๊ธฐ | ์ฝ๋๋ฅผ ์ง์ ์ง์์ผ ํจ | ๋ ๋ฒจ๋ง ์ฌ๋ฆฌ๋ฉด ๋จ (์ฝ๋ ์์ ๋ถํ์) |
| ์๊ฐ/์์น ์ ๋ณด | ์ง์ ๋ฃ์ด์ผ ํจ | ์๋ ํฌํจ (์๊ฐ, ํ์ผ๋ช , ์ค ๋ฒํธ) |
| ํ์ผ ์ ์ฅ | ๋ฆฌ๋ค์ด๋ ์ ํ์ | FileHandler๋ก ๋ฐ๋ก ๊ฐ๋ฅ |
| ๋ฉํฐ๋ชจ๋ | ์ด๋์ ์ฐ์๋์ง ์ถ์ ์ด๋ ค์ | ๋ก๊ฑฐ ์ด๋ฆ์ผ๋ก ๋ชจ๋๋ณ ๊ตฌ๋ถ |
# 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๋จ๊ณ ์ฌ๊ฐ๋
| ๋ ๋ฒจ | ์ซ์ | ์๋ฏธ | ์ค๋ฌด ๊ธฐ์ค |
|---|---|---|---|
| DEBUG | 10 | ๊ฐ๋ฐ ์ค ์์ธ ์ถ์ ์ ๋ณด | ๋ณ์ ๊ฐ, ๋ถ๊ธฐ ์ง์ , SQL ์ฟผ๋ฆฌ ๋ฑ โ ์ด์์์๋ ๊บผ๋ |
| INFO | 20 | ์ ์์ ์ธ ๋์ ํ์ธ | ์๋น์ค ์์/์ข ๋ฃ, ์์ ์๋ฃ, ์์ฒญ ์ฒ๋ฆฌ ๋ฑ |
| WARNING | 30 | ๋น์ฅ ๋ฌธ์ ๋ ์๋์ง๋ง ์ฃผ์ ํ์ | ๋์คํฌ 80% ๋๋ฌ, deprecated API ํธ์ถ, ์ฌ์๋ ๋ฐ์ |
| ERROR | 40 | ๊ธฐ๋ฅ์ด ์คํจํ์ง๋ง ์๋น์ค๋ ๊ณ์ | API ํธ์ถ ์คํจ, ํ์ผ ์ด๊ธฐ ์คํจ, ํ์์์ |
| CRITICAL | 50 | ์๋น์ค ์์ฒด๊ฐ ์ค๋จ๋ ์ ์๋ ์ฌ๊ฐํ ์ค๋ฅ | 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 โ ๋ฉ์์ง๋ฅผ ์์ฑํ๋ ๊ฐ์ฒด
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 ์๋ฌ ์ ์๋ฆผ ๋ฉ์ผ |
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 โ ์ถ๋ ฅ ํ์ ์ ์
# ์์ฃผ ์ฐ๋ ํฌ๋งท ํ๋
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 | ํ๋ก์ธ์ค ID | 12345 |
%(thread)d | ์ค๋ ๋ ID | 140234 |
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 ๊ตฌ์กฐํ ๋ก๊ทธ
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 โ ์์ฒญ๋ณ ์ปจํ
์คํธ ์๋ ์ฃผ์
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 ์ฌ์ฉ ๊ถ์ฅ โ ์๋ฒ๊ฐ ์ฌ๋ฌ ๋ฆฌ์ ์ ์์ ๋ ํผ๋ ๋ฐฉ์ง |