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

3

我一直在尝试使用Python内置的logging库创建一个数据库处理程序。我尝试通过子类化主日志模块的“Handler”类来实现,但它会抛出许多错误,而StreamHandler类是完全正常的,并且它的编写方式与我的DatabaseHandler类相同。

我已经检查了logging库的文档,但似乎没有用于创建自定义日志处理程序的类。因此,我决定编写DatabaseHandler类,而不继承Handler类的属性,并且当我输入第一行以创建'logs'表(如果不存在)时,SQLite 3代码会抛出错误,说没有这样的表。

我的代码:

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表中。我还为该类创建了一个“emit”方法,因为我认为logging __init__.py模块文件中的Handler类在进行新的日志调用并创建LogRecord时会触发该方法。但是,问题在于:每当我在类的__init__方法上调用super().__init__(self)以继承来自Handler类的属性时,它就会抛出此异常。
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]

子类化Handler是创建自定义处理程序的正确方法。你遇到了什么错误?此外,你绝对应该调用super.__init__,否则你的处理程序甚至都没有level属性。 - undefined
@blues 每当我这样做时,我会收到一个错误,错误信息是 AttributeError: 'DatabaseHandler' object has no attribute 'level'。此外,我为该类创建了一个 emit 方法,因为我在 logging 的 __init__.py 模块文件中看到了 Handler 类有这个方法,我认为这个方法在创建新的日志调用和 LogRecord 时被调用。 - undefined
AttributeError应该通过super init调用来修复。Handler类的init方法有这行代码:self.level = _checkLevel(level) 实现emit确实是正确的做法。 - undefined
@blues追踪回溯(最近的调用在最后): 文件 "",第47行,在中 文件 "",第7行,在__init__中 文件 "/lib/python3.8/logging/__init__.py",第865行,在__init__中 self.level = _checkLevel(level) 文件 "/lib/python3.8/logging/__init__.py",第192行,在_checkLevel中 elif str(level) == level: 文件 "/lib/python3.8/logging/__init__.py",第1035行,在__repr__中 level = getLevelName(self.level) AttributeError: 'DatabaseHandler'对象没有属性'level' [程序已结束] - undefined
如果你不相信我,那就自己运行代码吧。 - undefined
由于堆栈跟踪明确显示您正在使用Python 3,因此您不应将self传递给super().__init__()。看看堆栈跟踪!super初始化程序正在尝试将您的参数(即您的DatabaseHandler实例)转换为级别字符串,而处理程序的__repr__方法正在尝试访问尚不存在的level属性... - undefined
1个回答

2

好的,你并没有那么远...

在调用__init__时出现错误是由于传递了self,它被解释为一个级别。即使这不是真正的错误,在创建表格时应该避免重复执行数据定义语言操作,并将其放在__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表中找到记录内容...

1
非常感谢,我曾经失去了希望,以为没有人愿意回答我的问题。另外,我在SQLite查询中注入参数的原因是因为我使用的是executescript SQLite 3 Connection方法,很不幸它不支持参数替换。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接