<aside>
</aside>
μλ
νμΈμ, loguru μμ μ 볡μ λ§μ§λ§ μ€μ΅μ μ€μ κ²μ νμν©λλ€! μ°λ¦¬λ μ§κΈκΉμ§ λ‘κ·Έλ₯Ό ν°λ―Έλκ³Ό νμΌμ μμ£Ό νλ₯νκ² κΈ°λ‘ν΄μμ΅λλ€. νμ§λ§ μλΉμ€μ μΉλͺ
μ μΈ μ€λ₯κ° μλ²½ 3μμ λ°μνλ€λ©΄, λ€μ λ μμΉ¨μ λ‘κ·Έ νμΌμ μ΄μ΄λ³΄κΈ° μ κΉμ§λ μ무λ κ·Έ μ¬μ€μ μ μ μκ² μ£ ? π¨
μ΄λ² μ±ν°μμλ loguruμ νμ₯μ±μ μ΅λλ‘ νμ©νμ¬, μ°λ¦¬μ λ‘κΉ
μμ€ν
μ μ€μκ° **'μ₯μ κ°μ§ λ° μλ¦Ό μμ€ν
'**μΌλ‘ μ§νμμΌ λ³΄κ² μ΅λλ€. loguruμ 컀μ€ν
μ±ν¬(Sink) κΈ°λ₯μ μ¬μ©νμ¬, ERROR λ 벨 μ΄μμ λ‘κ·Έκ° λ°μνμ λ κ°λ°νμ νμ νμ
λκ΅¬μΈ **μ¬λ(Slack)**μΌλ‘ μ¦μ μλ¦Όμ 보λ΄λ κΈ°λ₯μ ꡬνν΄ λ΄
λλ€! π
μ΄λ² μ±ν°λ₯Ό λ§μΉλ©΄ μ¬λ¬λΆμ λ€μμ ν μ μκ² λ κ±°μμ!
logger.add()μ 컀μ€ν
μ±ν¬ ν¨μλ₯Ό λ±λ‘νκ³ , level νν°λ₯Ό μ μ©νμ¬ μ¬κ°ν μ€λ₯λ§ SlackμΌλ‘ μ μ‘ν μ μμ΅λλ€.loguru-mastery ν΄λμ slack_alert.py νμΌμ μλ‘ μΆκ°νμ¬ μ€μ΅μ μ§νν©λλ€. μ΄ μ½λλ ERROR λ 벨μ λ‘κ·Έκ° λ°μνμ λ, μ€μ λ Slack μ±λλ‘ μλ¦Όμ 보λ
λλ€.
νλ‘μ νΈ κ΅¬μ‘°
loguru-mastery/
βββ venv/
βββ requirements.txt <- slack-sdk λΌμ΄λΈλ¬λ¦¬ μΆκ°
βββ slack_alert.py <- μλ‘ μΆκ°λ νμΌ!
requirements.txt μμ
loguru
slack-sdk
slack_alert.py μ 체 μ½λ (Slack SDK μ¬μ©, μ μ λ³Έ)
# slack_alert.py
import os
import sys
import traceback
from loguru import logger
from slack_sdk.webhook import WebhookClient # SDKμ Webhook ν΄λΌμ΄μΈνΈ μ¬μ©
# --- 1. Slack Webhook URL μ€μ ---
# π¨ μ λ μ½λμ URLμ μ§μ λ£μ§ λ§μΈμ! 보μμ λ§€μ° μνν©λλ€.
# λ°λμ νκ²½ λ³μλ₯Ό ν΅ν΄ URLμ λΆλ¬μ΅λλ€.
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
# --- 2. Loguru 컀μ€ν
μ±ν¬(Sink) ν¨μ μ μ ---
# ν¨μ μ±ν¬λ ν¬λ§·λ λ¬Έμμ΄μ΄ μλλΌ, loguruμ Message κ°μ²΄λ₯Ό μ λ¬λ°μ΅λλ€.
# message.recordμ level/name/exception λ± μΈλΆ μ λ³΄κ° λ€μ΄ μμ΅λλ€.
def slack_sink(message):
record = message.record
log_text = record["message"]
# μμΈκ° μμΌλ©΄ μ¬λμ΄ μ½κΈ° μ¬μ΄ νΈλ μ΄μ€λ°±μΌλ‘ λ³ν
exc = record["exception"]
if exc:
tb = "".join(traceback.format_exception(exc.type, exc.value, exc.traceback))
else:
tb = ""
payload = {
"text": "π¨ νλ‘λμ
μλ²μ μ€λ₯κ° λ°μνμ΅λλ€! π¨",
"attachments": [{
"color": "#FF0000",
"fields": [
{"title": "Level", "value": record["level"].name, "short": True},
{"title": "Module", "value": record["module"], "short": True},
{"title": "Message", "value": log_text or "-", "short": False},
{
"title": "Error Traceback",
"value": f"```{tb}```" if tb else "N/A",
"short": False
},
]
}]
}
try:
WebhookClient(SLACK_WEBHOOK_URL).send(**payload)
except Exception as e:
# μ¬κΈ°μ loggerλ₯Ό λ€μ νΈμΆνλ©΄ μ¬κ· μν -> stderrλ§ μ¬μ©
sys.stderr.write(f"[Slack sink] μ μ‘ μ€ν¨: {e}\\\\n")
# --- 3. Loguru λ‘κ±° μ€μ ---
logger.remove()
# μ±ν¬ 1: μ½μ μΆλ ₯ (λͺ¨λ DEBUG λ 벨 μ΄μμ λ‘κ·Έ)
logger.add(sys.stderr, level="DEBUG")
# μ±ν¬ 2: μ¬λ μλ¦Ό (ERROR λ 벨 μ΄μμ μ¬κ°ν λ‘κ·Έλ§!)
if SLACK_WEBHOOK_URL:
# ν¨μ μ±ν¬λ Message κ°μ²΄λ₯Ό λ°μΌλ―λ‘ format μ§μ λΆνμ
logger.add(
slack_sink, # μ°λ¦¬κ° λ§λ ν¨μλ₯Ό μ±ν¬λ‘ μ§μ !
level="ERROR", # ERROR λ 벨 μ΄μμΌ λλ§ μ΄ μ±ν¬κ° λμ
backtrace=True,
diagnose=False, # μ΄μ νκ²½μμ κ³Όλν λ‘컬 λ³μ λ
ΈμΆ λ°©μ§
enqueue=True # λΉλκΈ° νλ‘ μ μ‘(μ ν리μΌμ΄μ
νλ¦ μ§μ° μ΅μν)
)
else:
# νκ²½ λ³μκ° μ€μ λμ§ μμμ κ²½μ° κ²½κ³ λ©μμ§ μΆλ ₯
logger.warning("SLACK_WEBHOOK_URL νκ²½ λ³μκ° μμ΄ μ¬λ μλ¦Όμ΄ λΉνμ±νλμμ΅λλ€.")
# --- 4. λ‘μ§ μ€ν λ° ν
μ€νΈ ---
def critical_task():
logger.info("μΌμΌ μ¬μ©μ λ°μ΄ν° μ§κ³ μμ
μ μμν©λλ€.")
logger.debug("λ°μ΄ν°λ² μ΄μ€μμ μ¬μ©μ λͺ©λ‘μ κ°μ Έμ΅λλ€...")
try:
# μλμ μΌλ‘ μλ¬ λ°μ!
result = 10 / 0
except ZeroDivisionError:
# logger.exception()μ μμΈ μ 보λ₯Ό μλμΌλ‘ λ‘κ·Έμ ν¬ν¨μν΅λλ€.
# μ΄ λ‘κ·Έλ ERROR λ 벨μ΄λ―λ‘, μ½μκ³Ό μ¬λ μμͺ½μ λͺ¨λ κΈ°λ‘λ©λλ€.
logger.exception("λ°μ΄ν° μ§κ³ μ€ μΉλͺ
μ μΈ μ€λ₯κ° λ°μνμ¬ μμ
μ μ€λ¨ν©λλ€.")
if __name__ == "__main__":
critical_task()