KR_Logging - somaz94/python-study GitHub Wiki
ํ์ด์ฌ์ ๋ก๊น
(logging)์ ์ ํ๋ฆฌ์ผ์ด์
์คํ ์ค์ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ฅผ ์ถ์ ํ๊ณ ๊ธฐ๋กํ๋ ์ค์ํ ๋ฉ์ปค๋์ฆ์ด๋ค. ํจ๊ณผ์ ์ธ ๋ก๊น
์ ๊ฐ๋ฐ, ๋๋ฒ๊น
, ์ด์ ๋จ๊ณ์์ ๋ฌธ์ ํด๊ฒฐ๊ณผ ์์คํ
๋ชจ๋ํฐ๋ง์ ์ํ ํ์์ ์ธ ๋๊ตฌ์ด๋ค.
๋ก๊น ์ ๋จ์ํ print ๋ฌธ๋ณด๋ค ๋ง์ ์ฅ์ ์ ์ ๊ณตํ๋ค:
- ๋ก๊ทธ ๋ ๋ฒจ์ ํตํ ์ค์๋ ๊ตฌ๋ถ
- ํ์ผ, ์ฝ์, ๋คํธ์ํฌ ๋ฑ ๋ค์ํ ์ถ๋ ฅ ๋์ ์ง์
- ํฌ๋งทํ ๊ณผ ํํฐ๋ง ๊ธฐ๋ฅ
- ๋ฉํฐ์ค๋ ๋/๋ฉํฐํ๋ก์ธ์ค ํ๊ฒฝ์์์ ์์ ์ฑ
- ๊ณ์ธต์ ๋ก๊ฑฐ ๊ตฌ์กฐ๋ฅผ ํตํ ์ ์ฐํ ๊ตฌ์ฑ
ํ์ด์ฌ์ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฌํจ๋ logging
๋ชจ๋์ ์ด๋ฌํ ๊ธฐ๋ฅ์ ๋ชจ๋ ์ ๊ณตํ๋ฉด์๋ ์ฌ์ฉํ๊ธฐ ์ฝ๊ฒ ์ค๊ณ๋์ด ์๋ค.
ํ์ด์ฌ์ logging ๋ชจ๋์ ์ฌ์ฉํ ๊ธฐ๋ณธ์ ์ธ ๋ก๊น
๋ฐฉ๋ฒ์ด๋ค.
import logging
# ๊ธฐ๋ณธ ๋ก๊น
์ค์
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='app.log'
)
# ๋ก๊ฑฐ ์์ฑ
logger = logging.getLogger(__name__)
# ๋ก๊ทธ ๋ ๋ฒจ ์ฌ์ฉ
logger.debug('๋๋ฒ๊ทธ ๋ฉ์์ง')
logger.info('์ ๋ณด ๋ฉ์์ง')
logger.warning('๊ฒฝ๊ณ ๋ฉ์์ง')
logger.error('์๋ฌ ๋ฉ์์ง')
logger.critical('์น๋ช
์ ์๋ฌ ๋ฉ์์ง')
โ
ํน์ง:
- ๋ค์ํ ๋ก๊ทธ ๋ ๋ฒจ
- ํ์ผ ์ถ๋ ฅ
- ํฌ๋งท ์ง์
- DEBUG (10): ์์ธํ ๋๋ฒ๊น ์ ๋ณด, ๊ฐ๋ฐ ์ค์ ์ ์ฉ
- INFO (20): ์ผ๋ฐ์ ์ธ ์ ๋ณด ๋ฉ์์ง, ์ ์ ๋์ ํ์ธ
- WARNING (30): ์ ์ฌ์ ๋ฌธ์ ์ํฉ, ํ์ง๋ง ํ๋ก๊ทธ๋จ์ ๊ณ์ ์คํ
- ERROR (40): ํน์ ๊ธฐ๋ฅ์ด ๋์ํ์ง ์๋ ์ค๋ฅ ์ํฉ
- CRITICAL (50): ํ๋ก๊ทธ๋จ ์์ฒด๊ฐ ์คํ์ ๊ณ์ํ ์ ์๋ ์ฌ๊ฐํ ์ค๋ฅ
- Logger: ๋ก๊น ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ ๊ฐ์ฒด
- Handler: ๋ก๊ทธ ๋ฉ์์ง๋ฅผ ์ ์ ํ ๋์์ผ๋ก ์ ๋ฌ
- Formatter: ๋ก๊ทธ ๋ฉ์์ง์ ์ต์ข ํ์ ์ง์
- Filter: ์ด๋ค ๋ก๊ทธ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ ์ง ๊ฒฐ์
-
__name__
๋ณ์ ์ฌ์ฉ: ๋ชจ๋ ์ด๋ฆ์ ๋ก๊ฑฐ ์ด๋ฆ์ผ๋ก ์ฌ์ฉํ์ฌ ๊ณ์ธต์ ๊ตฌ์กฐ ์์ฑ - ๋ฃจํธ ๋ก๊ฑฐ: ์ด๋ฆ ์์ด
logging.getLogger()
๋ก ์์ฑ - ์ ํ๋ฆฌ์ผ์ด์
์ ์ฉ ๋ก๊ฑฐ:
logging.getLogger('app_name')
# ๊ณ์ธต์ ๋ก๊ฑฐ ์์
logger = logging.getLogger('app.module.submodule')
๋ก๊ทธ ํธ๋ค๋ฌ๋ ๋ก๊ทธ ๋ฉ์์ง๋ฅผ ๋ค์ํ ์ถ๋ ฅ ๋์์ผ๋ก ์ ๋ฌํ๋ ์ญํ ์ ํ๋ค.
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
# ํ์ผ ํธ๋ค๋ฌ ์ค์
file_handler = RotatingFileHandler(
'app.log',
maxBytes=1024*1024, # 1MB
backupCount=5
)
file_handler.setLevel(logging.DEBUG)
# ์ฝ์ ํธ๋ค๋ฌ
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)
# ํฌ๋งคํฐ ์ค์
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# ๋ก๊ฑฐ์ ํธ๋ค๋ฌ ์ถ๊ฐ
logger = logging.getLogger(__name__)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
โ
ํน์ง:
- ํ์ผ ๋กํ ์ด์
- ๋ค์ค ํธ๋ค๋ฌ
- ๋ ๋ฒจ๋ณ ์ฒ๋ฆฌ
- StreamHandler: ์คํธ๋ฆผ(๋ณดํต sys.stdout, sys.stderr)์ผ๋ก ๋ก๊ทธ ์ถ๋ ฅ
- FileHandler: ํ์ผ์ ๋ก๊ทธ ๊ธฐ๋ก
- RotatingFileHandler: ํ์ผ ํฌ๊ธฐ ๊ธฐ๋ฐ ๋กํ ์ด์
- TimedRotatingFileHandler: ์๊ฐ ๊ธฐ๋ฐ ๋กํ ์ด์ (์ผ๋ณ, ์๊ฐ๋ณ ๋ฑ)
- SMTPHandler: ์ด๋ฉ์ผ๋ก ๋ก๊ทธ ์ ์ก
- SysLogHandler: ์์คํ ๋ก๊ทธ๋ก ์ ์ก
- HTTPHandler: HTTP ์๋ฒ๋ก ๋ก๊ทธ ์ ์ก
- SocketHandler: TCP/IP ์์ผ์ผ๋ก ๋ก๊ทธ ์ ์ก
- NullHandler: ๋ก๊ทธ ๋ฌด์ (๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ์ฉ)
# ํฌ๊ธฐ ๊ธฐ๋ฐ ๋กํ
์ด์
(๋ ์์ธํ ์์)
size_handler = RotatingFileHandler(
'app.log',
maxBytes=10*1024*1024, # 10MB
backupCount=10
)
# ์๊ฐ ๊ธฐ๋ฐ ๋กํ
์ด์
time_handler = TimedRotatingFileHandler(
'app.log',
when='midnight', # ๋งค์ผ ์์
interval=1, # 1์ผ๋ง๋ค
backupCount=30 # 30์ผ์น ๋ณด๊ด
)
# ์ฌ์ฉ์ ์ ์ ๋กํ
์ด์
(S3 ๋ฐฑ์
์์)
import boto3
from logging.handlers import RotatingFileHandler
class S3RotatingFileHandler(RotatingFileHandler):
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0,
encoding=None, delay=False, bucket_name=None):
self.bucket_name = bucket_name
super().__init__(filename, mode, maxBytes, backupCount, encoding, delay)
def doRollover(self):
# ๊ธฐ๋ณธ ๋กํ
์ด์
์ํ
super().doRollover()
# S3์ ๋ฐฑ์
if self.bucket_name:
s3 = boto3.client('s3')
backup_file = f"{self.baseFilename}.1"
s3.upload_file(
backup_file,
self.bucket_name,
f"logs/{os.path.basename(backup_file)}"
)
๊ฐ ํธ๋ค๋ฌ๋ ๋ ๋ฆฝ์ ์ธ ๋ก๊ทธ ๋ ๋ฒจ์ ๊ฐ์ง ์ ์์ด, ๋ค์ํ ์ค์๋์ ๋ฉ์์ง๋ฅผ ์ ์ ํ ์ถ๋ ฅ ๋์์ผ๋ก ๋ณด๋ผ ์ ์๋ค.
# ๋ ๋ฒจ๋ณ ๋ค์ค ํธ๋ค๋ฌ ์ค์ ์์
logger = logging.getLogger('app')
logger.setLevel(logging.DEBUG) # ๋ก๊ฑฐ ์์ฒด๋ ๋ชจ๋ ๋ฉ์์ง ์ฒ๋ฆฌ
# ๋ชจ๋ ๋ก๊ทธ๋ ํ์ผ์ ๊ธฐ๋ก
file_handler = logging.FileHandler('full.log')
file_handler.setLevel(logging.DEBUG)
logger.addHandler(file_handler)
# ๊ฒฝ๊ณ ์ด์์ ์ฝ์์ ์ถ๋ ฅ
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)
logger.addHandler(console_handler)
# ์๋ฌ ์ด์์ ์ด๋ฉ์ผ๋ก ์ ์ก
email_handler = logging.handlers.SMTPHandler(
mailhost=('smtp.example.com', 587),
fromaddr='[email protected]',
toaddrs=['[email protected]'],
subject='Application Error',
credentials=('username', 'password'),
secure=()
)
email_handler.setLevel(logging.ERROR)
logger.addHandler(email_handler)
๋ก๊น
์ค์ ์ ์ฝ๋์ ๋ถ๋ฆฌํ์ฌ ์ธ๋ถ ํ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค.
# logging.conf
[loggers]
keys=root,myapp
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_myapp]
level=DEBUG
handlers=fileHandler
qualname=myapp
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('app.log', 'a')
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
โ
ํน์ง:
- ์ค์ ํ์ผ ๋ถ๋ฆฌ
- ์ ์ฐํ ๊ตฌ์ฑ
- ์ฌ์ฌ์ฉ์ฑ
import logging
import logging.config
# .conf ํ์ผ ์ฌ์ฉ
logging.config.fileConfig('logging.conf')
# ์ค์ ํ์ผ์ ์ ์๋ ๋ก๊ฑฐ ์ฌ์ฉ
logger = logging.getLogger('myapp')
logger.info('์ค์ ํ์ผ์์ ๋ก๋๋ ๋ก๊ฑฐ๋ก ๋ฉ์์ง ๊ธฐ๋ก')
๋ณต์กํ, ๊ณ์ธต์ ๊ตฌ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ dictConfig๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ์ฉํ๋ค.
# logging.json ์์
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"standard": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "standard",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "detailed",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5,
"encoding": "utf8"
}
},
"loggers": {
"": {
"handlers": ["console"],
"level": "INFO"
},
"myapp": {
"handlers": ["file"],
"level": "DEBUG",
"propagate": false
}
}
}
# JSON ์ค์ ๋ก๋
import json
import logging.config
with open('logging.json', 'r') as f:
config = json.load(f)
logging.config.dictConfig(config)
logger = logging.getLogger('myapp')
๊ฐ๋ฐ, ํ ์คํธ, ํ๋ก๋์ ํ๊ฒฝ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ก๊น ์ค์ ์ ์ ์ฉํ ์ ์๋ค.
import os
import logging.config
import yaml
# ํ๊ฒฝ ๋ณ์์ ๋ฐ๋ฅธ ์ค์ ํ์ผ ์ ํ
env = os.environ.get('ENVIRONMENT', 'development')
config_path = f'config/logging_{env}.yaml'
# YAML ์ค์ ๋ก๋
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
logging.config.dictConfig(config)
์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ค์๋ ๋ก๊น ์ค์ ์ ๋ณ๊ฒฝํ ์ ์๋ค.
# ์คํ ์ค ๋ก๊ทธ ๋ ๋ฒจ ๋ณ๊ฒฝ
def set_log_level(logger_name, level):
"""๋ก๊ฑฐ์ ๋ ๋ฒจ์ ๋์ ์ผ๋ก ๋ณ๊ฒฝ"""
logger = logging.getLogger(logger_name)
logger.setLevel(getattr(logging, level))
# ํธ๋ค๋ฌ ๋ ๋ฒจ๋ ๋ณ๊ฒฝ
for handler in logger.handlers:
handler.setLevel(getattr(logging, level))
return f"๋ก๊ฑฐ '{logger_name}'์ ๋ ๋ฒจ์ด {level}๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค."
# ์ฌ์ฉ ์: ๋๋ฒ๊ทธ ๋ชจ๋ ์ ํ
set_log_level('myapp', 'DEBUG') # ๋ ์์ธํ ๋ก๊น
์ผ๋ก ์ ํ
๋ก๊น
์์คํ
์ ํ์ฅํ์ฌ ํน๋ณํ ์๊ตฌ์ฌํญ์ ์ฒ๋ฆฌํ ์ ์๋ค.
import logging
import re
from datetime import datetime
class SecurityFilter(logging.Filter):
def __init__(self, param=None):
self.param = param
def filter(self, record):
if hasattr(record, 'msg'):
record.msg = self.mask_sensitive_data(record.msg)
return True
def mask_sensitive_data(self, msg):
if isinstance(msg, str):
msg = re.sub(r'\d{4}-\d{4}-\d{4}-\d{4}', 'XXXX-XXXX-XXXX-XXXX', msg)
msg = re.sub(r'password=.*?[,\s]', 'password=****', msg)
return msg
class CustomLogger(logging.Logger):
def __init__(self, name):
super().__init__(name)
self.addFilter(SecurityFilter())
โ
ํน์ง:
- ๋ฏผ๊ฐ ์ ๋ณด ๋ง์คํน
- ๋ก๊ทธ ํํฐ๋ง
- ์ปค์คํ ๋ก์ง
1. ์ปค์คํ
ํํฐ ํ์ฉ:
import logging
import re
class SensitiveDataFilter(logging.Filter):
"""๋ฏผ๊ฐํ ๊ฐ์ธ์ ๋ณด๋ฅผ ๋ง์คํนํ๋ ํํฐ"""
def __init__(self):
super().__init__()
# ์ ๊ท์ ํจํด ์ปดํ์ผ
self.patterns = {
'credit_card': re.compile(r'\b(?:\d{4}[- ]?){3}\d{4}\b'),
'email': re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'),
'phone': re.compile(r'\b01[016789][- ]?\d{3,4}[- ]?\d{4}\b'),
'ssn': re.compile(r'\b\d{6}[- ]?\d{7}\b')
}
self.replacements = {
'credit_card': 'XXXX-XXXX-XXXX-XXXX',
'email': '[์ด๋ฉ์ผ ์ญ์ ๋จ]',
'phone': '[์ ํ๋ฒํธ ์ญ์ ๋จ]',
'ssn': '[์ฃผ๋ฏผ๋ฒํธ ์ญ์ ๋จ]'
}
def filter(self, record):
if isinstance(record.msg, str):
for key, pattern in self.patterns.items():
record.msg = pattern.sub(self.replacements[key], record.msg)
return True
# ํํฐ ์ ์ฉ
logger = logging.getLogger('secure')
logger.addFilter(SensitiveDataFilter())
# ์ฌ์ฉ ์์
logger.info("์ฌ์ฉ์ ์ด๋ฉ์ผ: [email protected], ์นด๋๋ฒํธ: 1234-5678-9012-3456")
# ์ถ๋ ฅ: "์ฌ์ฉ์ ์ด๋ฉ์ผ: [์ด๋ฉ์ผ ์ญ์ ๋จ], ์นด๋๋ฒํธ: XXXX-XXXX-XXXX-XXXX"
2. ์ปค์คํ
๋ก๊ฑฐ ๊ตฌํ:
import logging
import time
class PerformanceLogger(logging.Logger):
"""์คํ ์๊ฐ์ ์๋์ผ๋ก ์ธก์ ํ๋ ๋ก๊ฑฐ"""
def __init__(self, name, level=logging.NOTSET):
super().__init__(name, level)
self.start_times = {}
def start_timer(self, task_name):
"""์์
ํ์ด๋จธ ์์"""
self.start_times[task_name] = time.time()
self.info(f"์์
์์: {task_name}")
def end_timer(self, task_name):
"""์์
ํ์ด๋จธ ์ข
๋ฃ ๋ฐ ๊ฒฝ๊ณผ ์๊ฐ ๋ก๊น
"""
if task_name in self.start_times:
elapsed = time.time() - self.start_times[task_name]
self.info(f"์์
์๋ฃ: {task_name} (์์ ์๊ฐ: {elapsed:.3f}์ด)")
del self.start_times[task_name]
else:
self.warning(f"์ ์ ์๋ ์์
: {task_name}")
# ์ปค์คํ
๋ก๊ฑฐ ํด๋์ค ๋ฑ๋ก
logging.setLoggerClass(PerformanceLogger)
# ์ฌ์ฉ ์์
logger = logging.getLogger('performance')
logger.start_timer('๋ฐ์ดํฐ ์ฒ๋ฆฌ')
# ... ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ์์
์ํ ...
logger.end_timer('๋ฐ์ดํฐ ์ฒ๋ฆฌ')
3. ์ปค์คํ
ํธ๋ค๋ฌ ๊ตฌํ:
import logging
import requests
class SlackHandler(logging.Handler):
"""Slack์ผ๋ก ๋ก๊ทธ ๋ฉ์์ง๋ฅผ ์ ์กํ๋ ํธ๋ค๋ฌ"""
def __init__(self, webhook_url, channel="#alerts", username="Logger"):
super().__init__()
self.webhook_url = webhook_url
self.channel = channel
self.username = username
def emit(self, record):
try:
# ๋ก๊ทธ ๋ฉ์์ง ํฌ๋งทํ
log_message = self.format(record)
# Slack ๋ฉ์์ง ํ์ด๋ก๋
payload = {
"channel": self.channel,
"username": self.username,
"text": log_message,
"icon_emoji": ":warning:" if record.levelno >= logging.WARNING else ":information_source:"
}
# Slack webhook ํธ์ถ
requests.post(self.webhook_url, json=payload)
except Exception:
self.handleError(record)
# ์ฌ์ฉ ์์
slack_handler = SlackHandler('https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK')
slack_handler.setLevel(logging.ERROR) # ์ค๋ฅ๋ง Slack์ผ๋ก ์ ์ก
formatter = logging.Formatter('%(levelname)s - %(message)s')
slack_handler.setFormatter(formatter)
logger = logging.getLogger('app')
logger.addHandler(slack_handler)
โ ๋ก๊น ์์คํ ํ์ฅ ์ฌ๋ก:
- ๋น์ฆ๋์ค ์ด๋ฒคํธ ๋ก๊น : ์ค์ ๋น์ฆ๋์ค ์ด๋ฒคํธ๋ฅผ ๋ณ๋๋ก ๊ธฐ๋ก
- ๊ฐ์ฌ(Audit) ๋ก๊น : ๊ท์ ์ค์๋ฅผ ์ํ ๊ฐ์ฌ ์ถ์ ๊ธฐ๋ก
- ๊ตฌ์กฐํ๋ ๋ก๊น : JSON ํ์์ผ๋ก ๋ก๊ทธ ์ถ๋ ฅํ์ฌ ๊ฒ์ ๋ฐ ๋ถ์ ์ฉ์ด
- ๋ก๊ทธ ํตํฉ: ์ฌ๋ฌ ์๋น์ค์ ๋ก๊ทธ๋ฅผ ์ค์ ์ง์ค์์ผ๋ก ์์ง
- ์กฐ๊ฑด๋ถ ๋ก๊น : ํน์ ์กฐ๊ฑด์์๋ง ์์ธ ๋ก๊น ํ์ฑํ
๋ก๊น
์ ์ปจํ
์คํธ ์ ๋ณด๋ฅผ ์ถ๊ฐํ์ฌ ๋ก๊ทธ์ ๊ฐ์น๋ฅผ ๋์ด๋ ๊ธฐ๋ฒ์ด๋ค.
from contextvars import ContextVar
from threading import Lock
request_id = ContextVar('request_id', default=None)
class ContextualLogger:
def __init__(self, logger):
self.logger = logger
self._lock = Lock()
def _format_message(self, message):
rid = request_id.get()
return f"[RequestID: {rid}] {message}" if rid else message
def info(self, message, *args, **kwargs):
with self._lock:
self.logger.info(self._format_message(message), *args, **kwargs)
โ
ํน์ง:
- ์ปจํ ์คํธ ์ถ์
- ์ค๋ ๋ ์์ ์ฑ
- ์์ฒญ ์๋ณ
1. ์ปจํ
์คํธ ๋ณ์ ํ์ฉ:
import logging
from contextvars import ContextVar
import uuid
# ์ปจํ
์คํธ ๋ณ์ ์ ์
request_id = ContextVar('request_id', default=None)
user_id = ContextVar('user_id', default=None)
class ContextFilter(logging.Filter):
"""๋ก๊ทธ ๋ ์ฝ๋์ ์ปจํ
์คํธ ์ ๋ณด ์ถ๊ฐ"""
def filter(self, record):
# ์ปจํ
์คํธ ๋ณ์์์ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
record.request_id = request_id.get()
record.user_id = user_id.get()
return True
# ์น ์์ฒญ ์ฒ๋ฆฌ ์์
def process_request(request_data, user=None):
# ์ปจํ
์คํธ ์ค์
req_id = str(uuid.uuid4())
request_id.set(req_id)
if user:
user_id.set(user['id'])
logger = logging.getLogger('app')
logger.info(f"์์ฒญ ์ฒ๋ฆฌ ์์: {request_data}")
# ์ฒ๋ฆฌ ๋ก์ง...
logger.info("์์ฒญ ์ฒ๋ฆฌ ์๋ฃ")
return {"status": "success", "request_id": req_id}
2. ์ค๋ ๋ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ฌ์ฉ:
import logging
import threading
import uuid
# ์ค๋ ๋ ๋ก์ปฌ ์คํ ๋ฆฌ์ง
context = threading.local()
class ThreadContextFilter(logging.Filter):
"""์ค๋ ๋ ๋ก์ปฌ ์ปจํ
์คํธ๋ฅผ ๋ก๊ทธ์ ์ถ๊ฐ"""
def filter(self, record):
if hasattr(context, 'request_id'):
record.request_id = context.request_id
else:
record.request_id = 'no-request-id'
if hasattr(context, 'user_id'):
record.user_id = context.user_id
else:
record.user_id = 'anonymous'
return True
# ์ค๋ ๋ ์ปจํ
์คํธ ์ด๊ธฐํ
def init_request_context():
context.request_id = str(uuid.uuid4())
context.start_time = time.time()
# ์ปจํ
์คํธ ํด๋ฆฌ์ด
def clear_request_context():
if hasattr(context, 'request_id'):
delattr(context, 'request_id')
if hasattr(context, 'start_time'):
delattr(context, 'start_time')
3. ์ปจํ
์คํธ ๋งค๋์ ํจํด:
import logging
from contextlib import contextmanager
import time
logger = logging.getLogger('app')
@contextmanager
def log_context(**kwargs):
"""๋ก๊น
์ปจํ
์คํธ๋ฅผ ์ค์ ํ๋ ์ปจํ
์คํธ ๋งค๋์ """
# ์ด์ MDC ์ ์ฅ
old_mdc = logging.MDC.get_map() if hasattr(logging, 'MDC') else {}
# ์ ์ปจํ
์คํธ ์ค์
for k, v in kwargs.items():
logging.MDC.put(k, v) if hasattr(logging, 'MDC') else None
try:
yield
finally:
# ์ด์ MDC ๋ณต์
logging.MDC.clear() if hasattr(logging, 'MDC') else None
for k, v in old_mdc.items():
logging.MDC.put(k, v) if hasattr(logging, 'MDC') else None
# ์ฌ์ฉ ์์
with log_context(operation='data_export', user_id='12345'):
logger.info("๋ฐ์ดํฐ ๋ด๋ณด๋ด๊ธฐ ์์")
# ... ์์
์ํ ...
logger.info("๋ฐ์ดํฐ ๋ด๋ณด๋ด๊ธฐ ์๋ฃ")
๋ก๊ทธ๋ฅผ ๊ตฌ์กฐํ๋ ํ์(์ฃผ๋ก JSON)์ผ๋ก ์ถ๋ ฅํ์ฌ ๊ฒ์ ๋ฐ ๋ถ์์ ์ฉ์ดํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
import logging
import json
from datetime import datetime
class JsonFormatter(logging.Formatter):
"""๋ก๊ทธ ๋ฉ์์ง๋ฅผ JSON ํ์์ผ๋ก ํฌ๋งทํ
"""
def format(self, record):
log_object = {
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"logger": record.name,
"path": record.pathname,
"line": record.lineno,
"function": record.funcName
}
# ์์ธ ์ ๋ณด ์ถ๊ฐ
if record.exc_info:
log_object["exception"] = self.formatException(record.exc_info)
# ์ถ๊ฐ ์์ฑ ํฌํจ
for key, value in record.__dict__.items():
if key not in log_object and not key.startswith('_') and isinstance(value, (str, int, float, bool, type(None))):
log_object[key] = value
return json.dumps(log_object)
# ์ฌ์ฉ ์์
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger = logging.getLogger('app')
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# ๊ตฌ์กฐํ๋ ๋ก๊ทธ ์ถ๋ ฅ
logger.info("์ฌ์ฉ์ ๋ก๊ทธ์ธ", extra={
"user_id": "user123",
"ip_address": "192.168.1.1",
"browser": "Chrome 90.0",
"status": "success"
})
- ๊ธฐ๊ณ๊ฐ ์ฝ๊ธฐ ์ฌ์ด ํ์
- ํ๋ ๊ธฐ๋ฐ ๊ฒ์ ๋ฐ ํํฐ๋ง
- ๋ก๊ทธ ์ง๊ณ ๋ฐ ๋ถ์ ์ฉ์ด
- ๋ก๊ทธ ์๊ฐํ ๋๊ตฌ์์ ํตํฉ
1. Python ๊ธฐ๋ณธ ๋ก๊น
๊ณผ JSON ํฌ๋งทํฐ:
์์ ๋ณด์ฌ์ค JsonFormatter ํด๋์ค ์ฌ์ฉ
2. ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ:
# python-json-logger ์ฌ์ฉ ์์
import logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter('%(timestamp)s %(level)s %(name)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# structlog ์ฌ์ฉ ์์
import structlog
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
)
logger = structlog.get_logger()
logger.info("์ด๋ฒคํธ ๋ฐ์", user_id=123, action="login")
3. ๋ก๊ทธ ์ด๋ฒคํธ์ ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ:
import logging
import json
import socket
import os
import platform
class EnhancedJsonFormatter(logging.Formatter):
"""์์คํ
์ ๋ณด๊ฐ ํฌํจ๋ ํฅ์๋ JSON ํฌ๋งทํฐ"""
def __init__(self):
super().__init__()
# ์ ์ ๋ฉํ๋ฐ์ดํฐ ๋ฏธ๋ฆฌ ๊ณ์ฐ
self.static_metadata = {
"hostname": socket.gethostname(),
"pid": os.getpid(),
"python_version": platform.python_version(),
"platform": platform.platform(),
"application": os.environ.get("APP_NAME", "unknown")
}
def format(self, record):
log_data = {
"timestamp": self.formatTime(record, self.datefmt),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"location": f"{record.pathname}:{record.lineno}",
**self.static_metadata
}
# ๋์ ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ
for key, value in record.__dict__.items():
if key not in log_data and not key.startswith('_'):
if isinstance(value, (str, int, float, bool, type(None))):
log_data[key] = value
# ์คํ ํธ๋ ์ด์ค ์ฒ๋ฆฌ
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data)
๋ค์ํ ์ ํ๋ฆฌ์ผ์ด์
์ํฉ์ ๋ง๋ ๊ณ ๊ธ ๋ก๊น
ํจํด๊ณผ ๊ธฐ๋ฒ์ด๋ค.
# Sentry ์ฐ๋ ์์
import logging
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
# Sentry ์ค์
sentry_logging = LoggingIntegration(
level=logging.INFO, # ์บก์ฒํ ์ต์ ๋ก๊ทธ ๋ ๋ฒจ
event_level=logging.ERROR # ์ด๋ฒคํธ๋ก ์ ์กํ ์ต์ ๋ ๋ฒจ
)
sentry_sdk.init(
dsn="https://[email protected]/0",
integrations=[sentry_logging]
)
# ELK ์คํ์ฉ ๋ก๊น
์ค์
import logging
from logstash_async.handler import AsynchronousLogstashHandler
# Logstash ํธ๋ค๋ฌ ์ค์
logstash_handler = AsynchronousLogstashHandler(
host='logstash.example.com',
port=5959,
database_path='logstash.db'
)
# ๋ก๊ฑฐ ์ค์
logger = logging.getLogger('app')
logger.addHandler(logstash_handler)
logger.setLevel(logging.INFO)
# OpenTelemetry์ ํธ๋ ์ด์ฑ ํตํฉ ์์
import logging
from opentelemetry import trace
from opentelemetry.trace.status import Status, StatusCode
# ํ์ฌ ์คํฌ์์ ํธ๋ ์ด์ค ID ๊ฐ์ ธ์ค๊ธฐ
class TraceContextFilter(logging.Filter):
def filter(self, record):
current_span = trace.get_current_span()
if current_span:
ctx = current_span.get_span_context()
record.trace_id = format(ctx.trace_id, '032x')
record.span_id = format(ctx.span_id, '016x')
else:
record.trace_id = '0' * 32
record.span_id = '0' * 16
return True
# ๋ก๊ฑฐ ์ค์
logger = logging.getLogger('app')
logger.addFilter(TraceContextFilter())
import logging
import random
class SamplingFilter(logging.Filter):
"""ํน์ ๋น์จ๋ก ๋ก๊ทธ๋ฅผ ์ํ๋งํ๋ ํํฐ"""
def __init__(self, rate=0.1):
self.rate = rate # ๋ก๊ทธ ์ํ๋ง ๋น์จ (0.0 ~ 1.0)
def filter(self, record):
# DEBUG ๋ก๊ทธ๋ ์ํ๋ง
if record.levelno == logging.DEBUG:
return random.random() < self.rate
# ๊ทธ ์ธ ๋ ๋ฒจ์ ๋ชจ๋ ๋ก๊น
return True
# ์ฌ์ฉ ์์
debug_handler = logging.FileHandler('debug.log')
debug_handler.setLevel(logging.DEBUG)
debug_handler.addFilter(SamplingFilter(rate=0.01)) # ๋๋ฒ๊ทธ ๋ก๊ทธ์ 1%๋ง ๊ธฐ๋ก
logger = logging.getLogger('app')
logger.addHandler(debug_handler)
import logging
import logging.handlers
import queue
import threading
# ๋ก๊ทธ ๋ฉ์์ง๋ฅผ ๋ด์ ํ
log_queue = queue.Queue(-1) # ๋ฌด์ ํ ํ ํฌ๊ธฐ
# ํ ํธ๋ค๋ฌ
queue_handler = logging.handlers.QueueHandler(log_queue)
# ์ค์ ํธ๋ค๋ฌ
file_handler = logging.FileHandler('app.log')
console_handler = logging.StreamHandler()
# ๋ฆฌ์ค๋ ์ค์
listener = logging.handlers.QueueListener(
log_queue, file_handler, console_handler, respect_handler_level=True
)
# ๋ก๊ฑฐ ์ค์
logger = logging.getLogger()
logger.addHandler(queue_handler)
logger.setLevel(logging.INFO)
# ๋ฆฌ์ค๋ ์์
listener.start()
# ์ ํ๋ฆฌ์ผ์ด์
์ข
๋ฃ ์ ๋ฆฌ์ค๋ ์ ์ง
# atexit.register(listener.stop)
๋ก๊น
์์คํ
์์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ๋ณดํธํ๊ณ ๊ท์ ์ ์ค์ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
import logging
import re
class GDPRCompliantFilter(logging.Filter):
"""GDPR์ ์ค์ํ๋ ๋ก๊น
ํํฐ"""
def __init__(self):
super().__init__()
# ๋ง์คํนํ ํจํด ์ ์
self.patterns = {
'email': (r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '[์ด๋ฉ์ผ]'),
'phone': (r'\b\+?[0-9]{1,3}[-\s]?[0-9]{3,4}[-\s]?[0-9]{3,4}[-\s]?[0-9]{3,4}\b', '[์ ํ๋ฒํธ]'),
'credit_card': (r'\b(?:\d{4}[- ]?){3}\d{4}\b', '[์นด๋๋ฒํธ]'),
'ip': (r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', '[IP์ฃผ์]'),
'password': (r'password["\']?\s*[:=]\s*["\']?[^"\',\s]+', 'password=*****'),
'ssn': (r'\d{3}-\d{2}-\d{4}', '[์ฃผ๋ฏผ๋ฒํธ]'),
}
def filter(self, record):
if isinstance(record.msg, str):
for _, (pattern, replacement) in self.patterns.items():
record.msg = re.sub(pattern, replacement, record.msg)
return True
# ์ํธํ๋ ๋ก๊ทธ ํธ๋ค๋ฌ
import logging
from cryptography.fernet import Fernet
class EncryptedFileHandler(logging.FileHandler):
"""๋ก๊ทธ๋ฅผ ์ํธํํ์ฌ ์ ์ฅํ๋ ํธ๋ค๋ฌ"""
def __init__(self, filename, mode='a', encoding=None, delay=False, key=None):
super().__init__(filename, mode, encoding, delay)
if key:
self.fernet = Fernet(key)
else:
# ํค ์์ฑ ๋ฐ ์ ์ฅ (์ค์ ๋ก๋ ์์ ํ๊ฒ ๊ด๋ฆฌํด์ผ ํจ)
key = Fernet.generate_key()
with open('log_key.key', 'wb') as key_file:
key_file.write(key)
self.fernet = Fernet(key)
def emit(self, record):
try:
msg = self.format(record)
# ๋ก๊ทธ ๋ฉ์์ง ์ํธํ
encrypted_msg = self.fernet.encrypt(msg.encode())
# ๋ฐ์ด๋๋ฆฌ ๋ชจ๋๋ก ๋ณ๊ฒฝ
if self.stream is None:
self.stream = self._open()
self.stream.write(encrypted_msg + b'\n')
self.flush()
except Exception:
self.handleError(record)
import logging
import functools
import inspect
import json
from datetime import datetime
# ๊ฐ์ฌ ๋ก๊ฑฐ ์ค์
audit_logger = logging.getLogger('audit')
audit_handler = logging.FileHandler('audit.log')
audit_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
audit_handler.setFormatter(audit_formatter)
audit_logger.addHandler(audit_handler)
audit_logger.setLevel(logging.INFO)
def audit_log(action=None):
"""ํจ์ ํธ์ถ์ ๊ฐ์ฌ ๋ก๊ทธ์ ๊ธฐ๋กํ๋ ๋ฐ์ฝ๋ ์ดํฐ"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# ํธ์ถ ์ ๋ณด ์์ง
func_args = inspect.getcallargs(func, *args, **kwargs)
# ๋ฏผ๊ฐํ ์ธ์ ๋ง์คํน
for arg_name in func_args:
if arg_name.lower() in ('password', 'secret', 'token'):
func_args[arg_name] = '*****'
# ๊ฐ์ฌ ์ด๋ฒคํธ ๊ธฐ๋ก
event = {
'timestamp': datetime.now().isoformat(),
'action': action or func.__name__,
'function': f"{func.__module__}.{func.__name__}",
'args': json.dumps(func_args, default=str)
}
audit_logger.info(f"AUDIT: {json.dumps(event)}")
# ํจ์ ์คํ ๋ฐ ๊ฒฐ๊ณผ ๋ฐํ
return func(*args, **kwargs)
return wrapper
return decorator
# ์ฌ์ฉ ์์
@audit_log(action='user_update')
def update_user(user_id, name=None, email=None, password=None):
# ์ฌ์ฉ์ ์ ๋ณด ์
๋ฐ์ดํธ ๋ก์ง
pass
๋ก๊น
์์คํ
์ ํจ์จ์ ์ผ๋ก ์ค๊ณํ๊ณ ์ฑ๋ฅ ์ํฅ์ ์ต์ํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
1. ์ง์ฐ ํ๊ฐ(Lazy Evaluation):
import logging
logger = logging.getLogger('app')
# ๋นํจ์จ์ ์ธ ๋ก๊น
- ํญ์ ๋ฌธ์์ด ์ฐ์ฐ ์ํ
def inefficient(data):
logger.debug("๋ณต์กํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: " + str(process_data(data)))
# ํจ์จ์ ์ธ ๋ก๊น
- ํ์ํ ๋๋ง ๋ฌธ์์ด ์ฐ์ฐ ์ํ
def efficient(data):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("๋ณต์กํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: %s", process_data(data))
# ๋ ํจ์จ์ ์ธ ๋ก๊น
- f-string ์ฌ์ฉ ๋ฐฉ์ง
def more_efficient(data):
# logger.debug(f"๋ณต์กํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: {process_data(data)}") # ๋นํจ์จ์
logger.debug("๋ณต์กํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: %s", process_data(data)) # ํจ์จ์
2. ๋น๋๊ธฐ ๋ก๊น
:
# ์์ ์ค๋ช
ํ QueueHandler์ QueueListener ํ์ฉ
3. ๋ฐฐ์น ์ฒ๋ฆฌ:
import logging
import threading
import time
import queue
class BatchingHandler(logging.Handler):
"""๋ก๊ทธ ๋ฉ์์ง๋ฅผ ๋ฐฐ์น๋ก ์ฒ๋ฆฌํ๋ ํธ๋ค๋ฌ"""
def __init__(self, capacity=100, flush_interval=5.0, target_handler=None):
super().__init__()
self.capacity = capacity # ์ต๋ ๋ฐฐ์น ํฌ๊ธฐ
self.flush_interval = flush_interval # ๊ฐ์ ํ๋ฌ์ ๊ฐ๊ฒฉ(์ด)
self.target_handler = target_handler # ์ค์ ์ฒ๋ฆฌํ ํธ๋ค๋ฌ
self.buffer = [] # ๋ก๊ทธ ๋ฉ์์ง ๋ฒํผ
self.lock = threading.Lock()
self.timer = None
self._start_timer()
def emit(self, record):
with self.lock:
self.buffer.append(record)
# ๋ฒํผ๊ฐ ์ฉ๋์ ์ด๊ณผํ๋ฉด ํ๋ฌ์
if len(self.buffer) >= self.capacity:
self.flush()
def flush(self):
with self.lock:
if not self.buffer:
return
# ํ๊ฒ ํธ๋ค๋ฌ๋ก ๋ชจ๋ ๋ก๊ทธ ๋ ์ฝ๋ ์ ๋ฌ
if self.target_handler:
for record in self.buffer:
self.target_handler.handle(record)
self.buffer.clear()
# ํ์ด๋จธ ์ฌ์์
self._start_timer()
def _start_timer(self):
if self.timer:
self.timer.cancel()
self.timer = threading.Timer(self.flush_interval, self.flush)
self.timer.daemon = True
self.timer.start()
def close(self):
self.flush()
if self.timer:
self.timer.cancel()
super().close()
4. ๋ก๊ทธ ์ํ๋ง:
# ์์ ์ค๋ช
ํ SamplingFilter ํ์ฉ
ํจ๊ณผ์ ์ธ ๋ก๊น
์ ์ํ ์ฃผ์ ํ๊ณผ ๋ชจ๋ฒ ์ฌ๋ก์ด๋ค.
โ
๋ชจ๋ฒ ์ฌ๋ก:
- ์ ์ ํ ๋ก๊ทธ ๋ ๋ฒจ ์ฌ์ฉ: ์ํฉ์ ๋ง๋ ๋ก๊ทธ ๋ ๋ฒจ ์ ํ์ผ๋ก ํํฐ๋ง ์ฉ์ดํ๊ฒ
- ๊ตฌ์กฐํ๋ ๋ก๊น : ๊ธฐ๊ณ๊ฐ ํ์ฑํ๊ธฐ ์ฌ์ด ํ์์ผ๋ก ๋ก๊ทธ ์ถ๋ ฅ
- ์์ธ ์ฒ๋ฆฌ ํฌํจ: ์์ธ ๋ฐ์ ์ ์คํ ํธ๋ ์ด์ค์ ์ปจํ ์คํธ ์ ๋ณด ํจ๊ป ๊ธฐ๋ก
- ์ฑ๋ฅ ๊ณ ๋ ค: ๋ก๊น ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ ์ต์ํ
- ๋ก๊ทธ ๋กํ ์ด์ ์ค์ : ๋์คํฌ ๊ณต๊ฐ ๊ด๋ฆฌ์ ๋ก๊ทธ ํ์ผ ์ ๋ฆฌ
- ๋ฏผ๊ฐ ์ ๋ณด ๋ณดํธ: ๊ฐ์ธ์ ๋ณด ๋ฐ ๋ณด์ ์ ๋ณด ๋ง์คํน
- ์ปจํ ์คํธ ์ ๋ณด ํฌํจ: ๋ก๊ทธ ๋ถ์ ์ฉ์ดํ๊ฒ ๋ค์ํ ์ปจํ ์คํธ ์ ๋ณด ์ถ๊ฐ
- ๋ชจ๋ํฐ๋ง ์ฐ๋: ๋ก๊ทธ ์์คํ ๊ณผ ๋ชจ๋ํฐ๋ง ๋๊ตฌ ํตํฉ
- ์ผ๊ด๋ ํ์: ๋ก๊ทธ ํ์์ ์ผ๊ด๋๊ฒ ์ ์งํ์ฌ ํ์ฑ ๋ฐ ๋ถ์ ์ฉ์ดํ๊ฒ
- ๋ก๊ทธ ์ง๊ณ: ์ฌ๋ฌ ์๋น์ค์ ๋ก๊ทธ๋ฅผ ์ค์์์ ๊ด๋ฆฌ
- ๋ก๊ทธ ๋ ๋ฒจ ์ปจํธ๋กค: ์คํ ์ค ๋ก๊ทธ ๋ ๋ฒจ์ ๋ณ๊ฒฝํ ์ ์๋ ๋ฉ์ปค๋์ฆ ์ ๊ณต
- ํ ์คํธ ๊ธฐ๋ฐ ๋ก๊ทธ: ์ฌ๋์ด ์ฝ๊ธฐ ์ฌ์ด ํ ์คํธ ๋ก๊ทธ์ ๊ธฐ๊ณ๊ฐ ์ฝ๊ธฐ ์ฌ์ด ๊ตฌ์กฐํ๋ ๋ก๊ทธ ๋ชจ๋ ๊ณ ๋ ค
โ
๋ก๊น
์ฒดํฌ๋ฆฌ์คํธ:
- ๋ก๊น ์ค๊ณ: ์ ํ๋ฆฌ์ผ์ด์ ์๊ตฌ์ฌํญ์ ๋ง๋ ๋ก๊น ์ ๋ต ์๋ฆฝ
- ๋ก๊ฑฐ ๊ณ์ธต ๊ตฌ์กฐ: ๋ชจ๋๋ณ ์ ์ ํ ๋ก๊ฑฐ ๊ณ์ธต ์ค๊ณ
- ํธ๋ค๋ฌ ๊ตฌ์ฑ: ์ํฉ์ ๋ง๋ ๋ค์ํ ์ถ๋ ฅ ๋์ ์ค์
- ํฌ๋งท ์ ์: ํ์ํ ์ ๋ณด๋ฅผ ๋ชจ๋ ํฌํจํ๋ ๋ก๊ทธ ํฌ๋งท ์ง์
- ๋ก๊ทธ ๋ ๋ฒจ ์ ์ฑ : ๊ฐ ํ๊ฒฝ(๊ฐ๋ฐ, ํ ์คํธ, ์ด์)๋ณ ๋ก๊ทธ ๋ ๋ฒจ ์ ์ฑ ์๋ฆฝ
- ๋ณด์ ๊ณ ๋ ค: ๋ฏผ๊ฐ ์ ๋ณด ์ฒ๋ฆฌ ๋ฐฉ์ ๋ง๋ จ
- ์ฑ๋ฅ ํ ์คํธ: ๋ก๊น ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ ์ธก์
- ๋ก๊ทธ ๊ด๋ฆฌ: ๋ก๊ทธ ๋กํ ์ด์ , ๋ณด๊ด, ์์นด์ด๋น ์ ์ฑ ์๋ฆฝ
- ๋ชจ๋ํฐ๋ง ํตํฉ: ์๋ฆผ ์์คํ ๊ณผ ๋ก๊ทธ ์์คํ ์ฐ๋
- ๋ก๊ทธ ๋ถ์: ๋ก๊ทธ ๋ถ์ ๋๊ตฌ ๋ฐ ๋ฐฉ๋ฒ๋ก ์ค๋น
ํ์ด์ฌ์ ๋ก๊น
์์คํ
์ ๊ฐ๋ ฅํ๊ณ ์ ์ฐํ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ๋ค์ํ ์ ํ๋ฆฌ์ผ์ด์
์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ ์ ์๋ค. ๊ธฐ๋ณธ์ ์ธ ๋ก๊ทธ ์ถ๋ ฅ๋ถํฐ ๊ณ ๊ธ ๋ถ์ฐ ์์คํ
๋ก๊น
๊น์ง, ์ ์ ํ ๋ก๊น
์ ๋ต์ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ๋ฐ, ๋๋ฒ๊น
, ์ด์์ ๋ชจ๋ ๋จ๊ณ์์ ์ค์ํ ์ญํ ์ ํ๋ค.
ํจ๊ณผ์ ์ธ ๋ก๊น
์ ๋จ์ํ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ๋ ๊ฒ ์ด์์ ์๋ฏธ๋ฅผ ๊ฐ๋๋ค. ๊ตฌ์กฐํ๋ ์ ๋ณด, ์ปจํ
์คํธ ๋ฐ์ดํฐ, ์ฑ๋ฅ ์ต์ ํ, ๋ณด์ ๊ณ ๋ ค์ฌํญ์ด ๋ชจ๋ ํตํฉ๋ ์ ๋ต์ด ํ์ํ๋ค. ์ด๋ฌํ ์ข
ํฉ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ํตํด ๋ก๊ทธ๋ ๋จ์ํ ๊ธฐ๋ก์ ๋์ด ๊ฐ๋ ฅํ ๋ฌธ์ ํด๊ฒฐ ๋๊ตฌ์ด์ ์์คํ
์ดํด์ ํต์ฌ ์์์ด ๋ ์ ์๋ค.