์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ Python ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ํšจ๊ณผ์ ์ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ

๐Ÿ Python ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ํšจ๊ณผ์ ์ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ

๋ฒ„๊ทธ์™€ ์‹ธ์šฐ๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ โœจ
def debug_code(): logger.info("Start") try: process() except Exception: logger.error("Bug!") ๋กœ๊น… ์‹œ์Šคํ…œ ๐Ÿ“ ๐Ÿ” ๐Ÿ› ๋กœ๊ทธ ๊ธฐ๋ก ์ถ”์  ๋””๋ฒ„๊น… โœจ
์•ˆ๋…•! ๐Ÿ‘‹ ์˜ค๋Š˜์€ Python ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ํ•œ ๋ฒˆ์ฏค ๊ณ ๋ฏผํ•˜๋Š” ์ฃผ์ œ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊นŒ ํ•ด. ๋ฐ”๋กœ ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ์ด์•ผ. ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋‹ค ๋ณด๋ฉด "์–ด? ์ด๊ฒŒ ์™œ ์•ˆ ๋ผ?" ํ•˜๋ฉด์„œ ๋จธ๋ฆฌ๋ฅผ ์ฅ์–ด๋œฏ์€ ๊ฒฝํ—˜, ๋‹ค๋“ค ์žˆ์ง€? ๐Ÿ˜…

์‚ฌ์‹ค ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ๋ฒ„๊ทธ๋Š” ํ”ผํ•  ์ˆ˜ ์—†๋Š” ์นœ๊ตฌ์•ผ. ์•„๋‹ˆ, ์นœ๊ตฌ๋ผ๊ธฐ๋ณด๋‹จ ๊ท€์ฐฎ์€ ๋™๊ฑฐ์ธ? ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ด ๋…€์„๋“ค์„ ํšจ๊ณผ์ ์œผ๋กœ ์ฐพ์•„๋‚ด๊ณ  ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ์Šคํ…œ์ด ํ•„์š”ํ•ด. ๋งˆ์น˜ ํƒ์ •์ด ๋ฒ”์ธ์„ ์ฐพ๋“ฏ์ด ๋ง์ด์•ผ! ๐Ÿ•ต๏ธ

์ด ๊ธ€์—์„œ๋Š” Python์˜ ๋กœ๊น… ์‹œ์Šคํ…œ๋ถ€ํ„ฐ ๋‹ค์–‘ํ•œ ๋””๋ฒ„๊น… ๋„๊ตฌ, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•๊นŒ์ง€ ๋ชจ๋‘ ๋‹ค๋ค„๋ณผ ๊ฑฐ์•ผ. ์žฌ๋Šฅ๋„ท์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šฐ๊ณ  ์žˆ๋‹ค๋ฉด, ์ด ๋‚ด์šฉ์ด ์ •๋ง ์œ ์šฉํ•  ๊ฑฐ๋ผ๊ณ  ์žฅ๋‹ดํ•ด! ๐Ÿ’ช

๐ŸŽฏ ์™œ ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น…์ด ์ค‘์š”ํ• ๊นŒ?

๊ฐœ๋ฐœ์ž์˜ ํ˜„์‹ค ์ฒดํฌ โœ…

์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์‹œ๊ฐ„๋ณด๋‹ค ๋””๋ฒ„๊น…ํ•˜๋Š” ์‹œ๊ฐ„์ด ๋” ๊ธธ๋‹ค๋Š” ํ†ต๊ณ„๊ฐ€ ์žˆ์–ด. ์‹ค์ œ๋กœ ๊ฐœ๋ฐœ ์‹œ๊ฐ„์˜ ์•ฝ 50~70%๊ฐ€ ๋ฒ„๊ทธ๋ฅผ ์ฐพ๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๋ฐ ์“ฐ์ธ๋‹ค๊ณ  ํ•ด. ๋†€๋ž์ง€? ๐Ÿ˜ฑ

๊ทธ๋ž˜์„œ ํšจ๊ณผ์ ์ธ ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฑด ๋‹จ์ˆœํžˆ '์žˆ์œผ๋ฉด ์ข‹์€ ๊ฒƒ'์ด ์•„๋‹ˆ๋ผ '๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ฒƒ'์ด์•ผ. ๋งˆ์น˜ ์š”๋ฆฌํ•  ๋•Œ ์ข‹์€ ์นผ์ด ํ•„์š”ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์ง€!
๐Ÿ”ฅ ๋กœ๊น…์˜ ํ•ต์‹ฌ ๊ฐ€์น˜ 1. ๋ฌธ์ œ ์ถ”์ ์˜ ์‹œ์ž‘์ 
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๋ฉด, ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ "์–ธ์ œ, ์–ด๋””์„œ, ๋ฌด์—‡์ด" ์ž˜๋ชป๋๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด.

2. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์˜ ๋ˆˆ
๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ดœ์ฐฎ์•˜๋Š”๋ฐ ์‹ค์ œ ์„œ๋น„์Šค์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ๋กœ๊ทธ๊ฐ€ ์—†๋‹ค๋ฉด? ์™„์ „ ์•”ํ‘ ์†์—์„œ ํ—ค๋งค๋Š” ๊ฑฐ์ง€. ๐ŸŒ‘

3. ์„ฑ๋Šฅ ๋ถ„์„์˜ ๊ธฐ์ดˆ
์–ด๋–ค ํ•จ์ˆ˜๊ฐ€ ๋А๋ฆฐ์ง€, ์–ด๋””์„œ ๋ณ‘๋ชฉ์ด ๋ฐœ์ƒํ•˜๋Š”์ง€ ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์–ด.

4. ๋ณด์•ˆ ๊ฐ์‚ฌ
๋ˆ„๊ฐ€, ์–ธ์ œ, ๋ฌด์—‡์„ ํ–ˆ๋Š”์ง€ ๊ธฐ๋กํ•˜๋ฉด ๋ณด์•ˆ ์‚ฌ๊ณ  ๋ฐœ์ƒ ์‹œ ์ถ”์ ์ด ๊ฐ€๋Šฅํ•ด.

๐Ÿ“š Python ๋กœ๊น…์˜ ๊ธฐ์ดˆ: logging ๋ชจ๋“ˆ ์™„์ „ ์ •๋ณต

Python์˜ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ logging ๋ชจ๋“ˆ์€ ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์•ผ. ๋งŽ์€ ์ดˆ๋ณด ๊ฐœ๋ฐœ์ž๋“ค์ด print() ๋ฌธ์œผ๋กœ ๋””๋ฒ„๊น…ํ•˜๋Š”๋ฐ, ์ด๊ฑด ๋งˆ์น˜ ์Šค๋งˆํŠธํฐ ์‹œ๋Œ€์— ์‚์‚๋ฅผ ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์•„! ๐Ÿ˜„

๐ŸŒŸ ๋กœ๊น… ๋ ˆ๋ฒจ์˜ ์ดํ•ด

๐Ÿ’ก ๋กœ๊น… ๋ ˆ๋ฒจ 5๋‹จ๊ณ„ Python์˜ logging ๋ชจ๋“ˆ์€ 5๊ฐ€์ง€ ๋ ˆ๋ฒจ์„ ์ œ๊ณตํ•ด. ์ˆซ์ž๊ฐ€ ํด์ˆ˜๋ก ์‹ฌ๊ฐ๋„๊ฐ€ ๋†’์•„:

DEBUG (10) โ†’ ์ƒ์„ธํ•œ ์ •๋ณด, ์ฃผ๋กœ ๋ฌธ์ œ ์ง„๋‹จ ์‹œ
INFO (20) โ†’ ์ผ๋ฐ˜์ ์ธ ์ •๋ณด์„ฑ ๋ฉ”์‹œ์ง€
WARNING (30) โ†’ ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€, ์•„์ง ๋ฌธ์ œ๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ฃผ์˜ ํ•„์š”
ERROR (40) โ†’ ์—๋Ÿฌ ๋ฐœ์ƒ, ๊ธฐ๋Šฅ ์ผ๋ถ€๊ฐ€ ์ž‘๋™ ์•ˆ ํ•จ
CRITICAL (50) โ†’ ์‹ฌ๊ฐํ•œ ์—๋Ÿฌ, ํ”„๋กœ๊ทธ๋žจ ์ „์ฒด๊ฐ€ ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ์Œ

๐ŸŽจ ๊ธฐ๋ณธ ๋กœ๊น… ์„ค์ •ํ•˜๊ธฐ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋กœ๊น…๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ์ด๊ฑด ์ •๋ง ๊ธฐ์ดˆ ์ค‘์˜ ๊ธฐ์ดˆ์•ผ:
import logging

# ๊ธฐ๋ณธ ์„ค์ •
logging.basicConfig(level=logging.DEBUG)

# ๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ
logging.debug("์ด๊ฑด ๋””๋ฒ„๊ทธ ๋ฉ”์‹œ์ง€์•ผ")
logging.info("์ •๋ณด์„ฑ ๋ฉ”์‹œ์ง€")
logging.warning("๊ฒฝ๊ณ ! ๋ญ”๊ฐ€ ์ด์ƒํ•ด")
logging.error("์—๋Ÿฌ ๋ฐœ์ƒ!")
logging.critical("์‹ฌ๊ฐํ•œ ๋ฌธ์ œ!")
ํ•˜์ง€๋งŒ ์ด๊ฑด ๋„ˆ๋ฌด ๋‹จ์ˆœํ•ด. ์‹ค์ „์—์„œ๋Š” ํ›จ์”ฌ ๋” ์ •๊ตํ•œ ์„ค์ •์ด ํ•„์š”ํ•˜์ง€. ๐ŸŽฏ

๐Ÿ”ง ์‹ค์ „ ๋กœ๊น… ์„ค์ •

ํ”„๋กœํŽ˜์…”๋„ํ•œ ๋กœ๊น… ์„ค์ • ์˜ˆ์ œ

์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ œ๋Œ€๋กœ ๋œ ๋กœ๊น… ์„ค์ •์„ ๋ณด์—ฌ์ค„๊ฒŒ:
import logging
import logging.handlers
from datetime import datetime
import os

