如何为我的 Python 记录器创建 SQLite 3 数据库处理程序?

How do I create an SQLite 3 database handler for my Python logger?

我一直在尝试使用 Python 中的内置日志库为我的记录器创建一个数据库处理程序。我试图通过 subclassing 主日志记录模块的“Handler”class 来做到这一点,但是当 StreamHandler class 完美时,它会抛出很多错误很好,它的写法就像我的 DatabaseHandler class 的写法一样。

我查看了日志库的文档,似乎没有 class 用于创建自定义日志处理程序。所以,我决定编写 DatabaseHandler class 而不从 Handler class 继承属性,SQLite 3 代码抛出一个错误,说没有这样的 table 作为 'logs' table 当我在第一行创建 table 如果它不存在。

我的代码:

from logging import getLogger, StreamHandler, Formatter, Handler, NOTSET, getLevelName
from datetime import datetime as date_time
from sqlite3 import connect

class DatabaseHandler(Handler):
    def __init__(self, db_file):
        super().__init__(self)
        self.db_file = db_file
        self.db_file = connect(self.db_file)
    def emit(self, record):
            """
            Conditionally emit the specified logging record.
    
            Emission depends on filters which may have been added to the handler.
            Wrap the actual emission of the record with acquisition/release of
            the I/O thread lock. Returns whether the filter passed the record for
            emission.
            """
            self.db_file.executescript(
                'CREATE TABLE IF NOT EXISTS logs (date TEXT, '
                'time TEXT, lvl INTEGER, lvl_name TEXT, msg TEXT, '
                'logger TEXT, lineno INTEGER);'
                'INSERT INTO logs VALUES ("%s", "%s", %s, "%s", "%s", "%s", %s)' % (
                    date_time.now().strftime('%A, the %d of %B, %Y'),
                    date_time.now().strftime('%I:%M %p'),
                    record.levelno,
                    record.level,
                    record.msg,
                    record.name,
                    record.lineno
                    )
            )
            self.db_file.commit()
            self.db_file.close()

logger = getLogger(__name__)

logger_formatter = Formatter(
    fmt = '<LVL: %(levelno)s (%(levelname)s), LOGGER: %(name)s> - "%(message)s at %(asctime)s"',
    datefmt = '%I:%M %p on %A, the %d of %B, %Y'
)

logger_stream_handler = StreamHandler()

logger_stream_handler.setFormatter(logger_formatter)
logger_stream_handler.setLevel(10)
logger_database_handler = DatabaseHandler('test.db')

logger.addHandler(logger_stream_handler)
logger.addHandler(logger_database_handler)

logger.log(
    msg = 'Something happened',
    level = 10
)

print(connect('test.db').execute('SELECT name FROM sqlite_master WHERE type = "table"').fetchall())

它的工作原理是将其添加为记录器的处理程序,然后每次记录某些内容并创建 LogRecord 时,它还将存储 LogRecord 在数据库的 logs table.

我还为 class 创建了一个“emit”方法,因为我认为这是 Handler class 在日志记录 __init__.py 模块文件在进行新的日志记录调用并创建 LogRecord 时触发。

但是,问题来了:每当我在 class' __init__ 方法上调用 super().__init__(self) 以从 Handler class 继承属性时,它抛出这个异常:

Traceback (most recent call last):
  File "<string>", line 47, in <module>
  File "<string>", line 7, in _init_
  File"/lib/python3.8/logging/_init_.py", line 865, in _init_
     self.level = checkLevel(level) 
  File "/lib/python3.8/logging/__init_.py", line 192, in checkLevel
     elif str(level) == level:
  File "/lib/python3.8/logging/__init_.py", line 1035, in _repr_
     level = getLevelName(self.level)
AttributeError: 'DatabaseHandler' object has no attribute 'level'

[Program finished]

[Program finished]

好吧,你没那么远...

调用__init__的错误是由传递self引起的,它被解释为级别。即使这不是真正的错误,创建 table 也是一种数据定义语言操作,不应在每条消息上重复:它应该进入 __init__ 方法:

def __init__(self, db_file):
    super().__init__()
    self.db_file = db_file
    self.db_file = connect(self.db_file)
    self.db_file.execute('CREATE TABLE IF NOT EXISTS logs (date TEXT, '
            'time TEXT, lvl INTEGER, lvl_name TEXT, msg TEXT, '
            'logger TEXT, lineno INTEGER)')

稍后在 emit 中,您传递了一个 record.level 参数,该参数应该是 record.levelname。而且你不应该在 emit 方法中关闭数据库:一个记录器应该能够记录多条消息!此外,您在查询本身中注入了参数,这很糟糕,因为几十年来它一直是 SQL 注入攻击的原因。您应该使用参数化查询:

        self.db_file.execute(
            'INSERT INTO logs VALUES (:1,:2,:3,:4, :5, :6, :7)', (
                date_time.now().strftime('%A, the %d of %B, %Y'),
                date_time.now().strftime('%I:%M %p'),
                record.levelno,
                record.levelname,
                record.msg,
                record.name,
                record.lineno
                )
        )
        self.db_file.commit()

并且由于默认级别是 NOTSET,您应该在记录器及其处理程序上设置它:

logger = getLogger(__name__)
logger.setLevel(10)

logger_database_handler = DatabaseHandler('test.db')

logger.addHandler(logger_database_handler)
logger_database_handler.setLevel(10)

logger.log(
    msg = 'Something happened',
    level = 10
)

完成这些更改后,您应该会在 logs table...

中找到记录内容