Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Базовое использование logging
basic_usage

Базовое использование logging

Logger, Handler, Formatter, Filter — основные компоненты

Базовое использование logging

Понимание основных компонентов logging — Logger, Handler, Formatter, Filter — ключ к эффективному использованию в production. Каждый компонент отвечает за свою часть обработки логов.

#1. Logger — основной объект для логирования

#1.1. Создание logger

Проблема: Нужно создать logger в модуле для логирования.

Решение: getLogger(__name__) создаёт logger с именем модуля.

import logging # В файле app/api/users.py logger = logging.getLogger(__name__) # Имя logger: 'app.api.users' # Использование logger.debug("Debug message") logger.info("Info message") logger.warning("Warning message") logger.error("Error message") logger.critical("Critical message")

#1.2. Иерархия logger'ов

Проблема: Нужно понять, как logger'ы связаны друг с другом.

Решение: Logger'ы организованы в дерево по именам.

Иерархия logger'ов

# Создание logger'ов logging.getLogger('app') # Родительский logging.getLogger('app.api') # Дочерний app logging.getLogger('app.api.users') # Дочерний app.api # Иерархия определяется по '.' # app.api.users → app.api → app → root

Правила иерархии:

  1. Имя root — корневой logger (получается через getLogger() без аргументов)
  2. app.api.users — дочерний app.api, который дочерний app
  3. Сообщения propagates вверх по дереву (если propagate=True)

#1.3. Установка уровня logger

import logging logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # Все сообщения от DEBUG и выше будут обработаны # Но могут быть отфильтрованы handler'ами

Важно: Уровень logger — это фильтр "на входе". Если сообщение не проходит этот фильтр, оно не попадает ни в один handler.

#1.4. Effective Level

Проблема: Какой уровень будет использоваться, если у logger'а не установлен свой уровень?

Решение: getEffectiveLevel() возвращает уровень ближайшего родителя с установленным уровнем.

import logging # Root logger имеет уровень WARNING по умолчанию logging.getLogger().setLevel(logging.WARNING) # app имеет свой уровень logging.getLogger('app').setLevel(logging.INFO) # app.api не имеет установленного уровня api_logger = logging.getLogger('app.api') print(api_logger.level) # 0 (NOTSET) print(api_logger.getEffectiveLevel()) # 20 (INFO) — от 'app' # app.api.users наследует от app.api → app users_logger = logging.getLogger('app.api.users') print(users_logger.getEffectiveLevel()) # 20 (INFO) — от 'app'

#1.5. Проверка включён ли уровень

Проблема: Форматирование сообщения дорогое, не хочется выполнять если уровень отключён.

Решение: isEnabledFor() проверяет перед форматированием.

# ❌ Плохо: строка формируется всегда logger.debug(f"Processing {expensive_operation()}") # ✅ Хорошо: проверка перед форматированием if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Processing {expensive_operation()}") # ✅ Хорошо: % форматирование (ленивое) logger.debug("Processing %s", expensive_operation()) # expensive_operation() вызывается только если DEBUG включён

Performance impact:

# Без isEnabledFor: 10000 вызовов = 0.5s (строки формируются) # С isEnabledFor: 10000 вызовов = 0.001s (строки не формируются)

#2. Handler — куда отправлять логи

Проблема: Нужно отправить логи в файл, консоль, email.

Решение: Handler определяет точку назначения.

#2.1. StreamHandler (консоль)

Проблема: Нужно выводить логи в консоль.

Решение: StreamHandler для stdout/stderr.

import logging import sys # Консоль (stdout) console_handler = logging.StreamHandler(sys.stdout) # Консоль (stderr) — для ошибок error_handler = logging.StreamHandler(sys.stderr) error_handler.setLevel(logging.ERROR) # Добавление к logger logger.addHandler(console_handler) logger.addHandler(error_handler)

#2.2. FileHandler (файл)

Проблема: Нужно сохранять логи в файл.

Решение: FileHandler пишет в файл.

import logging # Простой файл file_handler = logging.FileHandler('app.log') # С кодировкой file_handler = logging.FileHandler('app.log', encoding='utf-8') # В абсолютный путь file_handler = logging.FileHandler('/var/log/myapp/app.log') # Добавление к logger logger.addHandler(file_handler)

#2.3. RotatingFileHandler (ротация по размеру)

Проблема: Файл лога растёт бесконечно.

Решение: RotatingFileHandler ротирует файл при достижении размера.