def setup_logger(name, log_file, level=logging.INFO):
    """๋กœ๊ฑฐ๋ฅผ ์„ค์ •ํ•˜๋Š” ํ•จ์ˆ˜"""
    
    # ๋กœ๊ทธ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ
    log_dir = os.path.dirname(log_file)
    if log_dir and not os.path.exists(log_dir):
        os.makedirs(log_dir)
    
    # ํฌ๋งคํ„ฐ ์„ค์ • - ๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ํ˜•์‹
    formatter = logging.Formatter(
        '%(asctime)s | %(name)s | %(levelname)s | %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ - ๋กœ๊ทธ๋ฅผ ํŒŒ์ผ์— ์ €์žฅ
    file_handler = logging.handlers.RotatingFileHandler(
        log_file,
        maxBytes=10*1024*1024,  # 10MB
        backupCount=5,  # ์ตœ๋Œ€ 5๊ฐœ ๋ฐฑ์—… ํŒŒ์ผ
        encoding='utf-8'
    )
    file_handler.setFormatter(formatter)
    
    # ์ฝ˜์†” ํ•ธ๋“ค๋Ÿฌ - ํ„ฐ๋ฏธ๋„์—๋„ ์ถœ๋ ฅ
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    
    # ๋กœ๊ฑฐ ์ƒ์„ฑ ๋ฐ ์„ค์ •
    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger

# ์‚ฌ์šฉ ์˜ˆ์ œ
app_logger = setup_logger('MyApp', 'logs/app.log')
app_logger.info("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘!")
์ด ์ฝ”๋“œ์˜ ํ•ต์‹ฌ ํฌ์ธํŠธ๋ฅผ ์„ค๋ช…ํ•ด์ค„๊ฒŒ! ๐ŸŽ“

๐Ÿ” ์ฝ”๋“œ ๋ถ„์„ 1. RotatingFileHandler
๋กœ๊ทธ ํŒŒ์ผ์ด ๋„ˆ๋ฌด ์ปค์ง€๋Š” ๊ฑธ ๋ฐฉ์ง€ํ•ด. 10MB๊ฐ€ ๋˜๋ฉด ์ž๋™์œผ๋กœ ์ƒˆ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ , ์ตœ๋Œ€ 5๊ฐœ๊นŒ์ง€ ๋ฐฑ์—…์„ ์œ ์ง€ํ•ด. ์˜ค๋ž˜๋œ ํŒŒ์ผ์€ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜์ง€.

2. ํฌ๋งคํ„ฐ(Formatter)
๋กœ๊ทธ ๋ฉ”์‹œ์ง€์˜ ํ˜•์‹์„ ์ •์˜ํ•ด. ์‹œ๊ฐ„, ๋กœ๊ฑฐ ์ด๋ฆ„, ๋ ˆ๋ฒจ, ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๊ธฐ ์ข‹๊ฒŒ ์ •๋ฆฌํ•ด์ฃผ์ง€.

3. ๋‹ค์ค‘ ํ•ธ๋“ค๋Ÿฌ
ํŒŒ์ผ๊ณผ ์ฝ˜์†”์— ๋™์‹œ์— ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•ด. ๊ฐœ๋ฐœํ•  ๋•Œ๋Š” ์ฝ˜์†”๋กœ ๋ฐ”๋กœ ํ™•์ธํ•˜๊ณ , ๋‚˜์ค‘์— ๋ถ„์„ํ•  ๋•Œ๋Š” ํŒŒ์ผ์„ ๋ณด๋ฉด ๋ผ!

๐ŸŽญ ์ปจํ…์ŠคํŠธ ์ •๋ณด ์ถ”๊ฐ€ํ•˜๊ธฐ

๋กœ๊ทธ์— ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ๋‹ด์œผ๋ฉด ๋””๋ฒ„๊น…์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž ID, ์š”์ฒญ ID, ํ•จ์ˆ˜ ์ด๋ฆ„ ๊ฐ™์€ ๊ฑฐ ๋ง์ด์•ผ:
import logging
from functools import wraps

def log_function_call(logger):
    """ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์ž๋™์œผ๋กœ ๋กœ๊น…ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logger.info(f"ํ•จ์ˆ˜ ํ˜ธ์ถœ: {func.__name__}")
            logger.debug(f"์ธ์ž: args={args}, kwargs={kwargs}")
            
            try:
                result = func(*args, **kwargs)
                logger.info(f"ํ•จ์ˆ˜ ์™„๋ฃŒ: {func.__name__}")
                return result
            except Exception as e:
                logger.error(f"ํ•จ์ˆ˜ ์—๋Ÿฌ: {func.__name__} - {str(e)}")
                raise
        
        return wrapper
    return decorator

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)

@log_function_call(logger)
def calculate_sum(a, b):
    return a + b

result = calculate_sum(5, 3)
์ด๋Ÿฐ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋กœ๊ทธ๊ฐ€ ๋‚จ์•„. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜Ž

๐Ÿ› ๋””๋ฒ„๊น… ๋„๊ตฌ ์™„์ „ ์ •๋ณต

๋กœ๊น…์ด ์‚ฌํ›„ ๋ถ„์„์„ ์œ„ํ•œ ๊ฑฐ๋ผ๋ฉด, ๋””๋ฒ„๊น…์€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฌธ์ œ๋ฅผ ์ฐพ๋Š” ๊ฑฐ์•ผ. Python์—๋Š” ์ •๋ง ๋‹ค์–‘ํ•œ ๋””๋ฒ„๊น… ๋„๊ตฌ๊ฐ€ ์žˆ์–ด!

๐Ÿ”ฌ pdb: Python ๋‚ด์žฅ ๋””๋ฒ„๊ฑฐ

pdb์˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

pdb๋Š” Python์— ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋œ ๋””๋ฒ„๊ฑฐ์•ผ. ๋ณ„๋„ ์„ค์น˜ ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด:
import pdb

def buggy_function(x, y):
    result = x + y
    pdb.set_trace()  # ์—ฌ๊ธฐ์„œ ์‹คํ–‰์ด ๋ฉˆ์ถค!
    result = result * 2
    return result

# ์‹คํ–‰ํ•˜๋ฉด ๋Œ€ํ™”ํ˜• ๋””๋ฒ„๊น… ๋ชจ๋“œ๋กœ ์ง„์ž…
buggy_function(5, 3)
Python 3.7๋ถ€ํ„ฐ๋Š” ๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•๋„ ์žˆ์–ด:
def buggy_function(x, y):
    result = x + y
    breakpoint()  # ์ด๊ฒŒ ๋” ํŽธํ•ด!
    result = result * 2
    return result
๐Ÿ’ก pdb ์ฃผ์š” ๋ช…๋ น์–ด ๋””๋ฒ„๊น… ๋ชจ๋“œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ช…๋ น์–ด๋“ค์ด์•ผ:

n (next) - ๋‹ค์Œ ์ค„๋กœ ์ด๋™
s (step) - ํ•จ์ˆ˜ ์•ˆ์œผ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ
c (continue) - ๋‹ค์Œ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๊นŒ์ง€ ์‹คํ–‰
l (list) - ํ˜„์žฌ ์œ„์น˜ ์ฃผ๋ณ€ ์ฝ”๋“œ ๋ณด๊ธฐ
p ๋ณ€์ˆ˜๋ช… - ๋ณ€์ˆ˜ ๊ฐ’ ์ถœ๋ ฅ
pp ๋ณ€์ˆ˜๋ช… - ๋ณ€์ˆ˜ ๊ฐ’ ์˜ˆ์˜๊ฒŒ ์ถœ๋ ฅ
w (where) - ํ˜„์žฌ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๋ณด๊ธฐ
q (quit) - ๋””๋ฒ„๊ฑฐ ์ข…๋ฃŒ

๐ŸŽจ ipdb: ๋” ์˜ˆ์œ ๋””๋ฒ„๊ฑฐ

pdb๊ฐ€ ์ข€ ํˆฌ๋ฐ•ํ•˜๋‹ค๋ฉด, ipdb๋ฅผ ์จ๋ด. IPython ๊ธฐ๋ฐ˜์ด๋ผ ํ›จ์”ฌ ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•ด:
# ์„ค์น˜
pip install ipdb

# ์‚ฌ์šฉ
import ipdb

def my_function():
    x = 10
    ipdb.set_trace()  # ์ปฌ๋Ÿฌํ’€ํ•œ ๋””๋ฒ„๊น… ์‹œ์ž‘!
    y = x * 2
    return y
ipdb๋Š” ์ž๋™์™„์„ฑ, ๋ฌธ๋ฒ• ํ•˜์ด๋ผ์ดํŒ…, ๋” ๋‚˜์€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋“ฑ์„ ์ œ๊ณตํ•ด. ํ•œ๋ฒˆ ์จ๋ณด๋ฉด pdb๋กœ ๋Œ์•„๊ฐ€๊ธฐ ํž˜๋“ค์–ด! ๐Ÿ˜„

๐Ÿš€ VS Code ๋””๋ฒ„๊ฑฐ: ๋น„์ฃผ์–ผ์˜ ํž˜

VS Code ๋””๋ฒ„๊น… ์„ค์ •

VS Code๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋””๋ฒ„๊น… ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด. GUI๋กœ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•˜๊ณ , ๋ณ€์ˆ˜๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์ง€.

launch.json ์„ค์ • ์˜ˆ์ œ:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": false,
            "env": {
                "PYTHONPATH": "${workspaceFolder}"
            }
        },
        {
            "name": "Python: Django",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/manage.py",
            "args": [
                "runserver",
                "--noreload"
            ],
            "django": true
        },
        {
            "name": "Python: Flask",
            "type": "python",
            "request": "launch",
            "module": "flask",
            "env": {
                "FLASK_APP": "app.py",
                "FLASK_ENV": "development"
            },
            "args": [
                "run",
                "--no-debugger",
                "--no-reload"
            ]
        }
    ]
}
VS Code์—์„œ ๋””๋ฒ„๊น…ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ •๋ง ์ง๊ด€์ ์ด์•ผ:

1๏ธโƒฃ ์ฝ”๋“œ ์™ผ์ชฝ ์—ฌ๋ฐฑ์„ ํด๋ฆญํ•ด์„œ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ ์„ค์ •
2๏ธโƒฃ F5 ํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ๋””๋ฒ„๊น… ์‹œ์ž‘
3๏ธโƒฃ ๋ณ€์ˆ˜ ํŒจ๋„์—์„œ ๋ชจ๋“  ๋ณ€์ˆ˜ ๊ฐ’ ํ™•์ธ
4๏ธโƒฃ ์กฐ์‚ฌ์‹(Watch)์— ํ‘œํ˜„์‹ ์ถ”๊ฐ€ํ•ด์„œ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง
5๏ธโƒฃ ์ฝœ ์Šคํƒ์œผ๋กœ ํ•จ์ˆ˜ ํ˜ธ์ถœ ํ๋ฆ„ ํŒŒ์•…

๐Ÿ” PyCharm ๋””๋ฒ„๊ฑฐ: ํ”„๋กœํŽ˜์…”๋„์˜ ์„ ํƒ

PyCharm์€ ์œ ๋ฃŒ์ง€๋งŒ ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋””๋ฒ„๊น… ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด. ํŠนํžˆ ๋ณต์žกํ•œ ํ”„๋กœ์ ํŠธ์—์„œ ๋น›์„ ๋ฐœํ•˜์ง€:
PyCharm ๋””๋ฒ„๊น… ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ 1. ์กฐ๊ฑด๋ถ€ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ
ํŠน์ • ์กฐ๊ฑด์ผ ๋•Œ๋งŒ ๋ฉˆ์ถ”๊ฒŒ ํ•  ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด "x > 100์ผ ๋•Œ๋งŒ ๋ฉˆ์ถฐ!"

2. ์˜ˆ์™ธ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ
ํŠน์ • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ์ž๋™์œผ๋กœ ๋ฉˆ์ถฐ. ์˜ˆ์™ธ ์ถ”์ ์ด ์ •๋ง ์‰ฌ์›Œ์ ธ.

3. ์›๊ฒฉ ๋””๋ฒ„๊น…
์„œ๋ฒ„์—์„œ ์‹คํ–‰ ์ค‘์ธ ์ฝ”๋“œ๋ฅผ ๋กœ์ปฌ์—์„œ ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์–ด!

4. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ๋””๋ฒ„๊น…
์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋ฅผ ๋™์‹œ์— ๋””๋ฒ„๊น…ํ•˜๊ณ  ์Šค๋ ˆ๋“œ ๊ฐ„ ์ „ํ™˜๋„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์–ด.

๐ŸŽฏ ์‹ค์ „ ๋กœ๊น… ์ „๋žต

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ „์—์„œ ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊นŒ? ๐Ÿš€

๐Ÿ“Š ๊ตฌ์กฐํ™”๋œ ๋กœ๊น… (Structured Logging)

์ผ๋ฐ˜ ํ…์ŠคํŠธ ๋กœ๊ทธ๋„ ์ข‹์ง€๋งŒ, JSON ํ˜•์‹์œผ๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋ฉด ๋‚˜์ค‘์— ๋ถ„์„ํ•˜๊ธฐ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ:
import logging
import json
from datetime import datetime

class JsonFormatter(logging.Formatter):
    """JSON ํ˜•์‹์œผ๋กœ ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ํฌ๋งคํ„ฐ"""
    
    def format(self, record):
        log_data = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno
        }
        
        # ์ถ”๊ฐ€ ์ปจํ…์ŠคํŠธ ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ํฌํ•จ
        if hasattr(record, 'user_id'):
            log_data['user_id'] = record.user_id
        if hasattr(record, 'request_id'):
            log_data['request_id'] = record.request_id
        
        # ์˜ˆ์™ธ ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ํฌํ•จ
        if record.exc_info:
            log_data['exception'] = self.formatException(record.exc_info)
        
        return json.dumps(log_data, ensure_ascii=False)

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# ์ปจํ…์ŠคํŠธ ์ •๋ณด์™€ ํ•จ๊ป˜ ๋กœ๊น…
logger.info("์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ", extra={'user_id': 12345, 'request_id': 'abc-123'})
๐Ÿ’ก ๊ตฌ์กฐํ™”๋œ ๋กœ๊น…์˜ ์žฅ์  JSON ๋กœ๊ทธ๋Š” Elasticsearch, Splunk ๊ฐ™์€ ๋กœ๊ทธ ๋ถ„์„ ๋„๊ตฌ์™€ ์™„๋ฒฝํ•˜๊ฒŒ ํ˜ธํ™˜๋ผ. ๋‚˜์ค‘์— "์ง€๋‚œ ์ฃผ์— ์—๋Ÿฌ๊ฐ€ ๋ช‡ ๋ฒˆ ๋ฐœ์ƒํ–ˆ์ง€?" ๊ฐ™์€ ์งˆ๋ฌธ์— ์‰ฝ๊ฒŒ ๋‹ตํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ“ˆ

๐ŸŽญ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ํ™œ์šฉํ•œ ๋กœ๊น…

ํŠน์ • ์ž‘์—…์˜ ์‹œ์ž‘๊ณผ ๋์„ ์ž๋™์œผ๋กœ ๋กœ๊น…ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•ด๋ด:
import logging
import time
from contextlib import contextmanager

@contextmanager
def log_execution_time(logger, operation_name):
    """์ž‘์—… ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ž๋™์œผ๋กœ ๋กœ๊น…"""
    start_time = time.time()
    logger.info(f"{operation_name} ์‹œ์ž‘")
    
    try:
        yield
    except Exception as e:
        logger.error(f"{operation_name} ์‹คํŒจ: {str(e)}")
        raise
    finally:
        elapsed_time = time.time() - start_time
        logger.info(f"{operation_name} ์™„๋ฃŒ (์†Œ์š”์‹œ๊ฐ„: {elapsed_time:.2f}์ดˆ)")

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)

with log_execution_time(logger, "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ"):
    # ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…
    time.sleep(2)
    result = "๋ฐ์ดํ„ฐ ์กฐํšŒ ์™„๋ฃŒ"
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋Š”์ง€ ์ž๋™์œผ๋กœ ์ธก์ •๋˜๊ณ  ๋กœ๊ทธ์— ๋‚จ์•„. ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•  ๋•Œ ์ •๋ง ์œ ์šฉํ•ด! โšก

๐Ÿ” ๋ฏผ๊ฐํ•œ ์ •๋ณด ํ•„ํ„ฐ๋ง

โš ๏ธ ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ

๋กœ๊ทธ์— ๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ‚ค, ๊ฐœ์ธ์ •๋ณด ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ๋‚จ์œผ๋ฉด ํฐ์ผ์ด์•ผ! ์ž๋™์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž:
import logging
import re

class SensitiveDataFilter(logging.Filter):
    """๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๋งˆ์Šคํ‚นํ•˜๋Š” ํ•„ํ„ฐ"""
    
    PATTERNS = [
        (re.compile(r'password["\']?\s*[:=]\s*["\']?([^"\'}\s,]+)', re.I), 'password=***'),
        (re.compile(r'api[_-]?key["\']?\s*[:=]\s*["\']?([^"\'}\s,]+)', re.I), 'api_key=***'),
        (re.compile(r'\b\d{3}-\d{4}-\d{4}\b'), '***-****-****'),  # ์ „ํ™”๋ฒˆํ˜ธ
        (re.compile(r'\b\d{6}-\d{7}\b'), '******-*******'),  # ์ฃผ๋ฏผ๋ฒˆํ˜ธ
    ]
    
    def filter(self, record):
        message = record.getMessage()
        
        for pattern, replacement in self.PATTERNS:
            message = pattern.sub(replacement, message)
        
        record.msg = message
        record.args = ()
        
        return True

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)
logger.addFilter(SensitiveDataFilter())

# ์ด์ œ ์•ˆ์ „ํ•˜๊ฒŒ ๋กœ๊น… ๊ฐ€๋Šฅ
logger.info("์‚ฌ์šฉ์ž ์ •๋ณด: password=secret123, api_key=abc123xyz")
# ์‹ค์ œ ๋กœ๊ทธ: "์‚ฌ์šฉ์ž ์ •๋ณด: password=***, api_key=***"

๐Ÿ› ๏ธ ๊ณ ๊ธ‰ ๋””๋ฒ„๊น… ํ…Œํฌ๋‹‰

๊ธฐ๋ณธ์ ์ธ ๋””๋ฒ„๊น… ๋ฐฉ๋ฒ•์€ ์ด์ œ ์•Œ์•˜์œผ๋‹ˆ, ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์„ ๋ฐฐ์›Œ๋ณผ๊นŒ? ๐ŸŽ“

๐ŸŽฏ ์›๊ฒฉ ๋””๋ฒ„๊น… (Remote Debugging)

์„œ๋ฒ„์—์„œ ์‹คํ–‰ ์ค‘์ธ Python ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋กœ์ปฌ์—์„œ ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์–ด. ์ด๊ฑด ์ •๋ง ๋งˆ๋ฒ• ๊ฐ™์€ ๊ธฐ๋Šฅ์ด์•ผ! โœจ
# ์„œ๋ฒ„ ์ธก ์ฝ”๋“œ
import debugpy

# ๋””๋ฒ„๊ทธ ์„œ๋ฒ„ ์‹œ์ž‘
debugpy.listen(("0.0.0.0", 5678))
print("๋””๋ฒ„๊ฑฐ ๋Œ€๊ธฐ ์ค‘...")
debugpy.wait_for_client()  # ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ๋Œ€๊ธฐ

# ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ ์ผ๋ฐ˜ ์ฝ”๋“œ
def main():
    print("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘")
    # ์ž‘์—… ์ˆ˜ํ–‰

if __name__ == "__main__":
    main()
VS Code์—์„œ ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด launch.json์— ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•ด:
{
    "name": "Python: Remote Attach",
    "type": "python",
    "request": "attach",
    "connect": {
        "host": "์„œ๋ฒ„IP์ฃผ์†Œ",
        "port": 5678
    },
    "pathMappings": [
        {
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/app"
        }
    ]
}

๐Ÿ”ฌ ๋ฉ”๋ชจ๋ฆฌ ํ”„๋กœํŒŒ์ผ๋ง

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ฐพ๊ธฐ

Python์€ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์ด ์žˆ์ง€๋งŒ, ์ˆœํ™˜ ์ฐธ์กฐ๋‚˜ ์ž˜๋ชป๋œ ์ฝ”๋“œ๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. memory_profiler๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด:
# ์„ค์น˜
pip install memory_profiler

# ์‚ฌ์šฉ
from memory_profiler import profile

@profile
def memory_intensive_function():
    # ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ์ž‘์—…
    big_list = [i for i in range(1000000)]
    big_dict = {i: i**2 for i in range(100000)}
    return len(big_list) + len(big_dict)

# ์‹คํ–‰
result = memory_intensive_function()
์‹คํ–‰ํ•˜๋ฉด ๊ฐ ์ค„๋งˆ๋‹ค ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ํ‘œ์‹œ๋ผ:
Line #    Mem usage    Increment   Line Contents
================================================
     3   38.816 MiB   38.816 MiB   @profile
     4                             def memory_intensive_function():
     5   45.289 MiB    6.473 MiB       big_list = [i for i in range(1000000)]
     6   53.125 MiB    7.836 MiB       big_dict = {i: i**2 for i in range(100000)}
     7   53.125 MiB    0.000 MiB       return len(big_list) + len(big_dict)

โšก ์„ฑ๋Šฅ ํ”„๋กœํŒŒ์ผ๋ง

์–ด๋–ค ํ•จ์ˆ˜๊ฐ€ ๋А๋ฆฐ์ง€ ์ฐพ๊ณ  ์‹ถ๋‹ค๋ฉด? cProfile์„ ์‚ฌ์šฉํ•ด๋ด:
import cProfile
import pstats
from pstats import SortKey

def slow_function():
    total = 0
    for i in range(1000000):
        total += i
    return total

def fast_function():
    return sum(range(1000000))

# ํ”„๋กœํŒŒ์ผ๋ง ์‹คํ–‰
profiler = cProfile.Profile()
profiler.enable()

slow_function()
fast_function()

profiler.disable()

# ๊ฒฐ๊ณผ ๋ถ„์„
stats = pstats.Stats(profiler)
stats.sort_stats(SortKey.TIME)
stats.print_stats(10)  # ์ƒ์œ„ 10๊ฐœ ํ•จ์ˆ˜ ์ถœ๋ ฅ
๋” ์‹œ๊ฐ์ ์ธ ๋ถ„์„์„ ์›ํ•œ๋‹ค๋ฉด snakeviz๋ฅผ ์‚ฌ์šฉํ•ด:
pip install snakeviz

# ํ”„๋กœํŒŒ์ผ ๋ฐ์ดํ„ฐ ์ €์žฅ
python -m cProfile -o output.prof your_script.py

# ์‹œ๊ฐํ™”
snakeviz output.prof
๋ธŒ๋ผ์šฐ์ €์—์„œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•œ ๊ทธ๋ž˜ํ”„๋กœ ์„ฑ๋Šฅ์„ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽจ

๐Ÿ—๏ธ ํ”„๋กœ์ ํŠธ๋ณ„ ๋กœ๊น… ์ „๋žต

ํ”„๋กœ์ ํŠธ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋กœ๊น… ์ „๋žต๋„ ๋‹ฌ๋ผ์ ธ์•ผ ํ•ด. ๊ฐ ์ƒํ™ฉ์— ๋งž๋Š” ์ตœ์ ์˜ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! ๐ŸŽฏ

๐ŸŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (Flask/Django)

Flask ๋กœ๊น… ์„ค์ •

Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์š”์ฒญ๋งˆ๋‹ค ๊ณ ์œ ํ•œ ID๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , ๋ชจ๋“  ๋กœ๊ทธ์— ์ด ID๋ฅผ ํฌํ•จ์‹œํ‚ค๋Š” ๊ฒŒ ์ข‹์•„:
from flask import Flask, request, g
import logging
import uuid

app = Flask(__name__)

# ๋กœ๊ฑฐ ์„ค์ •
logger = logging.getLogger(__name__)

@app.before_request
def before_request():
    """๊ฐ ์š”์ฒญ๋งˆ๋‹ค ๊ณ ์œ  ID ์ƒ์„ฑ"""
    g.request_id = str(uuid.uuid4())
    logger.info(f"[{g.request_id}] ์š”์ฒญ ์‹œ์ž‘: {request.method} {request.path}")

@app.after_request
def after_request(response):
    """์š”์ฒญ ์™„๋ฃŒ ๋กœ๊น…"""
    logger.info(f"[{g.request_id}] ์‘๋‹ต: {response.status_code}")
    return response

@app.errorhandler(Exception)
def handle_exception(e):
    """์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ๋กœ๊น…"""
    logger.error(f"[{g.request_id}] ์—๋Ÿฌ ๋ฐœ์ƒ: {str(e)}", exc_info=True)
    return {"error": "Internal Server Error"}, 500

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    logger.info(f"[{g.request_id}] ์‚ฌ์šฉ์ž ์กฐํšŒ: {user_id}")
    # ์‹ค์ œ ๋กœ์ง
    return {"user_id": user_id, "name": "ํ™๊ธธ๋™"}
Django ๋กœ๊น… ์„ค์ •

Django๋Š” settings.py์—์„œ ๋กœ๊น…์„ ์„ค์ •ํ•ด:
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'logs/django.log',
            'maxBytes': 1024*1024*15,  # 15MB
            'backupCount': 10,
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': False,
        },
        'myapp': {
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}

๐Ÿค– ๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ / ๋ฐฐ์น˜ ์ž‘์—…

๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ž‘์—…์—์„œ๋Š” ์ง„ํ–‰ ์ƒํ™ฉ๊ณผ ํ†ต๊ณ„๋ฅผ ๋กœ๊น…ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด:
import logging
from datetime import datetime
import time

class DataPipelineLogger:
    """๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ ์ „์šฉ ๋กœ๊ฑฐ"""
    
    def __init__(self, name):
        self.logger = logging.getLogger(name)
        self.stats = {
            'processed': 0,
            'errors': 0,
            'start_time': None
        }
    
    def start_pipeline(self, total_items):
        """ํŒŒ์ดํ”„๋ผ์ธ ์‹œ์ž‘"""
        self.stats['start_time'] = time.time()
        self.stats['total'] = total_items
        self.logger.info(f"ํŒŒ์ดํ”„๋ผ์ธ ์‹œ์ž‘: ์ด {total_items}๊ฐœ ํ•ญ๋ชฉ")
    
    def log_progress(self, current):
        """์ง„ํ–‰ ์ƒํ™ฉ ๋กœ๊น…"""
        self.stats['processed'] = current
        progress = (current / self.stats['total']) * 100
        elapsed = time.time() - self.stats['start_time']
        rate = current / elapsed if elapsed > 0 else 0
        
        self.logger.info(
            f"์ง„ํ–‰: {current}/{self.stats['total']} ({progress:.1f}%) "
            f"| ์†๋„: {rate:.1f}๊ฐœ/์ดˆ | ์—๋Ÿฌ: {self.stats['errors']}๊ฐœ"
        )
    
    def log_error(self, item_id, error):
        """์—๋Ÿฌ ๋กœ๊น…"""
        self.stats['errors'] += 1
        self.logger.error(f"ํ•ญ๋ชฉ {item_id} ์ฒ˜๋ฆฌ ์‹คํŒจ: {str(error)}")
    
    def finish_pipeline(self):
        """ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฃŒ"""
        elapsed = time.time() - self.stats['start_time']
        success_rate = ((self.stats['processed'] - self.stats['errors']) / 
                       self.stats['total'] * 100)
        
        self.logger.info(
            f"ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฃŒ! "
            f"์†Œ์š”์‹œ๊ฐ„: {elapsed:.1f}์ดˆ | "
            f"์„ฑ๊ณต๋ฅ : {success_rate:.1f}% | "
            f"์—๋Ÿฌ: {self.stats['errors']}๊ฐœ"
        )

# ์‚ฌ์šฉ ์˜ˆ์ œ
pipeline_logger = DataPipelineLogger('data_pipeline')

items = range(1000)
pipeline_logger.start_pipeline(len(items))

for i, item in enumerate(items):
    try:
        # ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
        time.sleep(0.001)  # ์‹ค์ œ ์ž‘์—… ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        
        if i % 100 == 0:
            pipeline_logger.log_progress(i)
    
    except Exception as e:
        pipeline_logger.log_error(item, e)

pipeline_logger.finish_pipeline()

๐Ÿ”Œ API ํด๋ผ์ด์–ธํŠธ

๐Ÿ’ก API ํ˜ธ์ถœ ๋กœ๊น… ํŒ

์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๋ชจ๋‘ ๋กœ๊น…ํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ๋‚˜์ค‘์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ์ •ํ™•ํžˆ ๋ฌด์—‡์ด ์ž˜๋ชป๋๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๊ฑฐ๋“ :
import logging
import requests
from functools import wraps
import time

def log_api_call(logger):
    """API ํ˜ธ์ถœ์„ ์ž๋™์œผ๋กœ ๋กœ๊น…ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            
            # ์š”์ฒญ ์ •๋ณด ๋กœ๊น…
            logger.info(f"API ํ˜ธ์ถœ ์‹œ์ž‘: {func.__name__}")
            logger.debug(f"์ธ์ž: args={args}, kwargs={kwargs}")
            
            try:
                response = func(*args, **kwargs)
                elapsed = time.time() - start_time
                
                # ์„ฑ๊ณต ๋กœ๊น…
                logger.info(
                    f"API ํ˜ธ์ถœ ์„ฑ๊ณต: {func.__name__} "
                    f"(์†Œ์š”์‹œ๊ฐ„: {elapsed:.2f}์ดˆ, ์ƒํƒœ์ฝ”๋“œ: {response.status_code})"
                )
                logger.debug(f"์‘๋‹ต: {response.text[:200]}")  # ์ฒ˜์Œ 200์ž๋งŒ
                
                return response
            
            except requests.exceptions.RequestException as e:
                elapsed = time.time() - start_time
                logger.error(
                    f"API ํ˜ธ์ถœ ์‹คํŒจ: {func.__name__} "
                    f"(์†Œ์š”์‹œ๊ฐ„: {elapsed:.2f}์ดˆ, ์—๋Ÿฌ: {str(e)})"
                )
                raise
        
        return wrapper
    return decorator

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)

