Ver3.0 ๐ Python ๋ก๊น ๊ณผ ๋๋ฒ๊น ์ ์ํ ํจ๊ณผ์ ์ธ ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ฑ

๐ Python ๋ก๊น ๊ณผ ๋๋ฒ๊น ์ ์ํ ํจ๊ณผ์ ์ธ ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ฑ
์ฌ์ค ํ๋ก๊ทธ๋๋ฐ์์ ๋ฒ๊ทธ๋ ํผํ ์ ์๋ ์น๊ตฌ์ผ. ์๋, ์น๊ตฌ๋ผ๊ธฐ๋ณด๋จ ๊ท์ฐฎ์ ๋๊ฑฐ์ธ? ๊ทธ๋์ ์ฐ๋ฆฌ๋ ์ด ๋ ์๋ค์ ํจ๊ณผ์ ์ผ๋ก ์ฐพ์๋ด๊ณ ์ถ์ ํ ์ ์๋ ์์คํ ์ด ํ์ํด. ๋ง์น ํ์ ์ด ๋ฒ์ธ์ ์ฐพ๋ฏ์ด ๋ง์ด์ผ! ๐ต๏ธ
์ด ๊ธ์์๋ Python์ ๋ก๊น ์์คํ ๋ถํฐ ๋ค์ํ ๋๋ฒ๊น ๋๊ตฌ, ๊ทธ๋ฆฌ๊ณ ์ค์ ์์ ๋ฐ๋ก ์จ๋จน์ ์ ์๋ ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ฑ ๋ฐฉ๋ฒ๊น์ง ๋ชจ๋ ๋ค๋ค๋ณผ ๊ฑฐ์ผ. ์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ๊ด๋ จ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋ ๋ฐฐ์ฐ๊ณ ์๋ค๋ฉด, ์ด ๋ด์ฉ์ด ์ ๋ง ์ ์ฉํ ๊ฑฐ๋ผ๊ณ ์ฅ๋ดํด! ๐ช
๐ฏ ์ ๋ก๊น ๊ณผ ๋๋ฒ๊น ์ด ์ค์ํ ๊น?
๊ฐ๋ฐ์์ ํ์ค ์ฒดํฌ โ
์ฝ๋๋ฅผ ์์ฑํ๋ ์๊ฐ๋ณด๋ค ๋๋ฒ๊น ํ๋ ์๊ฐ์ด ๋ ๊ธธ๋ค๋ ํต๊ณ๊ฐ ์์ด. ์ค์ ๋ก ๊ฐ๋ฐ ์๊ฐ์ ์ฝ 50~70%๊ฐ ๋ฒ๊ทธ๋ฅผ ์ฐพ๊ณ ์์ ํ๋ ๋ฐ ์ฐ์ธ๋ค๊ณ ํด. ๋๋์ง? ๐ฑ๊ทธ๋์ ํจ๊ณผ์ ์ธ ๋ก๊น ๊ณผ ๋๋ฒ๊น ํ๊ฒฝ์ ๊ตฌ์ถํ๋ ๊ฑด ๋จ์ํ '์์ผ๋ฉด ์ข์ ๊ฒ'์ด ์๋๋ผ '๋ฐ๋์ ํ์ํ ๊ฒ'์ด์ผ. ๋ง์น ์๋ฆฌํ ๋ ์ข์ ์นผ์ด ํ์ํ ๊ฒ์ฒ๋ผ ๋ง์ด์ง!
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ๊ธฐ๋ก์ ๋จ๊ธฐ๋ฉด, ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๋ "์ธ์ , ์ด๋์, ๋ฌด์์ด" ์๋ชป๋๋์ง ๋น ๋ฅด๊ฒ ํ์ ํ ์ ์์ด.
2. ํ๋ก๋์ ํ๊ฒฝ์ ๋
๊ฐ๋ฐ ํ๊ฒฝ์์๋ ๊ด์ฐฎ์๋๋ฐ ์ค์ ์๋น์ค์์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์. ๋ก๊ทธ๊ฐ ์๋ค๋ฉด? ์์ ์ํ ์์์ ํค๋งค๋ ๊ฑฐ์ง. ๐
3. ์ฑ๋ฅ ๋ถ์์ ๊ธฐ์ด
์ด๋ค ํจ์๊ฐ ๋๋ฆฐ์ง, ์ด๋์ ๋ณ๋ชฉ์ด ๋ฐ์ํ๋์ง ๋ก๊ทธ๋ฅผ ํตํด ๋ถ์ํ ์ ์์ด.
4. ๋ณด์ ๊ฐ์ฌ
๋๊ฐ, ์ธ์ , ๋ฌด์์ ํ๋์ง ๊ธฐ๋กํ๋ฉด ๋ณด์ ์ฌ๊ณ ๋ฐ์ ์ ์ถ์ ์ด ๊ฐ๋ฅํด.
๐ Python ๋ก๊น ์ ๊ธฐ์ด: logging ๋ชจ๋ ์์ ์ ๋ณต
Python์ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ logging ๋ชจ๋์ ์ ๋ง ๊ฐ๋ ฅํ ๋๊ตฌ์ผ. ๋ง์ ์ด๋ณด ๊ฐ๋ฐ์๋ค์ด print() ๋ฌธ์ผ๋ก ๋๋ฒ๊น ํ๋๋ฐ, ์ด๊ฑด ๋ง์น ์ค๋งํธํฐ ์๋์ ์์๋ฅผ ์ฐ๋ ๊ฒ๊ณผ ๊ฐ์! ๐๐ ๋ก๊น ๋ ๋ฒจ์ ์ดํด
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("์ ํ๋ฆฌ์ผ์ด์
์์!")
๋ก๊ทธ ํ์ผ์ด ๋๋ฌด ์ปค์ง๋ ๊ฑธ ๋ฐฉ์งํด. 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๋ 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
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๋ ์ ๋ง ๊ฐ๋ ฅํ ๋๋ฒ๊น ๊ธฐ๋ฅ์ ์ ๊ณตํด. 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"
]
}
]
}
1๏ธโฃ ์ฝ๋ ์ผ์ชฝ ์ฌ๋ฐฑ์ ํด๋ฆญํด์ ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์
2๏ธโฃ F5 ํค๋ฅผ ๋๋ฌ์ ๋๋ฒ๊น ์์
3๏ธโฃ ๋ณ์ ํจ๋์์ ๋ชจ๋ ๋ณ์ ๊ฐ ํ์ธ
4๏ธโฃ ์กฐ์ฌ์(Watch)์ ํํ์ ์ถ๊ฐํด์ ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
5๏ธโฃ ์ฝ ์คํ์ผ๋ก ํจ์ ํธ์ถ ํ๋ฆ ํ์
๐ PyCharm ๋๋ฒ๊ฑฐ: ํ๋กํ์ ๋์ ์ ํ
PyCharm์ ์ ๋ฃ์ง๋ง ์ ๋ง ๊ฐ๋ ฅํ ๋๋ฒ๊น ๊ธฐ๋ฅ์ ์ ๊ณตํด. ํนํ ๋ณต์กํ ํ๋ก์ ํธ์์ ๋น์ ๋ฐํ์ง:ํน์ ์กฐ๊ฑด์ผ ๋๋ง ๋ฉ์ถ๊ฒ ํ ์ ์์ด. ์๋ฅผ ๋ค์ด "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'})
๐ญ ์ปจํ ์คํธ ๋งค๋์ ๋ฅผ ํ์ฉํ ๋ก๊น
ํน์ ์์ ์ ์์๊ณผ ๋์ ์๋์ผ๋ก ๋ก๊น ํ๊ณ ์ถ๋ค๋ฉด? ์ปจํ ์คํธ ๋งค๋์ ๋ฅผ ์ฌ์ฉํด๋ด: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 ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์์ฒญ๋ง๋ค ๊ณ ์ ํ 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๋ 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๋ฅผ ํธ์ถํ ๋๋ ์์ฒญ๊ณผ ์๋ต์ ๋ชจ๋ ๋ก๊น ํ๋ ๊ฒ ์ข์. ๋์ค์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ์ ํํ ๋ฌด์์ด ์๋ชป๋๋์ง ์ ์ ์๊ฑฐ๋ :
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๋ ๋ก๊ทธ ๋ถ์์ ํ์ค์ด์ผ. ์๋ฐฑ๋ง ๊ฐ์ ๋ก๊ทธ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ฒ์ํ๊ณ ์๊ฐํํ ์ ์์ด:
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๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก๊ทธ๋ฅผ ์บก์ฒํด. ํ ์คํธ๊ฐ ์คํจํ๋ฉด ๊ด๋ จ ๋ก๊ทธ๋ฅผ ๋ณด์ฌ์ฃผ์ง:
# 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
๐จ ๋๋ฒ๊น ์ํฌํ๋ก์ฐ
ํจ๊ณผ์ ์ธ ๋๋ฒ๊น ์ ์ฒด๊ณ์ ์ธ ์ ๊ทผ์ด ํ์ํด. ํ๋ก๋ค์ด ์ฌ์ฉํ๋ ์ํฌํ๋ก์ฐ๋ฅผ ์๋ ค์ค๊ฒ! ๐๐ ๋๋ฒ๊น ์ฒดํฌ๋ฆฌ์คํธ
๋ฒ๊ทธ๋ฅผ ์ผ๊ด๋๊ฒ ์ฌํํ ์ ์์ด์ผ ํด. ์ฌํ ๋ถ๊ฐ๋ฅํ ๋ฒ๊ทธ๋ ๊ณ ์น๊ธฐ ์ ๋ง ์ด๋ ค์.
2๋จ๊ณ: ๋ก๊ทธ ํ์ธ
์๋ฌ ๋ฉ์์ง, ์คํ ํธ๋ ์ด์ค, ๊ด๋ จ ๋ก๊ทธ๋ฅผ ๋ชจ๋ ํ์ธํด.
3๋จ๊ณ: ๊ฐ์ค ์๋ฆฝ
"์๋ง ์ด ๋ถ๋ถ์ด ๋ฌธ์ ์ผ ๊ฑฐ์ผ"๋ผ๋ ๊ฐ์ค์ ์ธ์.
4๋จ๊ณ: ๊ฒฉ๋ฆฌ
๋ฌธ์ ๊ฐ ๋๋ ์ฝ๋๋ฅผ ์ต์ํ์ผ๋ก ์ค์ฌ๋ด. ๋จ์ ํ ์คํธ๋ก ๋ง๋ค๋ฉด ๋ ์ข์.
5๋จ๊ณ: ๋๋ฒ๊ฑฐ ํ์ฉ
๋ธ๋ ์ดํฌํฌ์ธํธ๋ฅผ ์ค์ ํ๊ณ ๋ณ์ ๊ฐ์ ํ์ธํด.
6๋จ๊ณ: ์์ ๋ฐ ๊ฒ์ฆ
์์ ํ ๋ฐ๋์ ํ ์คํธ๋ก ๊ฒ์ฆํด. ํ ๋ฒ๊ทธ๋ฅผ ๊ณ ์น๋ค๊ฐ ๋ค๋ฅธ ๋ฒ๊ทธ๋ฅผ ๋ง๋ค ์ ์์ด!
๐ฌ ๋๋ฒ๊น ๋๊ตฌ ์กฐํฉ
โ print() ๋๋ logging.debug()
๋น ๋ฅด๊ฒ ํ์ธํ๊ณ ์ถ์ ๋ ์ต๊ณ ์ผ.
๋ก์ง ์ค๋ฅ
โ pdb ๋๋ IDE ๋๋ฒ๊ฑฐ
๋จ๊ณ๋ณ๋ก ์คํํ๋ฉด์ ๋ณ์ ๊ฐ์ ํ์ธํด.
์ฑ๋ฅ ๋ฌธ์
โ cProfile + snakeviz
์ด๋ ํจ์๊ฐ ๋๋ฆฐ์ง ์๊ฐ์ ์ผ๋ก ํ์ ํด.
๋ฉ๋ชจ๋ฆฌ ๋์
โ memory_profiler + objgraph
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ถ์ ํ๊ณ ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๋ถ์ํด.
๋์์ฑ ๋ฌธ์
โ threading ๋ชจ๋ + ๋ก๊น
์ค๋ ๋๋ณ๋ก ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ํ์ด๋ฐ์ ๋ถ์ํด.
ํ๋ก๋์ ๋ฒ๊ทธ
โ ๊ตฌ์กฐํ๋ ๋ก๊น + ๋ชจ๋ํฐ๋ง ๋๊ตฌ
์ค์๊ฐ์ผ๋ก ๋ก๊ทธ๋ฅผ ๋ถ์ํ๊ณ ํจํด์ ์ฐพ์.
๐ ์ฌ๋ฅ๋ท์์ ๋ฐฐ์ฐ๊ณ ๋๋๊ธฐ
์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด Python ๋ก๊น ๊ณผ ๋๋ฒ๊น ์ ๋ํด ๊ฝค ๋ง์ด ์๊ฒ ๋์ ๊ฑฐ์ผ! ๐ํ์ง๋ง ํ๋ก๊ทธ๋๋ฐ์ ํผ์ ๊ณต๋ถํ๋ ๊ฒ๋ณด๋ค ํจ๊ป ๋ฐฐ์ฐ๊ณ ๋๋๋ ๊ฒ ํจ์ฌ ํจ๊ณผ์ ์ด์ผ. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค๊ณผ ๊ฒฝํ์ ๊ณต์ ํ๊ณ , ์ค์ ํ๋ก์ ํธ์์ ๊ฒช์ ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋๋๋ฉด ์ ๋ง ๋ง์ด ์ฑ์ฅํ ์ ์์ด.
์ด๋ก ๋ง ์๋ ๊ฒ๊ณผ ์ค์ ๋ก ์ ์ฉํด๋ณธ ๊ฒ์ ์์ ํ ๋ฌ๋ผ. ์์ ํ๋ก์ ํธ๋ผ๋ ์ง์ ๋ง๋ค์ด๋ณด๋ฉด์ ๋ก๊น ๊ณผ ๋๋ฒ๊น ์ ์ ์ฉํด๋ด.
2. ์คํ์์ค ๊ธฐ์ฌ
์ ๋ช ํ Python ํ๋ก์ ํธ๋ค์ ์ฝ๋๋ฅผ ์ฝ์ด๋ด. ์ด๋ป๊ฒ ๋ก๊น ์ ๊ตฌ์ฑํ๋์ง, ์ด๋ค ๋๋ฒ๊น ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋์ง ๋ฐฐ์ธ ๊ฒ ์ ๋ง ๋ง์.
3. ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ
Stack Overflow, Reddit, ํ๊ตญ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์์ ๋ค๋ฅธ ์ฌ๋๋ค์ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ ๋ณด๋ฉด์ ๋ฐฐ์. ์ง์ ์ง๋ฌธํ๊ณ ๋ต๋ณํ๋ ๊ฒ๋ ์ข์ ๊ณต๋ถ์ผ.
4. ๋ธ๋ก๊ทธ ์์ฑ
๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด์ ๊ธ๋ก ์ฐ๋ฉด ์ดํด๊ฐ ๋ ๊น์ด์ ธ. ๋ค๋ฅธ ์ฌ๋์๊ฒ ์ค๋ช ํ ์ ์์ด์ผ ์ง์ง ์๋ ๊ฑฐ์ผ!
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
๋ก๊น ์ ์ค์์ฑ
โข ๋ฌธ์ ์ถ์ ๊ณผ ๋๋ฒ๊น ์ ์์์
โข ํ๋ก๋์ ํ๊ฒฝ์์์ ๋
โข ์ฑ๋ฅ ๋ถ์๊ณผ ๋ณด์ ๊ฐ์ฌ์ ๊ธฐ์ด
ํจ๊ณผ์ ์ธ ๋ก๊น ์ ๋ต
โข ์ ์ ํ ๋ก๊ทธ ๋ ๋ฒจ ์ฌ์ฉ
โข ๊ตฌ์กฐํ๋ ๋ก๊น (JSON)
โข ์ปจํ ์คํธ ์ ๋ณด ํฌํจ
โข ๋ฏผ๊ฐํ ์ ๋ณด ํํฐ๋ง
โข ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ์ฑ๋ฅ ์ต์ ํ
๋๋ฒ๊น ๋๊ตฌ ํ์ฉ
โข pdb/ipdb๋ก ๋ํํ ๋๋ฒ๊น
โข IDE ๋๋ฒ๊ฑฐ๋ก ์๊ฐ์ ๋ถ์
โข ํ๋กํ์ผ๋ง์ผ๋ก ์ฑ๋ฅ ์ต์ ํ
โข ์๊ฒฉ ๋๋ฒ๊น ์ผ๋ก ํ๋ก๋์ ๋ฌธ์ ํด๊ฒฐ
ํ๋ก๋์ ํ๊ฒฝ ๊ณ ๋ ค์ฌํญ
โข ๋ณด์์ ์ต์ฐ์ ์ผ๋ก
โข ์ฑ๋ฅ ์ํฅ ์ต์ํ
โข ๋ก๊ทธ ๋ถ์ ๋๊ตฌ ํ์ฉ
โข ์ค์๊ฐ ๋ชจ๋ํฐ๋ง๊ณผ ์๋ฆผ
์ฒ์์๋ ๋ณต์กํด ๋ณด์ผ ์ ์์ง๋ง, ํ๋์ฉ ์ ์ฉํด๋ณด๋ฉด์ ์ต์ํด์ง๋ฉด ๋ผ. ์์ ํ๋ก์ ํธ๋ถํฐ ์์ํด์ ์ ์ ๋ ์ ๊ตํ ๋ก๊น ์์คํ ์ ๊ตฌ์ถํด๋๊ฐ๋ ๊ฑฐ์ง.
๊ทธ๋ฆฌ๊ณ ๊ธฐ์ตํด! ์ข์ ๋ก๊ทธ๋ ๋ฏธ๋์ ๋น์ ์ ๊ตฌ์ํด์ค ๊ฑฐ์ผ. ์ง๊ธ ๊ท์ฐฎ๋๋ผ๋ ์ ๋๋ก ๋ ๋ก๊น ์์คํ ์ ๊ตฌ์ถํด๋๋ฉด, ๋์ค์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ์ ๋ง ๊ณ ๋ง์ํ ๊ฑฐ์ผ. ๐
ํ๋ณตํ ์ฝ๋ฉ ๋๊ธธ ๋ฐ๋ผ! ๋ฒ๊ทธ ์๋ ํ๋ฃจ๋ ์๊ฒ ์ง๋ง, ๋ฒ๊ทธ๋ฅผ ๋น ๋ฅด๊ฒ ์ก๋ ํ๋ฃจ๋ ๋ง๋ค ์ ์์ด! ๐โจ
ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์์ ์ฌ๋ฌ๋ถ์ ๊ธฐ๋ค๋ฆฝ๋๋ค.
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.