KR_Debugging - somaz94/python-study GitHub Wiki

Python ๋””๋ฒ„๊น… ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ print ๋””๋ฒ„๊น…

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋””๋ฒ„๊น… ๋ฐฉ๋ฒ•์ด๋‹ค.

def calculate_total(items):
    total = 0
    for item in items:
        print(f"ํ˜„์žฌ ์•„์ดํ…œ: {item}")  # ๋””๋ฒ„๊น…์šฉ ์ถœ๋ ฅ
        total += item
        print(f"ํ˜„์žฌ ํ•ฉ๊ณ„: {total}")   # ๋””๋ฒ„๊น…์šฉ ์ถœ๋ ฅ
    return total

# ์‹คํ–‰
items = [1, 2, 3, 4, 5]
result = calculate_total(items)
print(f"์ตœ์ข… ๊ฒฐ๊ณผ: {result}")

โœ… ํŠน์ง•:

  • ๊ฐ„๋‹จํ•œ ๊ตฌํ˜„
  • ์ฆ‰๊ฐ์ ์ธ ํ™•์ธ
  • ์ฝ”๋“œ ํ๋ฆ„ ์ถ”์ 

โœ… ๊ณ ๊ธ‰ print ๋””๋ฒ„๊น… ๊ธฐ๋ฒ•:

  • ๊ตฌ๋ถ„์ž ์‚ฌ์šฉ: print("===DEBUG===", var)
  • ์กฐ๊ฑด๋ถ€ ๋””๋ฒ„๊น…: if debug_mode: print(var)
  • f-string ํ‘œํ˜„์‹: print(f"{var=}") (Python 3.8+)
  • ์ปจํ…์ŠคํŠธ ์ถ”๊ฐ€: print(f"[ํ•จ์ˆ˜: {func_name}] ๊ฐ’: {value}")


2๏ธโƒฃ pdb ์‚ฌ์šฉํ•˜๊ธฐ

Python ๋‚ด์žฅ ๋””๋ฒ„๊ฑฐ์ธ pdb๋ฅผ ํ™œ์šฉํ•œ ๋Œ€ํ™”ํ˜• ๋””๋ฒ„๊น… ๋ฐฉ๋ฒ•์ด๋‹ค.

import pdb

def complex_function(data):
    result = []
    for item in data:
        # ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ ์„ค์ •
        pdb.set_trace()  # Python 3.7 ์ดํ•˜
        # breakpoint()  # Python 3.7 ์ด์ƒ
        processed = item * 2
        result.append(processed)
    return result

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

โœ… ํŠน์ง•:

  • ๋Œ€ํ™”ํ˜• ๋””๋ฒ„๊น…
  • ์ƒํƒœ ๊ฒ€์‚ฌ
  • ๋‹จ๊ณ„๋ณ„ ์‹คํ–‰

โœ… ๊ณ ๊ธ‰ pdb ๊ธฐ๋Šฅ:

  • ์กฐ๊ฑด๋ถ€ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ: pdb.set_trace() if condition else None
  • ํฌ์ŠคํŠธ๋ชจํ…œ ๋””๋ฒ„๊น…: python -m pdb your_script.py
  • ๋ช…๋ น์–ด ๋ฐ˜๋ณต: Enter ํ‚ค๋กœ ๋งˆ์ง€๋ง‰ ๋ช…๋ น์–ด ๋ฐ˜๋ณต
  • ํ‘œํ˜„์‹ ํ‰๊ฐ€: !expression์œผ๋กœ ํ‘œํ˜„์‹ ์ง์ ‘ ์‹คํ–‰
  • ์ฝ”๋“œ ์‹คํ–‰: ! ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Python ๋ช…๋ น ์‹คํ–‰


3๏ธโƒฃ IDE ๋””๋ฒ„๊ฑฐ ์‚ฌ์šฉ