from logging.handlers import RotatingFileHandler # Ротация при 10MB, хранение 5 backup файлов handler = RotatingFileHandler( 'app.log', maxBytes=10 * 1024 * 1024, # 10 MB backupCount=5 ) # Файлы: # app.log ← текущий # app.log.1 ← последний backup # app.log.2 # ... # app.log.5 ← самый старый

#2.4. TimedRotatingFileHandler (ротация по времени)

Проблема: Нужно ротировать логи по расписанию (каждый день).

Решение: TimedRotatingFileHandler ротирует по времени.

from logging.handlers import TimedRotatingFileHandler # Ротация каждый день в полночь, хранение 30 дней handler = TimedRotatingFileHandler( 'app.log', when='midnight', interval=1, backupCount=30 ) # when варианты: # 'S' — секунды # 'M' — минуты # 'H' — часы # 'D' — дни # 'W0'-'W6' — дни недели (0=понедельник) # 'midnight' — полночь

#2.5. SMTPHandler (email)

Проблема: Нужно отправлять критические ошибки по email.

Решение: SMTPHandler отправляет логи по email.

from logging.handlers import SMTPHandler # Email при ERROR и выше mail_handler = SMTPHandler( mailhost=('smtp.example.com', 587), fromaddr='app@example.com', toaddrs=['admin@example.com', 'oncall@example.com'], subject='Application Error', credentials=('username', 'password'), secure=() # TLS ) mail_handler.setLevel(logging.ERROR)

#2.6. Несколько handler'ов

Проблема: Нужно выводить логи одновременно в консоль, файл и email.

Решение: Добавить несколько handler'ов к logger.

import logging from logging.handlers import RotatingFileHandler, SMTPHandler logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # Консоль — INFO и выше console = logging.StreamHandler() console.setLevel(logging.INFO) # Файл — DEBUG и выше file_handler = RotatingFileHandler('app.log', maxBytes=10*1024*1024, backupCount=5) file_handler.setLevel(logging.DEBUG) # Email — ERROR и выше mail_handler = SMTPHandler( mailhost=('smtp.example.com', 587), fromaddr='app@example.com', toaddrs=['admin@example.com'], subject='Application Error' ) mail_handler.setLevel(logging.ERROR) # Добавление handler'ов logger.addHandler(console) logger.addHandler(file_handler) logger.addHandler(mail_handler)

Flow:

logger.error("Error") → проходит logger (DEBUG+)
                     → проходит console (INFO+) → вывод в консоль
                     → проходит file_handler (DEBUG+) → запись в файл
                     → проходит mail_handler (ERROR+) → отправка email

#2.7. Избегание дублирования handlers

Проблема: При многократном импорте модуля handlers добавляются повторно.

Решение: Проверять перед добавлением.

import logging logger = logging.getLogger(__name__) # ❌ Плохо: handler добавляется при каждом импорте logger.addHandler(logging.StreamHandler()) # ✅ Хорошо: проверка перед добавлением if not logger.handlers: logger.addHandler(logging.StreamHandler()) # ✅ Или: проверка типа if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers): logger.addHandler(logging.StreamHandler())

#3. Formatter — форматирование сообщений

Проблема: Нужно форматировать сообщения с timestamp, уровнем, именем logger.

Решение: Formatter определяет формат вывода.

#3.1. Базовый Formatter

import logging formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter)

Пример вывода:

2026-03-21 10:00:00,123 - app.api.users - INFO - User logged in

#3.2. Формат-переменные

ПеременнаяЗначениеПример
%(asctime)sTimestamp2026-03-21 10:00:00,123
%(name)sИмя loggerapp.api.users
%(levelname)sУровеньINFO, ERROR
%(message)sСообщениеUser logged in
%(filename)sИмя файлаusers.py
%(lineno)dНомер строки42
%(funcName)sФункцияcreate_user
%(process)dPID процесса12345
%(thread)dID потока98765
%(module)sМодульusers

#3.3. Кастомный формат timestamp

# ISO 8601 формат formatter = logging.Formatter( '%(asctime)s - %(message)s', datefmt='%Y-%m-%dT%H:%M:%S%z' ) # Вывод: 2026-03-21T10:00:00+0000 - User logged in # Простой формат formatter = logging.Formatter( '%(asctime)s - %(message)s', datefmt='%H:%M:%S' ) # Вывод: 10:00:00 - User logged in

#3.4. Formatter с контекстом (extra)