@log_api_call(logger)
def fetch_user_data(user_id):
    """์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์กฐํšŒ"""
    response = requests.get(f"https://api.example.com/users/{user_id}")
    response.raise_for_status()
    return response

# ํ˜ธ์ถœ
try:
    data = fetch_user_data(12345)
except Exception as e:
    logger.error(f"์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ: {e}")

๐ŸŽจ ๋กœ๊ทธ ๋ถ„์„ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ๊ทธ๊ฑธ ํšจ๊ณผ์ ์œผ๋กœ ๋ถ„์„ํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด! ๐Ÿ“Š

๐Ÿ“ˆ ELK Stack (Elasticsearch, Logstash, Kibana)

ELK Stack ์†Œ๊ฐœ

ELK๋Š” ๋กœ๊ทธ ๋ถ„์„์˜ ํ‘œ์ค€์ด์•ผ. ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ๋กœ๊ทธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ์–ด:

Elasticsearch - ๋กœ๊ทธ๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ฒ€์ƒ‰
Logstash - ๋กœ๊ทธ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ณ€ํ™˜
Kibana - ๋กœ๊ทธ๋ฅผ ์‹œ๊ฐํ™”ํ•˜๊ณ  ๋Œ€์‹œ๋ณด๋“œ ์ƒ์„ฑ