ํ†ตํ•ฉ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ(IDE)์—์„œ ์ œ๊ณตํ•˜๋Š” ๋””๋ฒ„๊น… ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def get_info(self):
        # IDE์—์„œ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ ์„ค์ •
        info = f"์ด๋ฆ„: {self.name}, ๋‚˜์ด: {self.age}"
        processed_info = self.process_info(info)
        return processed_info
    
    def process_info(self, info):
        # ์—ฌ๊ธฐ์„œ๋„ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ ์„ค์ • ๊ฐ€๋Šฅ
        return info.upper()

โœ… ํŠน์ง•:

  • ์‹œ๊ฐ์  ๋””๋ฒ„๊น…
  • ๋ณ€์ˆ˜ ๊ฐ์‹œ
  • ์ฝœ์Šคํƒ ํ™•์ธ

โœ… ์ฃผ์š” IDE ๋””๋ฒ„๊น… ๊ธฐ๋Šฅ:

  • VS Code: ์กฐ๊ฑด๋ถ€ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ, ๋ณ€์ˆ˜ ๊ฐ์‹œ, ํ˜ธ์ถœ ์Šคํƒ ๋ณด๊ธฐ
  • PyCharm: ์›๊ฒฉ ๋””๋ฒ„๊น…, ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ๋””๋ฒ„๊น…, ํ‘œํ˜„์‹ ํ‰๊ฐ€
  • Jupyter Notebook: %debug ๋งค์ง ๋ช…๋ น์–ด๋กœ ์˜ˆ์™ธ ๋ฐœ์ƒ ์ง€์  ๋””๋ฒ„๊น…
  • Spyder: ๋ณ€์ˆ˜ ํƒ์ƒ‰๊ธฐ, ์ง์ ‘ ์‹คํ–‰ ์ฝ˜์†”


4๏ธโƒฃ logging์„ ํ†ตํ•œ ๋””๋ฒ„๊น…

๋กœ๊น… ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฒด๊ณ„์ ์œผ๋กœ ๋””๋ฒ„๊น… ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

import logging

# ๋กœ๊น… ์„ค์ •
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='debug.log'
)

logger = logging.getLogger(__name__)

def process_data(data):
    logger.debug(f"๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ์ž‘: {data}")
    try:
        result = data * 2
        logger.info(f"๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: {result}")
        return result
    except Exception as e:
        logger.error(f"์—๋Ÿฌ ๋ฐœ์ƒ: {str(e)}")
        raise

โœ… ํŠน์ง•:

  • ์˜๊ตฌ์ ์ธ ๊ธฐ๋ก
  • ๋ ˆ๋ฒจ๋ณ„ ๋กœ๊น…
  • ํŒŒ์ผ ์ถœ๋ ฅ ์ง€์›

โœ… ๊ณ ๊ธ‰ ๋กœ๊น… ๊ธฐ๋ฒ•:

  • ๋กœ๊ฑฐ ๊ณ„์ธต ๊ตฌ์กฐ ํ™œ์šฉ
  • ์—ฌ๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ์‚ฌ์šฉ (ํŒŒ์ผ, ์ฝ˜์†”, ์ด๋ฉ”์ผ)
  • ๋กœ๊ทธ ํฌ๋งท ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
  • ๋กœ๊ทธ ํšŒ์ „(rotation) ์„ค์ •
  • ์ปจํ…์ŠคํŠธ ์ •๋ณด ํฌํ•จ
# ๊ณ ๊ธ‰ ๋กœ๊น… ์„ค์ • ์˜ˆ์‹œ
import logging
from logging.handlers import RotatingFileHandler
import traceback

# ๋กœ๊ฑฐ ์ƒ์„ฑ
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)

# ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ (ํฌ๊ธฐ ๊ธฐ๋ฐ˜ ๋กœํ…Œ์ด์…˜)
file_handler = RotatingFileHandler('app.log', maxBytes=1024*1024, backupCount=5)
file_handler.setLevel(logging.INFO)

# ์ฝ˜์†” ํ•ธ๋“ค๋Ÿฌ
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