# Formatter с контекстными полями formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s - user_id=%(user_id)s request_id=%(request_id)s' ) # Использование logger.info( "User action completed", extra={ "user_id": 123, "request_id": "abc-xyz" } ) # Вывод: 2026-03-21 10:00:00,123 - INFO - User action completed - user_id=123 request_id=abc-xyz

Важно: Если extra поле не передано, а formatter его ожидает — KeyError.

#3.5. Разные formatter для разных handler'ов

Проблема: В консоли нужен краткий формат, в файле — подробный.

Решение: Разные formatter для разных handler'ов.

# Консоль — кратко console_formatter = logging.Formatter( '%(levelname)s: %(message)s' ) console_handler.setFormatter(console_formatter) # Файл — подробно file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(filename)s:%(lineno)d' ) file_handler.setFormatter(file_formatter) # Email — с traceback email_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s\n%(message)s\n\n%(exc_text)s' ) email_handler.setFormatter(email_formatter)

#4. Filter — фильтрация сообщений

Проблема: Нужно фильтровать сообщения по кастомным условиям.

Решение: Filter решает, пропустить ли LogRecord.

#4.1. Встроенная фильтрация по уровню

# Handler фильтрует по уровню handler.setLevel(logging.WARNING) # Пропускает только WARNING, ERROR, CRITICAL

#4.2. Кастомный Filter

import logging class SensitiveDataFilter(logging.Filter): def filter(self, record): # Пропускаем логи с чувствительными данными message = record.getMessage() if 'password' in message.lower(): return False if 'secret' in message.lower(): return False return True logger.addFilter(SensitiveDataFilter())

#4.3. Filter по имени logger

# Пропускать только логи от определённых модулей class ModuleFilter(logging.Filter): def __init__(self, allowed_modules): super().__init__() self.allowed_modules = allowed_modules def filter(self, record): return any( record.name.startswith(mod) for mod in self.allowed_modules ) logger.addFilter(ModuleFilter(['app.api', 'app.db']))

#4.4. Filter на handler

Проблема: Нужно фильтровать сообщения для конкретного handler.

Решение: Добавить filter к handler.

# Только ERROR для email mail_handler.addFilter(lambda record: record.levelno >= logging.ERROR) # Исключить health check логи из файла class HealthCheckFilter(logging.Filter): def filter(self, record): return 'health check' not in record.getMessage().lower() file_handler.addFilter(HealthCheckFilter())

#4.5. Filter для добавления контекста

import logging from contextvars import ContextVar # ContextVar для хранения request_id request_id_var: ContextVar[str] = ContextVar('request_id', default='') class RequestContextFilter(logging.Filter): """Добавляет request_id из ContextVar ко всем логам.""" def filter(self, record): record.request_id = request_id_var.get() return True # Применение logger = logging.getLogger('app') logger.addFilter(RequestContextFilter()) # В middleware request_id_var.set('abc-123') logger.info("Processing request") # Автоматически включает request_id

#5. LoggerAdapter — адаптер с контекстом

Проблема: Нужно добавлять контекст (user_id, request_id) ко всем сообщениям без явного extra={}.

Решение: LoggerAdapter автоматически добавляет контекст.

#5.1. Создание адаптера

import logging logger = logging.getLogger(__name__) # Создание адаптера с контекстом adapter = logging.LoggerAdapter( logger, extra={ "user_id": 123, "request_id": "abc-xyz" } ) # Использование как обычный logger adapter.info("User logged in") # Вывод: ... - User logged in - user_id=123 request_id=abc-xyz adapter.debug("Processing data") # Контекст добавляется автоматически

#5.2. Адаптер в request

Проблема: Нужно добавить request_id ко всем логам запроса.

Решение: Создать адаптер при начале запроса.

# middleware.py import logging import uuid class RequestLoggingMiddleware: def __init__(self, app): self.app = app def __call__(self, request): # Генерация request_id request_id = str(uuid.uuid4()) request.request_id = request_id # Создание logger с контекстом logger = logging.getLogger('app.api') adapter = logging.LoggerAdapter( logger, extra={ "request_id": request_id, "method": request.method, "path": request.path } ) adapter.info("Request started") try: response = self.app(request) adapter.info("Request completed", extra={"status": response.status}) return response except Exception as e: adapter.exception("Request failed") raise

#5.3. Кастомный LoggerAdapter

Проблема: Нужно динамически вычислять контекст.

Решение: Переопределить process().