Python์—์„œ Elasticsearch๋กœ ์ง์ ‘ ๋กœ๊ทธ๋ฅผ ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•:
from elasticsearch import Elasticsearch
import logging
from datetime import datetime

class ElasticsearchHandler(logging.Handler):
    """Elasticsearch๋กœ ๋กœ๊ทธ๋ฅผ ์ „์†กํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ"""
    
    def __init__(self, es_host='localhost', es_port=9200, index_name='app-logs'):
        super().__init__()
        self.es = Elasticsearch([f'http://{es_host}:{es_port}'])
        self.index_name = index_name
    
    def emit(self, record):
        """๋กœ๊ทธ ๋ ˆ์ฝ”๋“œ๋ฅผ Elasticsearch๋กœ ์ „์†ก"""
        log_entry = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno
        }
        
        if record.exc_info:
            log_entry['exception'] = self.format(record)
        
        try:
            self.es.index(
                index=f"{self.index_name}-{datetime.utcnow().strftime('%Y.%m.%d')}",
                document=log_entry
            )
        except Exception as e:
            print(f"Elasticsearch ์ „์†ก ์‹คํŒจ: {e}")

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)
es_handler = ElasticsearchHandler()
logger.addHandler(es_handler)
logger.setLevel(logging.INFO)

logger.info("์ด ๋กœ๊ทธ๋Š” Elasticsearch์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค!")

๐Ÿ”” ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์„ค์ •