# ํฌ๋งท ์ง€์ •
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# ๋กœ๊น… ์‚ฌ์šฉ ์˜ˆ
def complex_operation(x, y):
    logger.debug(f"complex_operation ์‹œ์ž‘: x={x}, y={y}")
    try:
        result = x / y
        logger.info(f"๊ณ„์‚ฐ ๊ฒฐ๊ณผ: {result}")
        return result
    except Exception as e:
        logger.error(f"์—๋Ÿฌ ๋ฐœ์ƒ: {str(e)}\n{traceback.format_exc()}")
        raise


5๏ธโƒฃ ๋””๋ฒ„๊น… ๋„๊ตฌ๋“ค

๋‹ค์–‘ํ•œ ๋””๋ฒ„๊น… ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํšจ์œจ์ ์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

# Python Debugger (ipdb)
"""
pip install ipdb
import ipdb; ipdb.set_trace()
"""

# pudb (์‹œ๊ฐ์  ๋””๋ฒ„๊ฑฐ)
"""
pip install pudb
import pudb; pudb.set_trace()
"""

# ๋ณ€์ˆ˜ ๊ฒ€์‚ฌ
from pprint import pprint

def inspect_object(obj):
    print("\n== ๊ฐ์ฒด ์ •๋ณด ==")
    pprint(vars(obj))
    print("\n== ํƒ€์ž… ์ •๋ณด ==")
    pprint(dir(obj))

โœ… ํŠน์ง•:

  • ๋‹ค์–‘ํ•œ ๋„๊ตฌ ์ง€์›
  • ์ƒ์„ธํ•œ ๊ฐ์ฒด ๊ฒ€์‚ฌ
  • ์‹œ๊ฐํ™” ๊ธฐ๋Šฅ

โœ… ์ถ”๊ฐ€ ๋””๋ฒ„๊น… ๋„๊ตฌ:

  • icecream: from icecream import ic; ic(variable) ํ˜•ํƒœ๋กœ ๊ฐ„ํŽธํ•œ ๋””๋ฒ„๊น… ์ถœ๋ ฅ
  • pytest: ํ…Œ์ŠคํŠธ ๊ธฐ๋ฐ˜ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ
  • trace: ์ฝ”๋“œ ์‹คํ–‰ ์ถ”์  ๋ฐ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ถ„์„
  • memory-profiler: ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ถ„์„
  • line_profiler: ๋ผ์ธ๋ณ„ ์‹คํ–‰ ์‹œ๊ฐ„ ๋ถ„์„


6๏ธโƒฃ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ๋””๋ฒ„๊น…

์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ ํšจ๊ณผ์ ์ธ ๋””๋ฒ„๊น… ์ „๋žต์ด๋‹ค.

def robust_function(data):
    try:
        result = process_data(data)
        return result
    except ValueError as e:
        print(f"๊ฐ’ ์˜ค๋ฅ˜: {str(e)}")
        # ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์ถ”๊ฐ€ ์ •๋ณด
        print(f"๋ฌธ์ œ ๋ฐ์ดํ„ฐ: {data}")
        return None
    except TypeError as e:
        print(f"ํƒ€์ž… ์˜ค๋ฅ˜: {str(e)}")
        # ํƒ€์ž… ์ •๋ณด ์ถœ๋ ฅ
        print(f"๋ฐ์ดํ„ฐ ํƒ€์ž…: {type(data)}")
        return None
    except Exception as e:
        # ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ์˜ ์ „์ฒด ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ์ถœ๋ ฅ
        import traceback
        print(f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {str(e)}")
        traceback.print_exc()
        return None

โœ… ํšจ๊ณผ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋””๋ฒ„๊น… ์ „๋žต:

  • ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํฌ์ฐฉ
  • ์ปจํ…์ŠคํŠธ ์ •๋ณด ๋กœ๊น…
  • ๋ณต๊ตฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๊ตฌํ˜„
  • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๋ถ„์„
  • ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ํ™œ์šฉ
# ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ์˜ˆ์‹œ
class DataValidationError(Exception):
    def __init__(self, message, invalid_fields=None):
        super().__init__(message)
        self.invalid_fields = invalid_fields or []
        self.timestamp = datetime.now()
    
    def __str__(self):
        return f"{super().__str__()} (์ž˜๋ชป๋œ ํ•„๋“œ: {self.invalid_fields}, ์‹œ๊ฐ„: {self.timestamp})"

# ์‚ฌ์šฉ ์˜ˆ์‹œ
def validate_user_data(user_data):
    invalid_fields = []
    
    if not isinstance(user_data.get('age'), int):
        invalid_fields.append('age')
    
    if not isinstance(user_data.get('name'), str):
        invalid_fields.append('name')
    
    if invalid_fields:
        raise DataValidationError("์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ", invalid_fields)
    
    return True


7๏ธโƒฃ ์„ฑ๋Šฅ ๋””๋ฒ„๊น…

์ฝ”๋“œ์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ฐพ๊ณ  ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋””๋ฒ„๊น… ๊ธฐ๋ฒ•์ด๋‹ค.

# ์‹œ๊ฐ„ ์ธก์ •
import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} ์‹คํ–‰ ์‹œ๊ฐ„: {end_time - start_time:.6f}์ดˆ")
        return result
    return wrapper

