KR_Logging - somaz94/python-study GitHub Wiki

Python ๋กœ๊น… ๊ฐœ๋… ์ •๋ฆฌ


๊ฐœ์š”

ํŒŒ์ด์ฌ์˜ ๋กœ๊น…(logging)์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์ค‘์— ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ถ”์ ํ•˜๊ณ  ๊ธฐ๋กํ•˜๋Š” ์ค‘์š”ํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋‹ค. ํšจ๊ณผ์ ์ธ ๋กœ๊น…์€ ๊ฐœ๋ฐœ, ๋””๋ฒ„๊น…, ์šด์˜ ๋‹จ๊ณ„์—์„œ ๋ฌธ์ œ ํ•ด๊ฒฐ๊ณผ ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•œ ํ•„์ˆ˜์ ์ธ ๋„๊ตฌ์ด๋‹ค.

๋กœ๊น…์€ ๋‹จ์ˆœํ•œ print ๋ฌธ๋ณด๋‹ค ๋งŽ์€ ์žฅ์ ์„ ์ œ๊ณตํ•œ๋‹ค:

  • ๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ํ†ตํ•œ ์ค‘์š”๋„ ๊ตฌ๋ถ„
  • ํŒŒ์ผ, ์ฝ˜์†”, ๋„คํŠธ์›Œํฌ ๋“ฑ ๋‹ค์–‘ํ•œ ์ถœ๋ ฅ ๋Œ€์ƒ ์ง€์›
  • ํฌ๋งทํŒ…๊ณผ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ
  • ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ/๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์Šค ํ™˜๊ฒฝ์—์„œ์˜ ์•ˆ์ „์„ฑ
  • ๊ณ„์ธต์  ๋กœ๊ฑฐ ๊ตฌ์กฐ๋ฅผ ํ†ตํ•œ ์œ ์—ฐํ•œ ๊ตฌ์„ฑ

ํŒŒ์ด์ฌ์˜ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋œ logging ๋ชจ๋“ˆ์€ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ๋ชจ๋‘ ์ œ๊ณตํ•˜๋ฉด์„œ๋„ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค.



1๏ธโƒฃ ๋กœ๊น… ๊ธฐ์ดˆ

ํŒŒ์ด์ฌ์˜ 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('์น˜๋ช…์  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€')

โœ… ํŠน์ง•:

  • ๋‹ค์–‘ํ•œ ๋กœ๊ทธ ๋ ˆ๋ฒจ
  • ํŒŒ์ผ ์ถœ๋ ฅ
  • ํฌ๋งท ์ง€์ •

๋กœ๊ทธ ๋ ˆ๋ฒจ ์ƒ์„ธ

  1. DEBUG (10): ์ƒ์„ธํ•œ ๋””๋ฒ„๊น… ์ •๋ณด, ๊ฐœ๋ฐœ ์ค‘์— ์œ ์šฉ
  2. INFO (20): ์ผ๋ฐ˜์ ์ธ ์ •๋ณด ๋ฉ”์‹œ์ง€, ์ •์ƒ ๋™์ž‘ ํ™•์ธ
  3. WARNING (30): ์ž ์žฌ์  ๋ฌธ์ œ ์ƒํ™ฉ, ํ•˜์ง€๋งŒ ํ”„๋กœ๊ทธ๋žจ์€ ๊ณ„์† ์‹คํ–‰
  4. ERROR (40): ํŠน์ • ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์˜ค๋ฅ˜ ์ƒํ™ฉ
  5. CRITICAL (50): ํ”„๋กœ๊ทธ๋žจ ์ž์ฒด๊ฐ€ ์‹คํ–‰์„ ๊ณ„์†ํ•  ์ˆ˜ ์—†๋Š” ์‹ฌ๊ฐํ•œ ์˜ค๋ฅ˜

๊ธฐ๋ณธ ๋กœ๊น… ๊ตฌ์„ฑ ์š”์†Œ

  • Logger: ๋กœ๊น… ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฐ์ฒด
  • Handler: ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ์ ์ ˆํ•œ ๋Œ€์ƒ์œผ๋กœ ์ „๋‹ฌ
  • Formatter: ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์˜ ์ตœ์ข… ํ˜•์‹ ์ง€์ •
  • Filter: ์–ด๋–ค ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ• ์ง€ ๊ฒฐ์ •