์ค‘์š”ํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฆ‰์‹œ ์•Œ๋ฆผ์„ ๋ฐ›๊ณ  ์‹ถ์ง€? Slack์ด๋‚˜ ์ด๋ฉ”์ผ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•์ด์•ผ:
import logging
import requests

class SlackHandler(logging.Handler):
    """Slack์œผ๋กœ ๋กœ๊ทธ๋ฅผ ์ „์†กํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ"""
    
    def __init__(self, webhook_url, username='LogBot', emoji=':robot_face:'):
        super().__init__()
        self.webhook_url = webhook_url
        self.username = username
        self.emoji = emoji
    
    def emit(self, record):
        """๋กœ๊ทธ ๋ ˆ์ฝ”๋“œ๋ฅผ Slack์œผ๋กœ ์ „์†ก"""
        if record.levelno < logging.ERROR:
            return  # ERROR ์ด์ƒ๋งŒ ์ „์†ก
        
        # ์ƒ‰์ƒ ์„ค์ •
        color_map = {
            logging.ERROR: '#ff9800',
            logging.CRITICAL: '#f44336'
        }
        
        message = {
            'username': self.username,
            'icon_emoji': self.emoji,
            'attachments': [{
                'color': color_map.get(record.levelno, '#2196f3'),
                'title': f'{record.levelname}: {record.name}',
                'text': record.getMessage(),
                'fields': [
                    {
                        'title': 'ํŒŒ์ผ',
                        'value': f'{record.filename}:{record.lineno}',
                        'short': True
                    },
                    {
                        'title': 'ํ•จ์ˆ˜',
                        'value': record.funcName,
                        'short': True
                    }
                ],
                'footer': 'Python Logger',
                'ts': int(record.created)
            }]
        }
        
        if record.exc_info:
            message['attachments'][0]['text'] += f"\n```{self.format(record)}```"
        
        try:
            requests.post(self.webhook_url, json=message)
        except Exception as e:
            print(f"Slack ์ „์†ก ์‹คํŒจ: {e}")

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)
slack_handler = SlackHandler('https://hooks.slack.com/services/YOUR/WEBHOOK/URL')
logger.addHandler(slack_handler)

# ์ด์ œ ERROR ์ด์ƒ์˜ ๋กœ๊ทธ๋Š” Slack์œผ๋กœ ์ „์†ก๋จ
logger.error("๊ธด๊ธ‰! ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ!")

๐Ÿงช ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ์˜ ๋กœ๊น…

ํ…Œ์ŠคํŠธํ•  ๋•Œ๋„ ๋กœ๊น…์ด ์ค‘์š”ํ•ด! ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ ์™œ ์‹คํŒจํ–ˆ๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐Ÿงช

๐ŸŽฏ pytest์™€ ๋กœ๊น…

pytest ๋กœ๊น… ์„ค์ •

pytest๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กœ๊ทธ๋ฅผ ์บก์ฒ˜ํ•ด. ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ๊ด€๋ จ ๋กœ๊ทธ๋ฅผ ๋ณด์—ฌ์ฃผ์ง€:
# conftest.py
import logging
import pytest

@pytest.fixture(autouse=True)
def setup_logging(caplog):
    """๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ๋กœ๊น… ์„ค์ • ์ ์šฉ"""
    caplog.set_level(logging.DEBUG)

# test_example.py
import logging

logger = logging.getLogger(__name__)

def test_with_logging():
    """๋กœ๊น…์„ ํฌํ•จํ•œ ํ…Œ์ŠคํŠธ"""
    logger.info("ํ…Œ์ŠคํŠธ ์‹œ์ž‘")
    
    result = 2 + 2
    logger.debug(f"๊ณ„์‚ฐ ๊ฒฐ๊ณผ: {result}")
    
    assert result == 4
    logger.info("ํ…Œ์ŠคํŠธ ์„ฑ๊ณต")

def test_log_capture(caplog):
    """๋กœ๊ทธ ์บก์ฒ˜ ํ…Œ์ŠคํŠธ"""
    logger.warning("๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€")
    
    # ๋กœ๊ทธ๊ฐ€ ์ œ๋Œ€๋กœ ๊ธฐ๋ก๋๋Š”์ง€ ํ™•์ธ
    assert "๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€" in caplog.text
    assert any(record.levelname == "WARNING" for record in caplog.records)
pytest ์‹คํ–‰ ์‹œ ๋กœ๊ทธ ์ถœ๋ ฅ ์˜ต์…˜:
# ๋ชจ๋“  ๋กœ๊ทธ ์ถœ๋ ฅ
pytest -v --log-cli-level=DEBUG

# ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ์˜ ๋กœ๊ทธ๋งŒ ์ถœ๋ ฅ
pytest -v --log-cli-level=INFO

# ๋กœ๊ทธ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅ
pytest --log-file=test.log --log-file-level=DEBUG

๐Ÿ” Mock๊ณผ ๋กœ๊น…

์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” mock์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ๋กœ๊น…์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด:
import logging
from unittest.mock import Mock, patch
import pytest

logger = logging.getLogger(__name__)

def fetch_data_from_api(api_client, user_id):
    """API์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜"""
    logger.info(f"์‚ฌ์šฉ์ž {user_id} ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ์ž‘")
    
    try:
        data = api_client.get_user(user_id)
        logger.info(f"๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต: {len(data)} ํ•ญ๋ชฉ")
        return data
    except Exception as e:
        logger.error(f"๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ: {e}")
        raise

def test_fetch_data_success(caplog):
    """์„ฑ๊ณต ์ผ€์ด์Šค ํ…Œ์ŠคํŠธ"""
    # Mock API ํด๋ผ์ด์–ธํŠธ
    mock_client = Mock()
    mock_client.get_user.return_value = {'id': 123, 'name': 'ํ™๊ธธ๋™'}
    
    # ํ•จ์ˆ˜ ์‹คํ–‰
    result = fetch_data_from_api(mock_client, 123)
    
    # ๊ฒฐ๊ณผ ํ™•์ธ
    assert result['id'] == 123
    
    # ๋กœ๊ทธ ํ™•์ธ
    assert "๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ์ž‘" in caplog.text
    assert "๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต" in caplog.text

def test_fetch_data_failure(caplog):
    """์‹คํŒจ ์ผ€์ด์Šค ํ…Œ์ŠคํŠธ"""
    # Mock API ํด๋ผ์ด์–ธํŠธ (์—๋Ÿฌ ๋ฐœ์ƒ)
    mock_client = Mock()
    mock_client.get_user.side_effect = Exception("API ์—๋Ÿฌ")
    
    # ์˜ˆ์™ธ ๋ฐœ์ƒ ํ™•์ธ
    with pytest.raises(Exception):
        fetch_data_from_api(mock_client, 123)
    
    # ์—๋Ÿฌ ๋กœ๊ทธ ํ™•์ธ
    assert "๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ" in caplog.text
    assert "API ์—๋Ÿฌ" in caplog.text

๐Ÿš€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ์ตœ์ ํ™”

๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์˜ ๋กœ๊น… ์ „๋žต์€ ๋‹ฌ๋ผ์•ผ ํ•ด. ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์„ฑ๋Šฅ๊ณผ ๋ณด์•ˆ์ด ๋” ์ค‘์š”ํ•˜๊ฑฐ๋“ ! ๐Ÿ”’

โšก ๋น„๋™๊ธฐ ๋กœ๊น…

์„ฑ๋Šฅ ์ตœ์ ํ™”: ๋น„๋™๊ธฐ ๋กœ๊น…

๋กœ๊น…์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ฃผ๋ฉด ์•ˆ ๋ผ. ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š์•„:
import logging
from logging.handlers import QueueHandler, QueueListener
import queue
import atexit

def setup_async_logging():
    """๋น„๋™๊ธฐ ๋กœ๊น… ์„ค์ •"""
    # ๋กœ๊ทธ ํ ์ƒ์„ฑ
    log_queue = queue.Queue(-1)  # ๋ฌด์ œํ•œ ํฌ๊ธฐ
    
    # ์‹ค์ œ ๋กœ๊ทธ๋ฅผ ์ฒ˜๋ฆฌํ•  ํ•ธ๋“ค๋Ÿฌ๋“ค
    file_handler = logging.handlers.RotatingFileHandler(
        'logs/app.log',
        maxBytes=10*1024*1024,
        backupCount=5
    )
    console_handler = logging.StreamHandler()
    
    # ํฌ๋งคํ„ฐ ์„ค์ •
    formatter = logging.Formatter(
        '%(asctime)s | %(levelname)s | %(name)s | %(message)s'
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)
    
    # QueueListener: ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ํ์˜ ๋กœ๊ทธ๋ฅผ ์ฒ˜๋ฆฌ
    listener = QueueListener(
        log_queue,
        file_handler,
        console_handler,
        respect_handler_level=True
    )
    listener.start()
    
    # ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ ์‹œ ๋ฆฌ์Šค๋„ˆ๋„ ์ข…๋ฃŒ
    atexit.register(listener.stop)
    
    # ๋ฃจํŠธ ๋กœ๊ฑฐ์— QueueHandler ์ถ”๊ฐ€
    queue_handler = QueueHandler(log_queue)
    root_logger = logging.getLogger()
    root_logger.addHandler(queue_handler)
    root_logger.setLevel(logging.INFO)
    
    return listener

# ์‚ฌ์šฉ
listener = setup_async_logging()
logger = logging.getLogger(__name__)

# ์ด์ œ ๋กœ๊น…์ด ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋จ!
for i in range(10000):
    logger.info(f"๋ฉ”์‹œ์ง€ {i}")  # ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š์Œ

๐ŸŽš๏ธ ๋™์  ๋กœ๊ทธ ๋ ˆ๋ฒจ ์กฐ์ •

ํ”„๋กœ๋•์…˜์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ, ์„œ๋ฒ„๋ฅผ ์žฌ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ  ๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์ •๋ง ํŽธํ•ด:
import logging
import signal
import sys

class DynamicLogLevel:
    """๋™์ ์œผ๋กœ ๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ์กฐ์ •ํ•˜๋Š” ํด๋ž˜์Šค"""
    
    def __init__(self):
        self.logger = logging.getLogger()
        self.current_level = logging.INFO
        self.logger.setLevel(self.current_level)
        
        # ์‹œ๊ทธ๋„ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
        signal.signal(signal.SIGUSR1, self.increase_verbosity)
        signal.signal(signal.SIGUSR2, self.decrease_verbosity)
    
    def increase_verbosity(self, signum, frame):
        """๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ๋” ์ƒ์„ธํ•˜๊ฒŒ (SIGUSR1)"""
        levels = [logging.CRITICAL, logging.ERROR, logging.WARNING, 
                 logging.INFO, logging.DEBUG]
        
        current_index = levels.index(self.current_level)
        if current_index < len(levels) - 1:
            self.current_level = levels[current_index + 1]
            self.logger.setLevel(self.current_level)
            print(f"๋กœ๊ทธ ๋ ˆ๋ฒจ ๋ณ€๊ฒฝ: {logging.getLevelName(self.current_level)}")
    
    def decrease_verbosity(self, signum, frame):
        """๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ๋œ ์ƒ์„ธํ•˜๊ฒŒ (SIGUSR2)"""
        levels = [logging.CRITICAL, logging.ERROR, logging.WARNING, 
                 logging.INFO, logging.DEBUG]
        
        current_index = levels.index(self.current_level)
        if current_index > 0:
            self.current_level = levels[current_index - 1]
            self.logger.setLevel(self.current_level)
            print(f"๋กœ๊ทธ ๋ ˆ๋ฒจ ๋ณ€๊ฒฝ: {logging.getLevelName(self.current_level)}")

