предисловие
Название этой статьи "Полный". Так называемая «полнота» означает выражение: извлечение набора минимальных комбинаций опыта, которые могут быть быстро применены к технике, могут работать и даже могут работать идеально. Эта статья посвящена тому, «как работать идеально».
первоначальное намерение
Самая оригинальная изначальная интенция звучит так: «Настоящее несовершенно и бессистемно».
Один из них: официальное использование Python библиотеки ведения журналов недостаточно «тщательно». Мы можем найти ценные сведения о библиотеке ведения журналов в официальной документации, вероятно, следующие:
- Документация библиотеки в основном знакомит с некоторыми классами и тем, как составлена библиотека протоколирования, с некоторыми фрагментарными методами использования, вкрапленными в середину, но до сих пор нет систематического введения в то, как ее использовать. они соответственно:
- 16.6. logging — средство ведения журнала для Python
- 16.7. logging.config — конфигурация ведения журнала
- 16.8. logging.handlers — обработчики протоколирования
- Два HOWTO. Подробно описаны состав и использование библиотеки протоколирования.Преимущества очевидны: в основном все охвачено. Недостатки тоже очевидны: все детализировано, и каждая часть действует одинаково, из-за чего люди не могут найти ключевой момент, и в нем легко заблудиться. они соответственно:
- Logging HOWTO
- Logging Cookbook
Во-вторых, новичку, вероятно, потребуются годы опыта, чтобы понять, что журнал — лучший способ отладки. В повседневной разработке соотношение одношаговой отладки и отладки по логу составляет примерно 1:9 (у меня лично 0:10). Новичкам обычно нравится использовать одношаговую отладку или отладку на основе печати, оба из которых относительно неэффективны, а именно:
- отладка печати. Не хочу вводить, недостатков много, не буду говорить, и это всем понятно. Для временного использования.
- Одношаговая отладка. Преимущества очевидны: можно сделать один шаг, можно увидеть ситуацию каждого шага. Недостатки тоже очевидны: низкая эффективность и отсутствие решения в случае многопоточности. Подходит для мелкосерийного использования.
Текущая ситуация
В текущей ситуации, как упоминалось выше, все не уделяют должного внимания роли отладки лога, а в официальной библиотеке логирования также отсутствует более систематизированный «минимально доступный туториал».
HOW TO DO
Начните с требований, то есть: выведите дизайн интерфейса с вызывающей стороны.
Вызывающий, вероятно, захочет использовать его вот так (как вызывающий, как правило, надеются, что интерфейс будет как можно более простым и компактным):
log_factory.SOME_LOGGER.info('MY LOG MSG')
Таким образом, мы можем сделать «log_factory» в пакет (модуль также может, но мне нравится принцип «Package организованный проект» на языке GO), «uverage_logger», «uver_logger», мы можем использовать один пример, однако Python имеет глобальную переменную, мы можем использовать глобальные переменные.
Кроме того, более интуитивная идея такова: в логе должен быть соответствующий конфигурационный файл, но Python — это скриптовый язык,Файл исходного кода скриптового языка по своей природе является конфигурационным файлом (поскольку скриптовый язык вообще не нуждается в компиляции, изменение исходного кода может быть быстро отправлено в онлайн для проверки, а также может быть обновлено в горячем режиме).
Таким образом, наш каталог можно спланировать следующим образом:
common_libs/
__init__.py
log_factory/
__init__.py
代码可以直接写在这里,或者拆分成多个 py 文件,反正对外也就提供一个『log_factory』的命名空间
Основное содержание следующее:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# author: he.zhiming
#
from __future__ import unicode_literals, absolute_import
import logging
import logging.config
import logging.handlers
from datetime import datetime
import os
class _InfoFilter(logging.Filter):
def filter(self, record):
"""only use INFO
筛选, 只需要 INFO 级别的log
:param record:
:return:
"""
if logging.INFO <= record.levelno < logging.ERROR:
# 已经是INFO级别了
# 然后利用父类, 返回 1
return super().filter(record)
else:
return 0
def _get_filename(*, basename='app.log', log_level='info'):
date_str = datetime.today().strftime('%Y%m%d')
pidstr = str(os.getpid())
return ''.join((
date_str, '-', pidstr, '-', log_level, '-', basename,))
class _LogFactory:
# 每个日志文件,使用 2GB
_SINGLE_FILE_MAX_BYTES = 2 * 1024 * 1024 * 1024
# 轮转数量是 10 个
_BACKUP_COUNT = 10
# 基于 dictConfig,做再次封装
_LOG_CONFIG_DICT = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
# 开发环境下的配置
'dev': {
'class': 'logging.Formatter',
'format': ('%(levelname)s %(asctime)s %(created)f %(name)s %(module)s [%(processName)s %(threadName)s] '
'[%(filename)s %(lineno)s %(funcName)s] %(message)s')
},
# 生产环境下的格式(越详细越好)
'prod': {
'class': 'logging.Formatter',
'format': ('%(levelname)s %(asctime)s %(created)f %(name)s %(module)s %(process)d %(thread)d '
'%(filename)s %(lineno)s %(funcName)s %(message)s')
}
# ? 使用UTC时间!!!
},
# 针对 LogRecord 的筛选器
'filters': {
'info_filter': {
'()': _InfoFilter,
}
},
# 处理器(被loggers使用)
'handlers': {
'console': { # 按理来说, console只收集ERROR级别的较好
'class': 'logging.StreamHandler',
'level': 'ERROR',
'formatter': 'dev'
},
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': _get_filename(log_level='info'),
'maxBytes': _SINGLE_FILE_MAX_BYTES, # 2GB
'encoding': 'UTF-8',
'backupCount': _BACKUP_COUNT,
'formatter': 'dev',
'delay': True,
'filters': ['info_filter', ] # only INFO, no ERROR
},
'file_error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': _get_filename(log_level='error'),
'maxBytes': _SINGLE_FILE_MAX_BYTES, # 2GB
'encoding': 'UTF-8',
'backupCount': _BACKUP_COUNT,
'formatter': 'dev',
'delay': True,
},
},
# 真正的logger(by name), 可以有丰富的配置
'loggers': {
'SAMPLE_LOGGER': {
# 输送到3个handler,它们的作用分别如下
# 1. console:控制台输出,方便我们直接查看,只记录ERROR以上的日志就好
# 2. file: 输送到文件,记录INFO以上的日志,方便日后回溯分析
# 3. file_error:输送到文件(与上面相同),但是只记录ERROR级别以上的日志,方便研发人员排错
'handlers': ['console', file', 'file_error'],
'level': 'INFO'
},
},
}
logging.config.dictConfig(_LOG_CONFIG_DICT)
@classmethod
def get_logger(cls, logger_name):
return logging.getLogger(logger_name)
# 一个示例
SAMPLE_LOGGER = _LogFactory.get_logger('SAMPLE_LOGGER')
# 示例——debugger,需要先配置好(如同SAMPLE_LOGGER一样)
DEBUGGER = _LogFactory.get_logger('CONSOLE')
# 软件项目一般是分层的,所以可以每一层放置一个logger,各司其职,这里是一个示例
SOME_BASE_LIB_LOGGER = _LogFactory.get_logger('SOME_BASE_LIB_LOGGER')
Несколько лучших практических советов
Настройте несколько обработчиков для одного и того же регистратора.
Один обработчик подходит для временного устранения неполадок, некоторые обработчики подходят для постоянной записи, некоторые обработчики записывают все в деталях, а некоторые обработчики записывают только проблемное содержимое (например, ERROR).
дизайн формата контента
Есть несколько требований:
- Может отслеживать процессы и потоки (должен иметь возможность отслеживать потоки, необходима многопоточность)
- Можно отследить количество строк ошибок
- Формат стандартизирован и един
Поэтому мы разработали следующий формат:
INFO 2018-05-18 16:42:56,637 1526632976.637384 DEBUGGER __main__ 73580 52688
__main__.py 29 test_func GOT RESULT. ['HELLO-WORLD FROM logginglib_project.business_layer.core.CoreUtils#get_hellowolrd']
分别对应
level date_time timestamp logger_name python_module process_id thread_id filename line_number function_name log_message
настройка имени файла журнала
Например20180518-73580-info-debug_INFO.log
, вам не нужно думать головой, вы знаете, что это за файл
Просто говори, не практикуй фальшивую ручку
Просто говорите и не тренируйтесь с поддельным дескриптором, напишите код сами и «ешьте корм для собак», демонстрация выглядит следующим образом:
более продвинутые требования
Обработчик уровня INFO, используйте только журналы «INFO
Используя концепцию фильтра, предоставляемую библиотекой протоколирования, ее можно легко реализовать:
先实现 Filter
class _InfoFilter(logging.Filter):
def filter(self, record):
"""only use INFO
筛选, 只需要 INFO 级别的log
:param record:
:return:
"""
if logging.INFO <= record.levelno < logging.ERROR:
# 已经是INFO级别了
# 然后利用父类, 返回 1
return super().filter(record)
else:
return 0
然后适配到Handler上面
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': _get_filename(log_level='info'),
'maxBytes': _SINGLE_FILE_MAX_BYTES, # 2GB
'encoding': 'UTF-8',
'backupCount': _BACKUP_COUNT,
'formatter': 'dev',
'delay': True,
'filters': ['info_filter', ] # only INFO, no ERROR
},
Другие соображения
Не обязательно подходит для фреймворков (которые предоставляют полные спецификации ведения журнала), таких как Django.
Полная спецификация журнала, предоставляемая Django, общим проектом Django, может соответствовать спецификации фреймворка (например, Django, вам необходимо настроить LOGGING в файле настроек).
Сколько регистраторов должно быть настроено
Как правило, в соответствии со своими потребностями, у меня есть особенно хороший способ: иерархическая структура проекта программного обеспечения (программное обеспечение иерархично, это должно быть здравым смыслом), каждый уровень настраивает регистратор, поэтому он не будет путаться.
Является ли процесс библиотеки протоколирования безопасным или потокобезопасным?
является потокобезопасным, но не процессно-безопасным. Но это легко решается, то есть: каждое имя файла может иметь pid, чтобы каждый процесс всегда соответствовал своему файлу (см. использование _get_filename).
Закрепить наши достижения
Станьте репозиторием на GitHub, добро пожаловать в звезду.
Ссылка: https://github.com/hezhiming/py_logging_usage/tree/master