๋กœ๊ฑฐ ์ด๋ฆ„ ํŒจํ„ด

  • __name__ ๋ณ€์ˆ˜ ์‚ฌ์šฉ: ๋ชจ๋“ˆ ์ด๋ฆ„์„ ๋กœ๊ฑฐ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ„์ธต์  ๊ตฌ์กฐ ์ƒ์„ฑ
  • ๋ฃจํŠธ ๋กœ๊ฑฐ: ์ด๋ฆ„ ์—†์ด logging.getLogger()๋กœ ์ƒ์„ฑ
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ๋กœ๊ฑฐ: logging.getLogger('app_name')
# ๊ณ„์ธต์  ๋กœ๊ฑฐ ์˜ˆ์‹œ
logger = logging.getLogger('app.module.submodule')


2๏ธโƒฃ ๋กœ๊ทธ ํ•ธ๋“ค๋Ÿฌ

๋กœ๊ทธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค์–‘ํ•œ ์ถœ๋ ฅ ๋Œ€์ƒ์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

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)

โœ… ํŠน์ง•:

  • ํŒŒ์ผ ๋กœํ…Œ์ด์…˜
  • ๋‹ค์ค‘ ํ•ธ๋“ค๋Ÿฌ
  • ๋ ˆ๋ฒจ๋ณ„ ์ฒ˜๋ฆฌ

์ฃผ์š” ํ•ธ๋“ค๋Ÿฌ ์œ ํ˜•

  1. StreamHandler: ์ŠคํŠธ๋ฆผ(๋ณดํ†ต sys.stdout, sys.stderr)์œผ๋กœ ๋กœ๊ทธ ์ถœ๋ ฅ
  2. FileHandler: ํŒŒ์ผ์— ๋กœ๊ทธ ๊ธฐ๋ก
  3. RotatingFileHandler: ํŒŒ์ผ ํฌ๊ธฐ ๊ธฐ๋ฐ˜ ๋กœํ…Œ์ด์…˜
  4. TimedRotatingFileHandler: ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ๋กœํ…Œ์ด์…˜ (์ผ๋ณ„, ์‹œ๊ฐ„๋ณ„ ๋“ฑ)
  5. SMTPHandler: ์ด๋ฉ”์ผ๋กœ ๋กœ๊ทธ ์ „์†ก
  6. SysLogHandler: ์‹œ์Šคํ…œ ๋กœ๊ทธ๋กœ ์ „์†ก
  7. HTTPHandler: HTTP ์„œ๋ฒ„๋กœ ๋กœ๊ทธ ์ „์†ก
  8. SocketHandler: TCP/IP ์†Œ์ผ“์œผ๋กœ ๋กœ๊ทธ ์ „์†ก
  9. 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)


3๏ธโƒฃ ๋กœ๊น… ์„ค์ • ํŒŒ์ผ

๋กœ๊น… ์„ค์ •์„ ์ฝ”๋“œ์™€ ๋ถ„๋ฆฌํ•˜์—ฌ ์™ธ๋ถ€ ํŒŒ์ผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

# 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('์„ค์ • ํŒŒ์ผ์—์„œ ๋กœ๋“œ๋œ ๋กœ๊ฑฐ๋กœ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก')

JSON/YAML ๊ธฐ๋ฐ˜ ์„ค์ •:

๋ณต์žกํ•œ, ๊ณ„์ธต์  ๊ตฌ์„ฑ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ 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')  # ๋” ์ƒ์„ธํ•œ ๋กœ๊น…์œผ๋กœ ์ „ํ™˜


4๏ธโƒฃ ์ปค์Šคํ…€ ๋กœ๊ฑฐ์™€ ํ•„ํ„ฐ

