Вход Python в базу данных
Я ищу способ позволить модулю python logger войти в базу данных и вернуться к файловой системе, когда db не работает.
Итак, в основном 2 вещи: как разрешить регистратору вести журнал в базе данных и как сделать так, чтобы он попадал в файл, когда база данных не работает.
6 ответов
Напишите себе обработчик, который направляет журналы в соответствующую базу данных. Когда это не удается, вы можете удалить его из списка обработчиков регистратора. Есть много способов справиться с режимами сбоя.
Черт возьми... прошло много времени, но недавно мне удалось написать собственный регистратор баз данных на Python. Так как я не смог найти ни одного примера, я решил опубликовать свой здесь. Может быть, кто-то найдет это полезным:) Работает с MS SQL.
Таблица базы данных может выглядеть так:
CREATE TABLE [db_name].[log](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[log_level] [int] NULL,
[log_levelname] [char](32) NULL,
[log] [char](2048) NOT NULL,
[created_at] [datetime2](7) NOT NULL,
[created_by] [char](32) NOT NULL,
) ON [PRIMARY]
Сам класс:
class LogDBHandler(logging.Handler):
'''
Customized logging handler that puts logs to the database.
pymssql required
'''
def __init__(self, sql_conn, sql_cursor, db_tbl_log):
logging.Handler.__init__(self)
self.sql_cursor = sql_cursor
self.sql_conn = sql_conn
self.db_tbl_log = db_tbl_log
def emit(self, record):
# Set current time
tm = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.created))
# Clear the log message so it can be put to db via sql (escape quotes)
self.log_msg = record.msg
self.log_msg = self.log_msg.strip()
self.log_msg = self.log_msg.replace('\'', '\'\'')
# Make the SQL insert
sql = 'INSERT INTO ' + self.db_tbl_log + ' (log_level, ' + \
'log_levelname, log, created_at, created_by) ' + \
'VALUES (' + \
'' + str(record.levelno) + ', ' + \
'\'' + str(record.levelname) + '\', ' + \
'\'' + str(self.log_msg) + '\', ' + \
'(convert(datetime2(7), \'' + tm + '\')), ' + \
'\'' + str(record.name) + '\')'
try:
self.sql_cursor.execute(sql)
self.sql_conn.commit()
# If error - print it out on screen. Since DB is not working - there's
# no point making a log about it to the database :)
except pymssql.Error as e:
print sql
print 'CRITICAL DB ERROR! Logging to database not possible!'
И пример использования:
import pymssql
import time
import logging
db_server = 'servername'
db_user = 'db_user'
db_password = 'db_pass'
db_dbname = 'db_name'
db_tbl_log = 'log'
log_file_path = 'C:\\Users\\Yourname\\Desktop\\test_log.txt'
log_error_level = 'DEBUG' # LOG error level (file)
log_to_db = True # LOG to database?
class LogDBHandler(logging.Handler):
[...]
# Main settings for the database logging use
if (log_to_db):
# Make the connection to database for the logger
log_conn = pymssql.connect(db_server, db_user, db_password, db_dbname, 30)
log_cursor = log_conn.cursor()
logdb = LogDBHandler(log_conn, log_cursor, db_tbl_log)
# Set logger
logging.basicConfig(filename=log_file_path)
# Set db handler for root logger
if (log_to_db):
logging.getLogger('').addHandler(logdb)
# Register MY_LOGGER
log = logging.getLogger('MY_LOGGER')
log.setLevel(log_error_level)
# Example variable
test_var = 'This is test message'
# Log the variable contents as an error
log.error('This error occurred: %s' % test_var)
Выше войдет как в базу данных, так и в файл. Если файл не нужен - пропустите строку 'logging.basicConfig(filename=log_file_path)'. Все, что зарегистрировано с помощью 'log' - будет зарегистрировано как MY_LOGGER. Если появляется какая-то внешняя ошибка (т.е. в импортированном модуле или что-то в этом роде) - ошибка будет отображаться как "root", так как logger "root" также активен и использует обработчик базы данных.
Ведение журнала Python в базе данных с помощью регистратора резервного копирования
Проблема
У меня была такая же проблема, когда я запускал проект Django на сервере, поскольку иногда вам нужно проверять журналы удаленно.
Решение
Во-первых, необходим обработчик для регистратора для вставки журналов в базу данных. До этого и поскольку мой SQL не очень хорош, мне нужна ORM, поэтому я выбрал SQLAlchemy.
модель:
# models.py
from sqlalchemy import Column, Integer, String, DateTime, Text
from sqlalchemy.ext.declarative import declarative_base
import datetime
base = declarative_base()
class Log(base):
__tablename__ = "log"
id = Column(Integer, primary_key=True, autoincrement=True)
time = Column(DateTime, nullable=False, default=datetime.datetime.now)
level_name = Column(String(10), nullable=True)
module = Column(String(200), nullable=True)
thread_name = Column(String(200), nullable=True)
file_name = Column(String(200), nullable=True)
func_name = Column(String(200), nullable=True)
line_no = Column(Integer, nullable=True)
process_name = Column(String(200), nullable=True)
message = Column(Text)
last_line = Column(Text)
Это сырье для вставки в базу данных:
#crud.py
import sqlalchemy
from .models import base
from traceback import print_exc
class Crud:
def __init__(self, connection_string=f'sqlite:///log_db.sqlite3',
encoding='utf-8',
pool_size=10,
max_overflow=20,
pool_recycle=3600):
self.connection_string = connection_string
self.encoding = encoding
self.pool_size = pool_size
self.max_overflow = max_overflow
self.pool_recycle = pool_recycle
self.engine = None
self.session = None
def initiate(self):
self.create_engine()
self.create_session()
self.create_tables()
def create_engine(self):
self.engine = sqlalchemy.create_engine(self.connection_string)
def create_session(self):
self.session = sqlalchemy.orm.Session(bind=self.engine)
def create_tables(self):
base.metadata.create_all(self.engine)
def insert(self, instances):
try:
self.session.add(instances)
self.session.commit()
self.session.flush()
except:
self.session.rollback()
raise
def __del__(self):
self.close_session()
self.close_all_connections()
def close_session(self):
try:
self.session.close()
except:
print_exc()
else:
self.session = None
def close_all_connections(self):
try:
self.engine.dispose()
except:
print_exc()
else:
self.engine = None
Обработчик:
# handler.py
from logging import Handler, getLogger
from traceback import print_exc
from .crud import Crud
from .models import Log
my_crud = Crud(
connection_string=<connection string to reach your db>,
encoding='utf-8',
pool_size=10,
max_overflow=20,
pool_recycle=3600)
my_crud.initiate()
class DBHandler(Handler):
backup_logger = None
def __init__(self, level=0, backup_logger_name=None):
super().__init__(level)
if backup_logger_name:
self.backup_logger = getLogger(backup_logger_name)
def emit(self, record):
try:
message = self.format(record)
try:
last_line = message.rsplit('\n', 1)[-1]
except:
last_line = None
try:
new_log = Log(module=record.module,
thread_name=record.threadName,
file_name=record.filename,
func_name=record.funcName,
level_name=record.levelname,
line_no=record.lineno,
process_name=record.processName,
message=message,
last_line=last_line)
# raise
my_crud.insert(instances=new_log)
except:
if self.backup_logger:
try:
getattr(self.backup_logger, record.levelname.lower())(record.msg)
except:
print_exc()
else:
print_exc()
except:
print_exc()
Тест для проверки регистратора:
# test.py
from logging import basicConfig, getLogger, DEBUG, FileHandler, Formatter
from .handlers import DBHandler
basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%d-%b-%y %H:%M:%S',
level=DEBUG)
format = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
backup_logger = getLogger('backup_logger')
file_handler = FileHandler('file.log')
file_handler.setLevel(DEBUG)
file_handler.setFormatter(format)
backup_logger.addHandler(file_handler)
db_logger = getLogger('logger')
db_handler = DBHandler(backup_logger_name='backup_logger')
db_handler.setLevel(DEBUG)
db_handler.setFormatter(format)
db_logger.addHandler(db_handler)
if __name__ == "__main__":
db_logger.debug('debug: hello world!')
db_logger.info('info: hello world!')
db_logger.warning('warning: hello world!')
db_logger.error('error: hello world!')
db_logger.critical('critical: hello world!!!!')
Вы можете видеть, что обработчик принимает регистратор резервных копий, который может использовать его в случае сбоя вставки базы данных.
Хорошим улучшением может быть вход в базу данных с помощью потоковой передачи.
Вы видели библиотеку log4mongo? https://pypi.python.org/pypi/log4mongo/ Предоставляет дескриптор Mongo. Этого отступления нет, но я полагаю, его было бы относительно легко добавить.
Я копаю это снова.
Существует решение с SqlAlchemy (пирамида не требуется для этого рецепта):
https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/logging/sqlalchemy_logger.html
И вы можете улучшить ведение журнала, добавив дополнительные поля, вот руководство: /questions/6908683/kak-dobavit-nastraivaemoe-pole-v-stroku-formata-zhurnala-python/6908691#6908691
Откат к ФС
Не уверен, что это на 100% правильно, но вы можете иметь 2 обработчика:
- обработчик базы данных (запись в БД)
- обработчик файла (запись в файл или поток)
Просто оберните DB-commit с помощью try-except
, Но имейте в виду: файл будет содержать ВСЕ записи журнала, а не только записи, для которых сбой сохранения БД.
Старый вопрос, но отбросить это для других. Если вы хотите использовать Python Logging, вы можете добавить два обработчика. Один для записи в файл, обработчик вращающегося файла. Это надежно и может быть сделано независимо от того, повышен дБ или нет. Другой может писать в другой сервис / модуль, как интеграция с pymongo.
Посмотрите в logging.config, как настроить ваши обработчики из кода или json.