# ์‚ฌ์šฉ
dynamic_log = DynamicLogLevel()

# ์ด์ œ ์‹คํ–‰ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค์— ์‹œ๊ทธ๋„์„ ๋ณด๋‚ด์„œ ๋กœ๊ทธ ๋ ˆ๋ฒจ ์กฐ์ • ๊ฐ€๋Šฅ
# kill -USR1 <pid>  # ๋” ์ƒ์„ธํ•˜๊ฒŒ
# kill -USR2 <pid>  # ๋œ ์ƒ์„ธํ•˜๊ฒŒ

๐Ÿ“Š ๋กœ๊ทธ ์ƒ˜ํ”Œ๋ง

๐Ÿ’ก ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ

์ดˆ๋‹น ์ˆ˜์ฒœ ๊ฐœ์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋น„์Šค์—์„œ ๋ชจ๋“  ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋ฉด ๋””์Šคํฌ๊ฐ€ ๊ธˆ๋ฐฉ ์ฐจ. ์ƒ˜ํ”Œ๋ง์„ ์‚ฌ์šฉํ•˜๋ฉด ์ผ๋ถ€๋งŒ ๋กœ๊น…ํ•  ์ˆ˜ ์žˆ์–ด:
import logging
import random

class SamplingFilter(logging.Filter):
    """๋กœ๊ทธ๋ฅผ ์ƒ˜ํ”Œ๋งํ•˜๋Š” ํ•„ํ„ฐ"""
    
    def __init__(self, sample_rate=0.1):
        """
        sample_rate: 0.0 ~ 1.0 ์‚ฌ์ด์˜ ๊ฐ’
        0.1 = 10%๋งŒ ๋กœ๊น…
        """
        super().__init__()
        self.sample_rate = sample_rate
    
    def filter(self, record):
        # ERROR ์ด์ƒ์€ ํ•ญ์ƒ ๋กœ๊น…
        if record.levelno >= logging.ERROR:
            return True
        
        # ๋‚˜๋จธ์ง€๋Š” ์ƒ˜ํ”Œ๋ง
        return random.random() < self.sample_rate

# ์‚ฌ์šฉ ์˜ˆ์ œ
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()

# INFO ๋กœ๊ทธ๋Š” 10%๋งŒ ๊ธฐ๋ก
sampling_filter = SamplingFilter(sample_rate=0.1)
handler.addFilter(sampling_filter)

logger.addHandler(handler)
logger.setLevel(logging.INFO)

# 1000๊ฐœ ์ค‘ ์•ฝ 100๊ฐœ๋งŒ ๋กœ๊น…๋จ
for i in range(1000):
    logger.info(f"์š”์ฒญ ์ฒ˜๋ฆฌ: {i}")

# ํ•˜์ง€๋งŒ ์—๋Ÿฌ๋Š” ๋ชจ๋‘ ๋กœ๊น…๋จ
logger.error("์ด๊ฑด ๋ฐ˜๋“œ์‹œ ๊ธฐ๋ก๋จ!")

๐Ÿ›ก๏ธ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

๋กœ๊น…ํ•  ๋•Œ ๋ณด์•ˆ์„ ๊ฐ„๊ณผํ•˜๋ฉด ํฐ์ผ๋‚˜! ์‹ค์ œ๋กœ ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์ •๋ณด๊ฐ€ ์œ ์ถœ๋˜๋Š” ์‚ฌ๊ณ ๊ฐ€ ๋งŽ์•„. ๐Ÿ˜ฑ

๐Ÿ” ์•ˆ์ „ํ•œ ๋กœ๊น… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โš ๏ธ ์ ˆ๋Œ€ ๋กœ๊ทธ์— ๋‚จ๊ธฐ๋ฉด ์•ˆ ๋˜๋Š” ๊ฒƒ๋“ค

โœ… ๋น„๋ฐ€๋ฒˆํ˜ธ - ํ‰๋ฌธ์ด๋“  ํ•ด์‹œ๋“  ์ ˆ๋Œ€ ์•ˆ ๋ผ
โœ… API ํ‚ค / ํ† ํฐ - ์ธ์ฆ ์ •๋ณด๋Š” ๋งˆ์Šคํ‚น ํ•„์ˆ˜
โœ… ์‹ ์šฉ์นด๋“œ ์ •๋ณด - PCI-DSS ์œ„๋ฐ˜
โœ… ์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ - ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์œ„๋ฐ˜
โœ… ์„ธ์…˜ ID - ์„ธ์…˜ ํ•˜์ด์žฌํ‚น ์œ„ํ—˜
โœ… ๊ฐœ์ธ ์‹๋ณ„ ์ •๋ณด - GDPR ๋“ฑ ๊ทœ์ • ์œ„๋ฐ˜

๐Ÿ›ก๏ธ ์•ˆ์ „ํ•œ ๋กœ๊น… ์˜ˆ์ œ
import logging
import hashlib
import re