@measure_time
def slow_function(size):
    result = []
    for i in range(size):
        result.append(i ** 2)
    return result

# ํ”„๋กœํŒŒ์ผ๋ง
import cProfile

def profile_function(func, *args, **kwargs):
    profiler = cProfile.Profile()
    profiler.enable()
    result = func(*args, **kwargs)
    profiler.disable()
    profiler.print_stats(sort='cumtime')
    return result

# ์‚ฌ์šฉ ์˜ˆ์‹œ
profile_function(slow_function, 10000)

โœ… ์„ฑ๋Šฅ ๋””๋ฒ„๊น… ๋„๊ตฌ:

  • cProfile: ํ•จ์ˆ˜ ํ˜ธ์ถœ ํ†ต๊ณ„
  • timeit: ์ฝ”๋“œ ๋ธ”๋ก ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
  • line_profiler: ๋ผ์ธ๋ณ„ ์‹คํ–‰ ์‹œ๊ฐ„ ๋ถ„์„
  • memory_profiler: ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ถ„์„
  • objgraph: ๊ฐ์ฒด ์ฐธ์กฐ ์‹œ๊ฐํ™”
# ๊ณ ๊ธ‰ ์‹œ๊ฐ„ ์ธก์ • ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €
import time
from contextlib import contextmanager

@contextmanager
def timer(name="์ž‘์—…"):
    start_time = time.time()
    yield
    elapsed = time.time() - start_time
    print(f"{name} ์™„๋ฃŒ: {elapsed:.6f}์ดˆ")

# ์‚ฌ์šฉ ์˜ˆ์‹œ
with timer("๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ"):
    result = process_large_dataset()


8๏ธโƒฃ ๋ณ‘๋ ฌ ๋ฐ ๋น„๋™๊ธฐ ์ฝ”๋“œ ๋””๋ฒ„๊น…

๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ, ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ, ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ๋””๋ฒ„๊น… ๊ธฐ๋ฒ•์ด๋‹ค.

import threading
import logging

# ์Šค๋ ˆ๋“œ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ๋กœ๊น… ์„ค์ •
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(threadName)s - %(message)s'
)

def thread_function(name, data):
    logging.debug(f"์Šค๋ ˆ๋“œ {name} ์‹œ์ž‘")
    try:
        # ์ž‘์—… ์ˆ˜ํ–‰
        result = process_data(data)
        logging.debug(f"์Šค๋ ˆ๋“œ {name} ๊ฒฐ๊ณผ: {result}")
    except Exception as e:
        logging.error(f"์Šค๋ ˆ๋“œ {name} ์˜ค๋ฅ˜: {str(e)}")
    finally:
        logging.debug(f"์Šค๋ ˆ๋“œ {name} ์ข…๋ฃŒ")

# ๋น„๋™๊ธฐ ์ฝ”๋“œ ๋””๋ฒ„๊น…
import asyncio