๋กœ๊น… ์‹œ์Šคํ…œ์„ ํ™•์žฅํ•˜์—ฌ ํŠน๋ณ„ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

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 ํ˜•์‹์œผ๋กœ ๋กœ๊ทธ ์ถœ๋ ฅํ•˜์—ฌ ๊ฒ€์ƒ‰ ๋ฐ ๋ถ„์„ ์šฉ์ด
  • ๋กœ๊ทธ ํ†ตํ•ฉ: ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์˜ ๋กœ๊ทธ๋ฅผ ์ค‘์•™ ์ง‘์ค‘์‹์œผ๋กœ ์ˆ˜์ง‘
  • ์กฐ๊ฑด๋ถ€ ๋กœ๊น…: ํŠน์ • ์กฐ๊ฑด์—์„œ๋งŒ ์ƒ์„ธ ๋กœ๊น… ํ™œ์„ฑํ™”


5๏ธโƒฃ ๋กœ๊น… ์ปจํ…์ŠคํŠธ

๋กœ๊น…์— ์ปจํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋กœ๊ทธ์˜ ๊ฐ€์น˜๋ฅผ ๋†’์ด๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.

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("๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ ์™„๋ฃŒ")


6๏ธโƒฃ ๊ตฌ์กฐํ™”๋œ ๋กœ๊น…

๋กœ๊ทธ๋ฅผ ๊ตฌ์กฐํ™”๋œ ํ˜•์‹(์ฃผ๋กœ 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)


7๏ธโƒฃ ๊ณ ๊ธ‰ ๋กœ๊น… ํŒจํ„ด

๋‹ค์–‘ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํ™ฉ์— ๋งž๋Š” ๊ณ ๊ธ‰ ๋กœ๊น… ํŒจํ„ด๊ณผ ๊ธฐ๋ฒ•์ด๋‹ค.


๋กœ๊ทธ ์ง‘๊ณ„ ๋ฐ ์ค‘์•™ํ™”

# 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)


8๏ธโƒฃ ๋กœ๊น… ๋ณด์•ˆ ๋ฐ ๊ทœ์ • ์ค€์ˆ˜

๋กœ๊น… ์‹œ์Šคํ…œ์—์„œ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๋ณดํ˜ธํ•˜๊ณ  ๊ทœ์ •์„ ์ค€์ˆ˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.


๋ฏผ๊ฐ ์ •๋ณด ๋ณดํ˜ธ

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)

๊ฐ์‚ฌ(Audit) ๋กœ๊น…

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


9๏ธโƒฃ ๋กœ๊น… ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋กœ๊น… ์‹œ์Šคํ…œ์„ ํšจ์œจ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ์„ฑ๋Šฅ ์˜ํ–ฅ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.


๋กœ๊น… ์„ฑ๋Šฅ ๊ฐœ์„  ๊ธฐ๋ฒ•:**


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 ํ™œ์šฉ


1๏ธโƒฃ0๏ธโƒฃ ๋กœ๊น… ๋ชจ๋ฒ” ์‚ฌ๋ก€

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

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

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

โœ… ๋กœ๊น… ์ฒดํฌ๋ฆฌ์ŠคํŠธ:

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


์š”์•ฝ

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

ํšจ๊ณผ์ ์ธ ๋กœ๊น…์€ ๋‹จ์ˆœํžˆ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š”๋‹ค. ๊ตฌ์กฐํ™”๋œ ์ •๋ณด, ์ปจํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ, ์„ฑ๋Šฅ ์ตœ์ ํ™”, ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ์ด ๋ชจ๋‘ ํ†ตํ•ฉ๋œ ์ „๋žต์ด ํ•„์š”ํ•˜๋‹ค. ์ด๋Ÿฌํ•œ ์ข…ํ•ฉ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ํ†ตํ•ด ๋กœ๊ทธ๋Š” ๋‹จ์ˆœํ•œ ๊ธฐ๋ก์„ ๋„˜์–ด ๊ฐ•๋ ฅํ•œ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋„๊ตฌ์ด์ž ์‹œ์Šคํ…œ ์ดํ•ด์˜ ํ•ต์‹ฌ ์ž์›์ด ๋  ์ˆ˜ ์žˆ๋‹ค.


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