class SecureLogger:
    """๋ณด์•ˆ์„ ๊ณ ๋ คํ•œ ๋กœ๊ฑฐ ๋ž˜ํผ"""
    
    def __init__(self, name):
        self.logger = logging.getLogger(name)
        self.sensitive_patterns = {
            'password': re.compile(r'password["\']?\s*[:=]\s*["\']?([^"\'}\s,]+)', re.I),
            'token': re.compile(r'token["\']?\s*[:=]\s*["\']?([^"\'}\s,]+)', re.I),
            'api_key': re.compile(r'api[_-]?key["\']?\s*[:=]\s*["\']?([^"\'}\s,]+)', re.I),
            'card': re.compile(r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b'),
            'ssn': re.compile(r'\b\d{6}-\d{7}\b'),
        }
    
    def _sanitize(self, message):
        """๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๋งˆ์Šคํ‚น"""
        for name, pattern in self.sensitive_patterns.items():
            message = pattern.sub(f'{name}=***REDACTED***', message)
        return message
    
    def _hash_identifier(self, identifier):
        """์‹๋ณ„์ž๋ฅผ ํ•ด์‹œ๋กœ ๋ณ€ํ™˜ (์ถ”์ ์€ ๊ฐ€๋Šฅํ•˜๋˜ ์›๋ณธ์€ ์ˆจ๊น€)"""
        return hashlib.sha256(str(identifier).encode()).hexdigest()[:16]
    
    def info(self, message, **kwargs):
        """์•ˆ์ „ํ•œ INFO ๋กœ๊น…"""
        safe_message = self._sanitize(message)
        self.logger.info(safe_message, **kwargs)
    
    def error(self, message, **kwargs):
        """์•ˆ์ „ํ•œ ERROR ๋กœ๊น…"""
        safe_message = self._sanitize(message)
        self.logger.error(safe_message, **kwargs)
    
    def log_user_action(self, user_id, action):
        """์‚ฌ์šฉ์ž ํ–‰๋™ ๋กœ๊น… (ID๋Š” ํ•ด์‹œ๋กœ)"""
        hashed_id = self._hash_identifier(user_id)
        self.logger.info(f"์‚ฌ์šฉ์ž ํ–‰๋™: {hashed_id} - {action}")

# ์‚ฌ์šฉ ์˜ˆ์ œ
secure_logger = SecureLogger(__name__)

# ์œ„ํ—˜ํ•œ ์ •๋ณด๊ฐ€ ์ž๋™์œผ๋กœ ๋งˆ์Šคํ‚น๋จ
secure_logger.info("๋กœ๊ทธ์ธ ์‹œ๋„: password=secret123, token=abc123xyz")
# ์‹ค์ œ ๋กœ๊ทธ: "๋กœ๊ทธ์ธ ์‹œ๋„: password=***REDACTED***, token=***REDACTED***"

# ์‚ฌ์šฉ์ž ID๋Š” ํ•ด์‹œ๋กœ ๋ณ€ํ™˜
secure_logger.log_user_action(12345, "์ƒํ’ˆ ๊ตฌ๋งค")
# ์‹ค์ œ ๋กœ๊ทธ: "์‚ฌ์šฉ์ž ํ–‰๋™: 8d969eef6ecad3c2 - ์ƒํ’ˆ ๊ตฌ๋งค"

๐Ÿ”’ ๋กœ๊ทธ ํŒŒ์ผ ๋ณด์•ˆ

๋กœ๊ทธ ํŒŒ์ผ ์ž์ฒด๋„ ๋ณดํ˜ธํ•ด์•ผ ํ•ด:
import os
import logging
from logging.handlers import RotatingFileHandler

def create_secure_log_handler(log_file):
    """๋ณด์•ˆ์ด ๊ฐ•ํ™”๋œ ๋กœ๊ทธ ํ•ธ๋“ค๋Ÿฌ ์ƒ์„ฑ"""
    
    # ๋กœ๊ทธ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ (๊ถŒํ•œ ์ œํ•œ)
    log_dir = os.path.dirname(log_file)
    if not os.path.exists(log_dir):
        os.makedirs(log_dir, mode=0o750)  # rwxr-x---
    
    # ํ•ธ๋“ค๋Ÿฌ ์ƒ์„ฑ
    handler = RotatingFileHandler(
        log_file,
        maxBytes=10*1024*1024,
        backupCount=5
    )
    
    # ๋กœ๊ทธ ํŒŒ์ผ ๊ถŒํ•œ ์„ค์ • (์†Œ์œ ์ž๋งŒ ์ฝ๊ธฐ/์“ฐ๊ธฐ)
    if os.path.exists(log_file):
        os.chmod(log_file, 0o600)  # rw-------
    
    return handler

# ์‚ฌ์šฉ
handler = create_secure_log_handler('/var/log/myapp/app.log')
logger = logging.getLogger(__name__)
logger.addHandler(handler)

๐ŸŽ“ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์˜ˆ์ œ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์™„์ „ํ•œ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ๐Ÿš€

๐Ÿ—๏ธ ์™„์ „ํ•œ ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•

ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ ๋กœ๊น… ์‹œ์Šคํ…œ

์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์™„์ „ํ•œ ๋กœ๊น… ์‹œ์Šคํ…œ์ด์•ผ:
# logging_config.py
import logging
import logging.handlers
import os
import json
from datetime import datetime
from pathlib import Path

class ProductionLoggingSystem:
    """ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์„ ์œ„ํ•œ ์™„์ „ํ•œ ๋กœ๊น… ์‹œ์Šคํ…œ"""
    
    def __init__(self, app_name, log_dir='logs', environment='production'):
        self.app_name = app_name
        self.log_dir = Path(log_dir)
        self.environment = environment
        
        # ๋กœ๊ทธ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
        self.log_dir.mkdir(parents=True, exist_ok=True)
        
        # ๋กœ๊ฑฐ ๋”•์…”๋„ˆ๋ฆฌ
        self.loggers = {}
        
        # ๊ธฐ๋ณธ ๋กœ๊ฑฐ๋“ค ์„ค์ •
        self._setup_loggers()
    
    def _setup_loggers(self):
        """๋‹ค์–‘ํ•œ ๋ชฉ์ ์˜ ๋กœ๊ฑฐ๋“ค์„ ์„ค์ •"""
        
        # 1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ฑฐ
        self.loggers['app'] = self._create_logger(
            'app',
            self.log_dir / 'app.log',
            level=logging.INFO
        )
        
        # 2. ์—๋Ÿฌ ๋กœ๊ฑฐ (ERROR ์ด์ƒ๋งŒ)
        self.loggers['error'] = self._create_logger(
            'error',
            self.log_dir / 'error.log',
            level=logging.ERROR
        )
        
        # 3. ์•ก์„ธ์Šค ๋กœ๊ฑฐ (API ์š”์ฒญ ๋“ฑ)
        self.loggers['access'] = self._create_logger(
            'access',
            self.log_dir / 'access.log',
            level=logging.INFO,
            format_type='access'
        )
        
        # 4. ์„ฑ๋Šฅ ๋กœ๊ฑฐ
        self.loggers['performance'] = self._create_logger(
            'performance',
            self.log_dir / 'performance.log',
            level=logging.INFO,
            format_type='json'
        )
        
        # 5. ๋ณด์•ˆ ๋กœ๊ฑฐ
        self.loggers['security'] = self._create_logger(
            'security',
            self.log_dir / 'security.log',
            level=logging.WARNING
        )
    
    def _create_logger(self, name, log_file, level=logging.INFO, format_type='standard'):
        """๊ฐœ๋ณ„ ๋กœ๊ฑฐ ์ƒ์„ฑ"""
        logger = logging.getLogger(f'{self.app_name}.{name}')
        logger.setLevel(level)
        logger.propagate = False
        
        # ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ
        file_handler = logging.handlers.RotatingFileHandler(
            log_file,
            maxBytes=50*1024*1024,  # 50MB
            backupCount=10,
            encoding='utf-8'
        )
        
        # ํฌ๋งคํ„ฐ ์„ ํƒ
        if format_type == 'json':
            formatter = self._get_json_formatter()
        elif format_type == 'access':
            formatter = self._get_access_formatter()
        else:
            formatter = self._get_standard_formatter()
        
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)
        
        # ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ์ฝ˜์†”์—๋„ ์ถœ๋ ฅ
        if self.environment == 'development':
            console_handler = logging.StreamHandler()
            console_handler.setFormatter(formatter)
            logger.addHandler(console_handler)
        
        return logger
    
    def _get_standard_formatter(self):
        """ํ‘œ์ค€ ํฌ๋งคํ„ฐ"""
        return logging.Formatter(
            '%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
    
    def _get_access_formatter(self):
        """์•ก์„ธ์Šค ๋กœ๊ทธ ํฌ๋งคํ„ฐ"""
        return logging.Formatter(
            '%(asctime)s | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
    
    def _get_json_formatter(self):
        """JSON ํฌ๋งคํ„ฐ"""
        class JsonFormatter(logging.Formatter):
            def format(self, record):
                log_data = {
                    'timestamp': datetime.utcnow().isoformat(),
                    'level': record.levelname,
                    'logger': record.name,
                    'message': record.getMessage(),
                    'module': record.module,
                    'function': record.funcName,
                    'line': record.lineno
                }
                
                if record.exc_info:
                    log_data['exception'] = self.formatException(record.exc_info)
                
                return json.dumps(log_data, ensure_ascii=False)
        
        return JsonFormatter()
    
    def get_logger(self, logger_type='app'):
        """ํŠน์ • ํƒ€์ž…์˜ ๋กœ๊ฑฐ ๋ฐ˜ํ™˜"""
        return self.loggers.get(logger_type, self.loggers['app'])

# ์‚ฌ์šฉ ์˜ˆ์ œ
logging_system = ProductionLoggingSystem('MyApp', environment='production')

# ๋‹ค์–‘ํ•œ ๋กœ๊ฑฐ ์‚ฌ์šฉ
app_logger = logging_system.get_logger('app')
error_logger = logging_system.get_logger('error')
access_logger = logging_system.get_logger('access')
perf_logger = logging_system.get_logger('performance')
security_logger = logging_system.get_logger('security')

# ๋กœ๊น…
app_logger.info("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘")
access_logger.info("GET /api/users - 200 - 45ms")
perf_logger.info("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์™„๋ฃŒ", extra={'duration': 0.045})
security_logger.warning("์˜์‹ฌ์Šค๋Ÿฌ์šด ๋กœ๊ทธ์ธ ์‹œ๋„")
error_logger.error("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ", exc_info=True)

๐ŸŽฏ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ๋กœ๊น…

์š”์ฒญ๋งˆ๋‹ค ๊ณ ์œ ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ๋กœ๊น…ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์•ผ:
# context_logging.py
import logging
import contextvars
import uuid
from functools import wraps

# ์ปจํ…์ŠคํŠธ ๋ณ€์ˆ˜ ์ •์˜
request_id_var = contextvars.ContextVar('request_id', default=None)
user_id_var = contextvars.ContextVar('user_id', default=None)

class ContextFilter(logging.Filter):
    """์ปจํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ๋กœ๊ทธ์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•„ํ„ฐ"""
    
    def filter(self, record):
        record.request_id = request_id_var.get() or 'N/A'
        record.user_id = user_id_var.get() or 'anonymous'
        return True

def setup_context_logging():
    """์ปจํ…์ŠคํŠธ ๋กœ๊น… ์„ค์ •"""
    logger = logging.getLogger()
    
    # ์ปจํ…์ŠคํŠธ ํ•„ํ„ฐ ์ถ”๊ฐ€
    context_filter = ContextFilter()
    
    handler = logging.StreamHandler()
    handler.addFilter(context_filter)
    
    # ํฌ๋งคํ„ฐ์— ์ปจํ…์ŠคํŠธ ์ •๋ณด ํฌํ•จ
    formatter = logging.Formatter(
        '%(asctime)s | [%(request_id)s] [%(user_id)s] | %(levelname)s | %(message)s'
    )
    handler.setFormatter(formatter)
    
    logger.addHandler(handler)
    logger.setLevel(logging.INFO)

def with_request_context(func):
    """์š”์ฒญ ์ปจํ…์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        # ์ƒˆ๋กœ์šด ์š”์ฒญ ID ์ƒ์„ฑ
        request_id = str(uuid.uuid4())
        request_id_var.set(request_id)
        
        logger = logging.getLogger(__name__)
        logger.info(f"์š”์ฒญ ์‹œ์ž‘: {func.__name__}")
        
        try:
            result = func(*args, **kwargs)
            logger.info(f"์š”์ฒญ ์™„๋ฃŒ: {func.__name__}")
            return result
        except Exception as e:
            logger.error(f"์š”์ฒญ ์‹คํŒจ: {func.__name__} - {str(e)}")
            raise
        finally:
            # ์ปจํ…์ŠคํŠธ ์ •๋ฆฌ
            request_id_var.set(None)
    
    return wrapper

# ์‚ฌ์šฉ ์˜ˆ์ œ
setup_context_logging()
logger = logging.getLogger(__name__)

@with_request_context
def process_user_request(user_id, action):
    """์‚ฌ์šฉ์ž ์š”์ฒญ ์ฒ˜๋ฆฌ"""
    # ์‚ฌ์šฉ์ž ID ์„ค์ •
    user_id_var.set(user_id)
    
    logger.info(f"์ž‘์—… ์ˆ˜ํ–‰: {action}")
    
    # ์‹ค์ œ ์ž‘์—…
    if action == "delete":
        logger.warning("์‚ญ์ œ ์ž‘์—… ์ˆ˜ํ–‰")
    
    return "์™„๋ฃŒ"

# ํ˜ธ์ถœ
process_user_request(12345, "update")
process_user_request(67890, "delete")

# ์ถœ๋ ฅ ์˜ˆ์‹œ:
# 2024-01-15 10:30:45 | [a1b2c3d4-...] [12345] | INFO | ์š”์ฒญ ์‹œ์ž‘: process_user_request
# 2024-01-15 10:30:45 | [a1b2c3d4-...] [12345] | INFO | ์ž‘์—… ์ˆ˜ํ–‰: update
# 2024-01-15 10:30:45 | [a1b2c3d4-...] [12345] | INFO | ์š”์ฒญ ์™„๋ฃŒ: process_user_request

๐ŸŽจ ๋””๋ฒ„๊น… ์›Œํฌํ”Œ๋กœ์šฐ

ํšจ๊ณผ์ ์ธ ๋””๋ฒ„๊น…์€ ์ฒด๊ณ„์ ์ธ ์ ‘๊ทผ์ด ํ•„์š”ํ•ด. ํ”„๋กœ๋“ค์ด ์‚ฌ์šฉํ•˜๋Š” ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ”

๐Ÿ“‹ ๋””๋ฒ„๊น… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๐Ÿ’ก ๋ฒ„๊ทธ๋ฅผ ๋งŒ๋‚ฌ์„ ๋•Œ ๋”ฐ๋ผํ•  ๋‹จ๊ณ„ 1๋‹จ๊ณ„: ๋ฌธ์ œ ์žฌํ˜„
๋ฒ„๊ทธ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด. ์žฌํ˜„ ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฒ„๊ทธ๋Š” ๊ณ ์น˜๊ธฐ ์ •๋ง ์–ด๋ ค์›Œ.

2๋‹จ๊ณ„: ๋กœ๊ทธ ํ™•์ธ
์—๋Ÿฌ ๋ฉ”์‹œ์ง€, ์Šคํƒ ํŠธ๋ ˆ์ด์Šค, ๊ด€๋ จ ๋กœ๊ทธ๋ฅผ ๋ชจ๋‘ ํ™•์ธํ•ด.

3๋‹จ๊ณ„: ๊ฐ€์„ค ์ˆ˜๋ฆฝ
"์•„๋งˆ ์ด ๋ถ€๋ถ„์ด ๋ฌธ์ œ์ผ ๊ฑฐ์•ผ"๋ผ๋Š” ๊ฐ€์„ค์„ ์„ธ์›Œ.

4๋‹จ๊ณ„: ๊ฒฉ๋ฆฌ
๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ตœ์†Œํ•œ์œผ๋กœ ์ค„์—ฌ๋ด. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๋งŒ๋“ค๋ฉด ๋” ์ข‹์•„.

5๋‹จ๊ณ„: ๋””๋ฒ„๊ฑฐ ํ™œ์šฉ
๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ณ€์ˆ˜ ๊ฐ’์„ ํ™•์ธํ•ด.

6๋‹จ๊ณ„: ์ˆ˜์ • ๋ฐ ๊ฒ€์ฆ
์ˆ˜์ • ํ›„ ๋ฐ˜๋“œ์‹œ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ•ด. ํ•œ ๋ฒ„๊ทธ๋ฅผ ๊ณ ์น˜๋‹ค๊ฐ€ ๋‹ค๋ฅธ ๋ฒ„๊ทธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด!

๐Ÿ”ฌ ๋””๋ฒ„๊น… ๋„๊ตฌ ์กฐํ•ฉ

์ƒํ™ฉ๋ณ„ ์ตœ์ ์˜ ๋„๊ตฌ ์„ ํƒ ๊ฐ„๋‹จํ•œ ๋ฒ„๊ทธ
โ†’ print() ๋˜๋Š” logging.debug()
๋น ๋ฅด๊ฒŒ ํ™•์ธํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์ตœ๊ณ ์•ผ.

๋กœ์ง ์˜ค๋ฅ˜
โ†’ pdb ๋˜๋Š” IDE ๋””๋ฒ„๊ฑฐ
๋‹จ๊ณ„๋ณ„๋กœ ์‹คํ–‰ํ•˜๋ฉด์„œ ๋ณ€์ˆ˜ ๊ฐ’์„ ํ™•์ธํ•ด.

์„ฑ๋Šฅ ๋ฌธ์ œ
โ†’ cProfile + snakeviz
์–ด๋А ํ•จ์ˆ˜๊ฐ€ ๋А๋ฆฐ์ง€ ์‹œ๊ฐ์ ์œผ๋กœ ํŒŒ์•…ํ•ด.

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜
โ†’ memory_profiler + objgraph
๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ถ”์ ํ•˜๊ณ  ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๋ถ„์„ํ•ด.

๋™์‹œ์„ฑ ๋ฌธ์ œ
โ†’ threading ๋ชจ๋“ˆ + ๋กœ๊น…
์Šค๋ ˆ๋“œ๋ณ„๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ํƒ€์ด๋ฐ์„ ๋ถ„์„ํ•ด.

ํ”„๋กœ๋•์…˜ ๋ฒ„๊ทธ
โ†’ ๊ตฌ์กฐํ™”๋œ ๋กœ๊น… + ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ
์‹ค์‹œ๊ฐ„์œผ๋กœ ๋กœ๊ทธ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํŒจํ„ด์„ ์ฐพ์•„.

๐ŸŒŸ ์žฌ๋Šฅ๋„ท์—์„œ ๋ฐฐ์šฐ๊ณ  ๋‚˜๋ˆ„๊ธฐ

์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด Python ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น…์— ๋Œ€ํ•ด ๊ฝค ๋งŽ์ด ์•Œ๊ฒŒ ๋์„ ๊ฑฐ์•ผ! ๐ŸŽ‰

ํ•˜์ง€๋งŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ํ˜ผ์ž ๊ณต๋ถ€ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ๋‚˜๋ˆ„๋Š” ๊ฒŒ ํ›จ์”ฌ ํšจ๊ณผ์ ์ด์•ผ. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•˜๊ณ , ์‹ค์ „ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒช์€ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๋‚˜๋ˆ„๋ฉด ์ •๋ง ๋งŽ์ด ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์–ด.

๐ŸŽ“ ๊ณ„์† ์„ฑ์žฅํ•˜๋Š” ๋ฐฉ๋ฒ• 1. ์‹ค์ „ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜
์ด๋ก ๋งŒ ์•„๋Š” ๊ฒƒ๊ณผ ์‹ค์ œ๋กœ ์ ์šฉํ•ด๋ณธ ๊ฒƒ์€ ์™„์ „ํžˆ ๋‹ฌ๋ผ. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ผ๋„ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋ฉด์„œ ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น…์„ ์ ์šฉํ•ด๋ด.

2. ์˜คํ”ˆ์†Œ์Šค ๊ธฐ์—ฌ
์œ ๋ช…ํ•œ Python ํ”„๋กœ์ ํŠธ๋“ค์˜ ์ฝ”๋“œ๋ฅผ ์ฝ์–ด๋ด. ์–ด๋–ป๊ฒŒ ๋กœ๊น…์„ ๊ตฌ์„ฑํ–ˆ๋Š”์ง€, ์–ด๋–ค ๋””๋ฒ„๊น… ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๋ฐฐ์šธ ๊ฒŒ ์ •๋ง ๋งŽ์•„.

3. ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌ
Stack Overflow, Reddit, ํ•œ๊ตญ ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์„ ๋ณด๋ฉด์„œ ๋ฐฐ์›Œ. ์ง์ ‘ ์งˆ๋ฌธํ•˜๊ณ  ๋‹ต๋ณ€ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๊ณต๋ถ€์•ผ.

4. ๋ธ”๋กœ๊ทธ ์ž‘์„ฑ
๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด์„œ ๊ธ€๋กœ ์“ฐ๋ฉด ์ดํ•ด๊ฐ€ ๋” ๊นŠ์–ด์ ธ. ๋‹ค๋ฅธ ์‚ฌ๋žŒ์—๊ฒŒ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ์ง„์งœ ์•„๋Š” ๊ฑฐ์•ผ!

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

ํ•ต์‹ฌ ์š”์•ฝ โœจ ์˜ค๋Š˜ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณผ๊นŒ?

๋กœ๊น…์˜ ์ค‘์š”์„ฑ
โ€ข ๋ฌธ์ œ ์ถ”์ ๊ณผ ๋””๋ฒ„๊น…์˜ ์‹œ์ž‘์ 
โ€ข ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ์˜ ๋ˆˆ
โ€ข ์„ฑ๋Šฅ ๋ถ„์„๊ณผ ๋ณด์•ˆ ๊ฐ์‚ฌ์˜ ๊ธฐ์ดˆ

ํšจ๊ณผ์ ์ธ ๋กœ๊น… ์ „๋žต
โ€ข ์ ์ ˆํ•œ ๋กœ๊ทธ ๋ ˆ๋ฒจ ์‚ฌ์šฉ
โ€ข ๊ตฌ์กฐํ™”๋œ ๋กœ๊น… (JSON)
โ€ข ์ปจํ…์ŠคํŠธ ์ •๋ณด ํฌํ•จ
โ€ข ๋ฏผ๊ฐํ•œ ์ •๋ณด ํ•„ํ„ฐ๋ง
โ€ข ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋””๋ฒ„๊น… ๋„๊ตฌ ํ™œ์šฉ
โ€ข pdb/ipdb๋กœ ๋Œ€ํ™”ํ˜• ๋””๋ฒ„๊น…
โ€ข IDE ๋””๋ฒ„๊ฑฐ๋กœ ์‹œ๊ฐ์  ๋ถ„์„
โ€ข ํ”„๋กœํŒŒ์ผ๋ง์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”
โ€ข ์›๊ฒฉ ๋””๋ฒ„๊น…์œผ๋กœ ํ”„๋กœ๋•์…˜ ๋ฌธ์ œ ํ•ด๊ฒฐ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ๊ณ ๋ ค์‚ฌํ•ญ
โ€ข ๋ณด์•ˆ์„ ์ตœ์šฐ์„ ์œผ๋กœ
โ€ข ์„ฑ๋Šฅ ์˜ํ–ฅ ์ตœ์†Œํ™”
โ€ข ๋กœ๊ทธ ๋ถ„์„ ๋„๊ตฌ ํ™œ์šฉ
โ€ข ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์•Œ๋ฆผ
๐Ÿ“ ๋กœ๊น… ๐Ÿ” ๋””๋ฒ„๊น… ๐Ÿš€ ์ตœ์ ํ™” ํšจ๊ณผ์ ์ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ Happy Debugging! ๐ŸŽ‰
๐ŸŽ“ ๋งˆ์ง€๋ง‰ ์กฐ์–ธ ์™„๋ฒฝํ•œ ์ฝ”๋“œ๋Š” ์—†์–ด. ๋ฒ„๊ทธ๋Š” ์–ธ์ œ๋‚˜ ์ƒ๊ธธ ์ˆ˜ ์žˆ์ง€. ์ค‘์š”ํ•œ ๊ฑด ๋ฒ„๊ทธ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฐพ๊ณ  ํšจ๊ณผ์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๋Šฅ๋ ฅ์ด์•ผ. ์˜ค๋Š˜ ๋ฐฐ์šด ๋กœ๊น…๊ณผ ๋””๋ฒ„๊น… ๊ธฐ์ˆ ๋“ค์ด ๊ทธ ๋Šฅ๋ ฅ์„ ํ‚ค์šฐ๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋  ๊ฑฐ์•ผ! ๐Ÿ’ช

์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์ ์šฉํ•ด๋ณด๋ฉด์„œ ์ต์ˆ™ํ•ด์ง€๋ฉด ๋ผ. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๋” ์ •๊ตํ•œ ๋กœ๊น… ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•ด๋‚˜๊ฐ€๋Š” ๊ฑฐ์ง€.

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์–ตํ•ด! ์ข‹์€ ๋กœ๊ทธ๋Š” ๋ฏธ๋ž˜์˜ ๋‹น์‹ ์„ ๊ตฌ์›ํ•ด์ค„ ๊ฑฐ์•ผ. ์ง€๊ธˆ ๊ท€์ฐฎ๋”๋ผ๋„ ์ œ๋Œ€๋กœ ๋œ ๋กœ๊น… ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•ด๋‘๋ฉด, ๋‚˜์ค‘์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ์ •๋ง ๊ณ ๋งˆ์›Œํ•  ๊ฑฐ์•ผ. ๐Ÿ˜Š

ํ–‰๋ณตํ•œ ์ฝ”๋”ฉ ๋˜๊ธธ ๋ฐ”๋ผ! ๋ฒ„๊ทธ ์—†๋Š” ํ•˜๋ฃจ๋Š” ์—†๊ฒ ์ง€๋งŒ, ๋ฒ„๊ทธ๋ฅผ ๋น ๋ฅด๊ฒŒ ์žก๋Š” ํ•˜๋ฃจ๋Š” ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿ›โœจ
์ด ๊ธ€์ด ๋„์›€์ด ๋๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ๋” ๋งŽ์€ ๊ฐœ๋ฐœ ์ง€์‹์„ ๋‚˜๋ˆ ๋ณด์„ธ์š”! ๐ŸŒŸ
ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์„ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.