import logging import threading class DynamicContextAdapter(logging.LoggerAdapter): def process(self, msg, kwargs): # Добавляем динамический контекст kwargs.setdefault('extra', {}) kwargs['extra'].update(self.extra) kwargs['extra']['thread_name'] = threading.current_thread().name return msg, kwargs # Использование adapter = DynamicContextAdapter( logger, extra={"user_id": 123} )

#6. Полная настройка logger

Проблема: Нужно настроить logger для production приложения.

Решение: Комбинация всех компонентов.

import logging from logging.handlers import RotatingFileHandler, SMTPHandler import sys def setup_logger(name: str) -> logging.Logger: """Настройка logger для production.""" logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) # Избегаем дублирования handler'ов if logger.handlers: return logger # Formatter'ы console_formatter = logging.Formatter( '%(levelname)s: %(message)s' ) file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(filename)s:%(lineno)d' ) email_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s\n%(message)s\n\n%(exc_text)s' ) # Console handler (INFO+) console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) console_handler.setFormatter(console_formatter) # File handler (DEBUG+, ротация) file_handler = RotatingFileHandler( 'app.log', maxBytes=10 * 1024 * 1024, backupCount=5 ) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(file_formatter) # Email handler (ERROR+, критические ошибки) email_handler = SMTPHandler( mailhost=('smtp.example.com', 587), fromaddr='app@example.com', toaddrs=['admin@example.com'], subject='Application Error' ) email_handler.setLevel(logging.ERROR) email_handler.setFormatter(email_formatter) # Фильтр для исключения health check из email class HealthCheckFilter(logging.Filter): def filter(self, record): return 'health check' not in record.getMessage().lower() email_handler.addFilter(HealthCheckFilter()) # Добавление handler'ов logger.addHandler(console_handler) logger.addHandler(file_handler) logger.addHandler(email_handler) return logger # Использование logger = setup_logger(__name__) logger.info("Application started")

#7. Best Practices

#7.1. Избегайте дублирования handler'ов

# ❌ Плохо: handler добавляется при каждом импорте logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) # ✅ Хорошо: проверка перед добавлением logger = logging.getLogger(__name__) if not logger.handlers: logger.addHandler(logging.StreamHandler()) # ✅ Или: use getHandler if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers): logger.addHandler(logging.StreamHandler())

#7.2. Не логируйте чувствительные данные

# ❌ Плохо logger.info(f"User login: {username}, password: {password}") # ✅ Хорошо logger.info(f"User login attempt", extra={"username": username}) # password не логируется

#7.3. Используйте exc_info для исключений

# ❌ Плохо try: risky_operation() except Exception as e: logger.error(f"Error: {e}") # ✅ Хорошо try: risky_operation() except Exception: logger.error("Operation failed", exc_info=True) # или logger.exception("Operation failed") # Только внутри except

#7.4. Именуйте logger по модулю

# ✅ Хорошо logger = logging.getLogger(__name__) # ❌ Плохо logger = logging.getLogger('my_logger') # Одинаковое имя везде

#7.5. Используйте propagate=False для application logger'ов

# ✅ Хорошо: предотвращает дублирование logger = logging.getLogger('app') logger.addHandler(handler) logger.propagate = False # ❌ Плохо: логи дублируются в root logger logger = logging.getLogger('app') logger.addHandler(handler) logger.propagate = True # По умолчанию

#Резюме

#Компоненты logging

КомпонентОтветственностьПример
LoggerТочка входа, фильтрация по уровнюgetLogger(__name__)
HandlerКуда отправлять логиStreamHandler, FileHandler
FormatterФормат вывода%(asctime)s - %(message)s
FilterКастомная фильтрацияSensitiveDataFilter
LoggerAdapterАвтоматический контекстLoggerAdapter(logger, extra={})

#Уровни и фильтрация

Сообщение → Logger.setLevel() → Handler.setLevel() → Filter → Вывод
  • Logger level — фильтр "на входе"
  • Handler level — фильтр "на выходе"
  • Filter — кастомные условия

#Иерархия logger'ов

app.api.users → app.api → app → root
  • Propagation — передача сообщений вверх по дереву
  • Effective level — уровень ближайшего родителя с установленным уровнем
  • propagate=False — отключить propagation

#Best Practices

  1. Используйте getLogger(__name__) для создания logger'ов
  2. Проверяйте if not logger.handlers перед добавлением
  3. Добавляйте контекст через extra={}
  4. Используйте exc_info=True для ошибок
  5. Не логируйте чувствительные данные
  6. Используйте propagate=False для application logger'ов
  7. Настраивайте разные уровни для разных handlers

Далее: Конфигурация логирования