async def async_function(name, data):
    print(f"๋น„๋™๊ธฐ ํ•จ์ˆ˜ {name} ์‹œ์ž‘")
    try:
        # ๋น„๋™๊ธฐ ์ž‘์—… ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        await asyncio.sleep(1)
        result = data * 2
        print(f"๋น„๋™๊ธฐ ํ•จ์ˆ˜ {name} ๊ฒฐ๊ณผ: {result}")
        return result
    except Exception as e:
        print(f"๋น„๋™๊ธฐ ํ•จ์ˆ˜ {name} ์˜ค๋ฅ˜: {str(e)}")
        raise

โœ… ๋ณ‘๋ ฌ ์ฝ”๋“œ ๋””๋ฒ„๊น… ์ „๋žต:

  • ์Šค๋ ˆ๋“œ๋ณ„ ๋กœ๊น… ๊ตฌ๋ถ„
  • ๊ฒฝ์Ÿ ์กฐ๊ฑด ์‹๋ณ„
  • ๋ฐ๋“œ๋ฝ ๊ฐ์ง€
  • ๊ณต์œ  ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์Šค๋ ˆ๋“œ ๋คํ”„ ๋ถ„์„

โœ… ๋น„๋™๊ธฐ ์ฝ”๋“œ ๋””๋ฒ„๊น… ์ „๋žต:

  • ์ด๋ฒคํŠธ ๋ฃจํ”„ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์ฝ”๋ฃจํ‹ด ์ถ”์ 
  • ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ ๋กœ๊น…
  • ํƒœ์Šคํฌ ์ƒํƒœ ๊ฒ€์‚ฌ
  • ์˜ˆ์™ธ ์ „ํŒŒ ์ถ”์ 


9๏ธโƒฃ ์›๊ฒฉ ๋ฐ ํ”„๋กœ๋•์…˜ ๋””๋ฒ„๊น…

์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ์˜ ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊น…ํ•˜๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.

# ๋กœ๊น… ๊ธฐ๋ฐ˜ ์›๊ฒฉ ๋””๋ฒ„๊น…
import logging
import json
import socket

class ContextFilter(logging.Filter):
    def __init__(self):
        self.hostname = socket.gethostname()
        
    def filter(self, record):
        record.hostname = self.hostname
        return True

# ์›๊ฒฉ ๋กœ๊น… ์„ค์ •
def setup_remote_logging():
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    # ์ปจํ…์ŠคํŠธ ํ•„ํ„ฐ ์ถ”๊ฐ€
    context_filter = ContextFilter()
    logger.addFilter(context_filter)
    
    # JSON ํฌ๋งทํ„ฐ
    class JsonFormatter(logging.Formatter):
        def format(self, record):
            log_record = {
                'time': self.formatTime(record),
                'name': record.name,
                'level': record.levelname,
                'hostname': getattr(record, 'hostname', ''),
                'message': record.getMessage(),
                'path': record.pathname,
                'line': record.lineno
            }
            if record.exc_info:
                log_record['exception'] = self.formatException(record.exc_info)
            return json.dumps(log_record)
    
    # ํ•ธ๋“ค๋Ÿฌ ์„ค์ •
    handler = logging.FileHandler('app.log')
    handler.setFormatter(JsonFormatter())
    logger.addHandler(handler)
    
    return logger

โœ… ํ”„๋กœ๋•์…˜ ๋””๋ฒ„๊น… ์ „๋žต:

  • ๊ตฌ์กฐํ™”๋œ ๋กœ๊น…
  • ๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ
  • ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ ํ™œ์šฉ
  • ์˜ค๋ฅ˜ ๋ณด๊ณ  ์‹œ์Šคํ…œ ๊ตฌ์ถ•
  • ๋ณต๊ตฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๊ตฌํ˜„
  • A/B ํ…Œ์ŠคํŠธ ๋ฐ ์นด๋‚˜๋ฆฌ ๋ฐฐํฌ


๐Ÿ”Ÿ ๋””๋ฒ„๊น… ๋ชจ๋ฒ” ์‚ฌ๋ก€

ํšจ์œจ์ ์ธ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์ฃผ์š” ํŒ๊ณผ ๋ชจ๋ฒ” ์‚ฌ๋ก€์ด๋‹ค.

โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€:

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

โœ… ๋””๋ฒ„๊น… ์ ˆ์ฐจ:

  1. ๋ฌธ์ œ ์žฌํ˜„: ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ •ํ™•ํ•œ ์กฐ๊ฑด ํŒŒ์•…
  2. ์ฆ์ƒ ๋ถ„์„: ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€, ๋กœ๊ทธ, ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๊ฒ€ํ† 
  3. ๊ฐ€์„ค ์ˆ˜๋ฆฝ: ๊ฐ€๋Šฅํ•œ ์›์ธ์— ๋Œ€ํ•œ ๊ฐ€์„ค ์„ธ์šฐ๊ธฐ
  4. ๊ฐ€์„ค ๊ฒ€์ฆ: ๋””๋ฒ„๊น… ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์„ค ํ…Œ์ŠคํŠธ
  5. ํ•ด๊ฒฐ์ฑ… ๊ตฌํ˜„: ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐ ์ˆ˜์ •
  6. ํ…Œ์ŠคํŠธ: ์ˆ˜์ • ์‚ฌํ•ญ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋Š”์ง€ ํ™•์ธ
  7. ๋ฌธ์„œํ™”: ๋ฐœ๊ฒฌํ•œ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ๊ธฐ๋ก


1๏ธโƒฃ1๏ธโƒฃ ๊ณ ๊ธ‰ ๋””๋ฒ„๊น… ๊ธฐ๋ฒ•

๋ณต์žกํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ๊ธ‰ ๋””๋ฒ„๊น… ๊ธฐ๋ฒ•์ด๋‹ค.

# ๋ชจ์˜ ๊ฐ์ฒด(Mock)๋ฅผ ์‚ฌ์šฉํ•œ ๋””๋ฒ„๊น…
from unittest.mock import patch, MagicMock

def test_with_mock():
    # ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋ชจ์˜ ๊ฐ์ฒด๋กœ ๋Œ€์ฒด
    with patch('module.api_call') as mock_api:
        # ๋ชจ์˜ ๋ฐ˜ํ™˜๊ฐ’ ์„ค์ •
        mock_api.return_value = {'status': 'success', 'data': [1, 2, 3]}
        
        # ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ
        result = process_api_data()
        
        # ํ˜ธ์ถœ ๊ฒ€์ฆ
        mock_api.assert_called_once()
        print(f"API ํ˜ธ์ถœ ์ธ์ž: {mock_api.call_args}")
        
        # ๊ฒฐ๊ณผ ๊ฒ€์ฆ
        assert result == [2, 4, 6]

# ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ
def debug_function_calls(func):
    def wrapper(*args, **kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
        signature = ", ".join(args_repr + kwargs_repr)
        print(f"ํ•จ์ˆ˜ ํ˜ธ์ถœ: {func.__name__}({signature})")
        try:
            result = func(*args, **kwargs)
            print(f"ํ•จ์ˆ˜ ๋ฐ˜ํ™˜: {func.__name__} -> {result!r}")
            return result
        except Exception as e:
            print(f"ํ•จ์ˆ˜ ์˜ˆ์™ธ: {func.__name__} -> {type(e).__name__}: {e}")
            raise
    return wrapper

@debug_function_calls
def complex_calculation(x, y, z=1):
    return (x * y) / z

โœ… ๊ณ ๊ธ‰ ๋””๋ฒ„๊น… ์ „๋žต:

  • ์ด์ง„ ๊ฒ€์ƒ‰ ๋””๋ฒ„๊น…: ์˜ค๋ฅ˜ ์›์ธ์„ ๋น ๋ฅด๊ฒŒ ์ฐพ๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ์ ˆ๋ฐ˜์”ฉ ๋‚˜๋ˆ„์–ด ๊ฒ€์‚ฌ
  • ๋Ÿฌ๋ฒ„๋• ๋””๋ฒ„๊น…: ๋ฌธ์ œ๋ฅผ ๋‹ค๋ฅธ ์‚ฌ๋žŒ(๋˜๋Š” ๋ฌผ์ฒด)์—๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด์„œ ํ•ด๊ฒฐ์ฑ… ๋ฐœ๊ฒฌ
  • ๋™์  ์ฝ”๋“œ ์‚ฝ์ž…: ๋Ÿฐํƒ€์ž„์— ์ฝ”๋“œ ์ˆ˜์ •ํ•˜์—ฌ ๋””๋ฒ„๊น…
  • ํ–‰๋™ ์ค‘์‹ฌ ๋””๋ฒ„๊น…: ์ฝ”๋“œ๊ฐ€ "๋ฌด์—‡์„ ํ•˜๋Š”์ง€"๊ฐ€ ์•„๋‹Œ "์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€" ์ง‘์ค‘
  • ๊ฑฐ๊พธ๋กœ ๋””๋ฒ„๊น…: ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์ง€์ ์—์„œ ์—ญ๋ฐฉํ–ฅ์œผ๋กœ ์ถ”์ 
  • ๋น„๊ต ๋””๋ฒ„๊น…: ์ž‘๋™ํ•˜๋Š” ๋ฒ„์ „๊ณผ ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š” ๋ฒ„์ „ ๋น„๊ต


1๏ธโƒฃ2๏ธโƒฃ ํŒŒ์ด์ฌ ๋ฒ„์ „๋ณ„ ๋””๋ฒ„๊น… ๊ธฐ๋Šฅ

ํŒŒ์ด์ฌ ๋ฒ„์ „์— ๋”ฐ๋ฅธ ์ƒˆ๋กœ์šด ๋””๋ฒ„๊น… ๊ธฐ๋Šฅ๊ณผ ๋„๊ตฌ๋“ค์ด๋‹ค.

โœ… Python 3.7+:

  • breakpoint() ๋‚ด์žฅ ํ•จ์ˆ˜: pdb.set_trace()์˜ ๋Œ€์ฒด
  • PYTHONBREAKPOINT ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋””๋ฒ„๊ฑฐ ์ „ํ™˜

โœ… Python 3.8+:

  • ๋””๋ฒ„๊ทธ์šฉ f-strings: f"{variable=}"๋กœ ๋ณ€์ˆ˜๋ช…๊ณผ ๊ฐ’ ํ•จ๊ป˜ ์ถœ๋ ฅ
  • ํ–ฅ์ƒ๋œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค

โœ… Python 3.9+:<

  • ํ–ฅ์ƒ๋œ ํƒ€์ž… ํžŒํŠธ ์ง€์›์œผ๋กœ ํƒ€์ž… ๊ด€๋ จ ๋””๋ฒ„๊น… ๊ฐœ์„ 
  • ๋ณด๋‹ค ์ •ํ™•ํ•œ ์ค„ ๋ฒˆํ˜ธ ์ถ”์ 

โœ… Python 3.10+:

  • ๋” ์ž์„ธํ•œ ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€์™€ ์ œ์•ˆ
  • ๊ตฌ์กฐ์  ํŒจํ„ด ๋งค์นญ์„ ํ™œ์šฉํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

โœ… Python 3.11+:

  • ํ–ฅ์ƒ๋œ ํŠธ๋ ˆ์ด์Šค๋ฐฑ: ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์œ„์น˜๋ฅผ ๋” ์ •ํ™•ํžˆ ํ‘œ์‹œ
  • ์˜ˆ์™ธ ๋…ธํŠธ: add_note() ๋ฉ”์„œ๋“œ๋กœ ์˜ˆ์™ธ์— ์ปจํ…์ŠคํŠธ ์ •๋ณด ์ถ”๊ฐ€
  • ๋””๋ฒ„๊น… ์„ฑ๋Šฅ ํ–ฅ์ƒ


1๏ธโƒฃ3๏ธโƒฃ ๋””๋ฒ„๊น… ์—ฐ์Šต

์‹ค์ œ ๋””๋ฒ„๊น… ์—ฐ์Šต ์‚ฌ๋ก€๋ฅผ ํ†ตํ•ด ๊ธฐ์ˆ ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

โœ… ์—ฐ์Šต์šฉ ๋ฒ„๊ทธ ์œ ํ˜•:

  • ๋…ผ๋ฆฌ ์˜ค๋ฅ˜: ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋‚˜ ์กฐ๊ฑด๋ฌธ ์˜ค๋ฅ˜
  • ํƒ€์ž… ์˜ค๋ฅ˜: ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํƒ€์ž… ๋ณ€ํ™˜
  • ๋ฒ”์œ„ ์˜ค๋ฅ˜: ๋ฐฐ์—ด ์ธ๋ฑ์Šค ์ดˆ๊ณผ
  • ๊ฒฝ์Ÿ ์กฐ๊ฑด: ๋ณ‘๋ ฌ ์ฝ”๋“œ์˜ ํƒ€์ด๋ฐ ๋ฌธ์ œ
  • ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜: ์ž์›์ด ํ•ด์ œ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ
  • ๋ฌดํ•œ ๋ฃจํ”„: ์ข…๋ฃŒ ์กฐ๊ฑด์ด ์—†๋Š” ๋ฐ˜๋ณต๋ฌธ

โœ… ์—ฐ์Šต ํ”„๋กœ์ ํŠธ:

  • ์˜๋„์ ์œผ๋กœ ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  ํ•ด๊ฒฐํ•˜๊ธฐ
  • ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ ์ด์Šˆ ํ•ด๊ฒฐ์— ์ฐธ์—ฌํ•˜๊ธฐ
  • ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ์ฝ”๋“œ ๋””๋ฒ„๊น…ํ•˜๊ธฐ
  • ๋””๋ฒ„๊น… ๋„๊ตฌ์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ํ•™์Šตํ•˜๊ธฐ
  • ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ํ˜„์ƒ ์ฐพ๊ณ  ์ตœ์ ํ™”ํ•˜๊ธฐ


์š”์•ฝ

ํšจ๊ณผ์ ์ธ ๋””๋ฒ„๊น…์€ ํŒŒ์ด์ฌ ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ ๊ธฐ์ˆ ์ด๋‹ค. ๊ธฐ๋ณธ์ ์ธ print ๋ฌธ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ํ”„๋กœํŒŒ์ผ๋ง ๋„๊ตฌ๊นŒ์ง€ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์ƒํ™ฉ์— ๋งž๊ฒŒ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค. ๋””๋ฒ„๊น…์€ ๋‹จ์ˆœํžˆ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ฝ”๋“œ์˜ ์ž‘๋™ ๋ฐฉ์‹์„ ์ดํ•ดํ•˜๊ณ  ๊ฐœ์„ ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค. ์ฒด๊ณ„์ ์ธ ์ ‘๊ทผ๋ฒ•๊ณผ ์ ์ ˆํ•œ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ๋ฌธ์ œ๋„ ํšจ์œจ์ ์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฌธ์ œ ํ•ด๊ฒฐ ๊ณผ์ •์—์„œ ๋ฐฐ์šด ๊ตํ›ˆ์„ ๋ฌธ์„œํ™”ํ•˜๊ณ  ํŒ€๊ณผ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜๋‹ค. ๋””๋ฒ„๊น…์€ ๊ธฐ์ˆ ์ ์ธ ์ธก๋ฉด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ธ๋‚ด์‹ฌ๊ณผ ๋…ผ๋ฆฌ์  ์‚ฌ๊ณ ๋ฅผ ์š”๊ตฌํ•˜๋Š” ๊ธฐ์ˆ ์ด๋ฏ€๋กœ, ์ง€์†์ ์ธ ์—ฐ์Šต๊ณผ ํ•™์Šต์„ ํ†ตํ•ด ํ–ฅ์ƒ์‹œ์ผœ์•ผ ํ•œ๋‹ค.


โš ๏ธ **GitHub.com Fallback** โš ๏ธ