云南网站建设,企业信息化软件定制开发

专业提供昆明网站建设, 昆明软件开发, 云南网站建设,企业信息化软件定制开发服务免费咨询QQ932256355

博客园 首页 新随笔 联系 订阅 管理
  144 随笔 :: 4 文章 :: 1 评论 :: 639 阅读

Python 日志处理全解析:从基础到进阶实战教程

在 Python 编程领域,日志处理是保障程序稳定运行、高效调试以及深入分析的关键技术。本教程深入探究 Python 日志处理的各个层面,涵盖多模块、多线程环境下的日志运用,多种处理器和格式化器的配置技巧,自定义处理级别、日志配置服务器的应用,以及应对日志阻塞和网络日志收发等进阶技术。通过丰富的代码示例、清晰的图文讲解,助力读者全面掌握日志处理技术。同时,分享日志处理的最佳实践,并结合实际项目案例,让读者切实了解其在真实场景中的应用。

多模块中的日志使用

  • 原理:在 Python 中,无论在同一个模块内还是跨模块调用logging.getLogger('someLogger'),返回的都是同一个 logger 对象的引用。这使得应用程序可以在一个模块中定义和配置父 logger,在其他模块中创建子 logger,子 logger 的调用会传递给父 logger。

示例

  • 主模块(main_module.py)
import logging
import auxiliary_module
# 创建'spam_application'日志记录器
logger = logging.getLogger('spam_application')
logger.setLevel(logging.DEBUG)
# 创建可记录调试消息的文件处理器
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# 创建具有更高日志层级的控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# 创建格式化器并将其添加到处理器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 将处理器添加到日志记录器
logger.addHandler(fh)
logger.addHandler(ch)
logger.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
logger.info('created an instance of auxiliary_module.Auxiliary')
logger.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
logger.info('finished auxiliary_module.Auxiliary.do_something')
logger.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
logger.info('done with auxiliary_module.some_function()')
  • 辅助模块(auxiliary_module.py)
import logging
# 创建日志记录器
module_logger = logging.getLogger('spam_application.auxiliary')
class Auxiliary:
def __init__(self):
self.logger = logging.getLogger('spam_application.auxiliary.Auxiliary')
self.logger.info('creating an instance of Auxiliary')
def do_something(self):
self.logger.info('doing something')
a = 1 + 1
self.logger.info('done doing something')
def some_function():
module_logger.info('received a call to "some_function"')

运行main_module.py后,spam.log文件中会记录详细的日志信息,包括各个操作的时间、模块名、日志级别和具体消息;当有错误发生时,控制台会输出错误级别的日志。

  • 扩展:在大型项目中,合理利用多模块日志可以清晰地追踪不同模块的运行状态。例如,在一个电商系统中,用户模块、订单模块、支付模块等都可以有各自的日志记录器,方便排查问题。在多模块开发时,为每个模块的日志记录器命名时,采用统一的命名规范,如项目名.模块名的形式,这样在查看日志时能快速定位到日志来源。在电商系统中,假设项目名为ecommerce,用户模块的日志记录器可命名为ecommerce.user。示例代码如下:
# 用户模块(user_module.py)
import logging
user_logger = logging.getLogger('ecommerce.user')
def register_user(username, password):
user_logger.info(f'用户 {username} 开始注册')
# 注册逻辑代码
user_logger.info(f'用户 {username} 注册成功')
def login_user(username, password):
user_logger.info(f'用户 {username} 开始登录')
# 登录逻辑代码
user_logger.info(f'用户 {username} 登录成功')
# 订单模块(order_module.py)
import logging
order_logger = logging.getLogger('ecommerce.order')
def create_order(user_id, product_list):
order_logger.info(f'用户 {user_id} 开始创建订单,商品列表: {product_list}')
# 创建订单逻辑代码
order_logger.info(f'用户 {user_id} 订单创建成功')
def cancel_order(order_id):
order_logger.info(f'开始取消订单 {order_id}')
# 取消订单逻辑代码
order_logger.info(f'订单 {order_id} 已取消')

多线程中的日志记录

  • 原理:多线程记录日志无需特殊处理,各个线程的日志会交替输出。
  • 示例
import logging
import threading
import time
def worker(arg):
while not arg['stop']:
logging.debug('Hi from myfunc')
time.sleep(0.5)
def main():
logging.basicConfig(level=logging.DEBUG, format='%(relativeCreated)6d%(threadName)s %(message)s')
info = {'stop': False}
thread = threading.Thread(target=worker, args=(info,))
thread.start()
while True:
try:
logging.debug('Hello from main')
time.sleep(0.75)
except KeyboardInterrupt:
info['stop'] = True
break
thread.join()
if __name__ == '__main__':
main()

运行上述代码,控制台会交替输出主线程和工作线程的日志信息,展示不同线程的日志交替输出情况。

  • 扩展:在高并发的 Web 应用中,多线程日志记录有助于了解各个线程的执行情况,比如在处理多个用户请求时,每个请求可能由一个线程处理,通过日志可以查看每个线程的处理过程。在多线程日志记录时,为避免日志混乱,可在日志格式中加入线程 ID,方便区分不同线程产生的日志。以下是修改后的示例代码:
import logging
import threading
import time
def worker(arg):
while not arg['stop']:
logging.debug(f'线程 {threading.get_ident()} - Hi from myfunc')
time.sleep(0.5)
def main():
logging.basicConfig(level=logging.DEBUG, format='%(relativeCreated)6d - %(thread)d - %(threadName)s - %(message)s')
info = {'stop': False}
thread = threading.Thread(target=worker, args=(info,))
thread.start()
while True:
try:
logging.debug(f'线程 {threading.get_ident()} - Hello from main')
time.sleep(0.75)
except KeyboardInterrupt:
info['stop'] = True
break
thread.join()
if __name__ == '__main__':
main()

多个 handler 和多种 formatter

  • 原理:日志是普通的 Python 对象,addHandler()方法可添加多个日志处理器。通过配置不同的处理器,可以实现将不同级别的日志信息输出到不同的目标,如将严重错误信息记入文本文件,一般错误或其他级别信息输出到控制台。

  • 示例

import logging
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# 创建可记录调试消息的文件处理器
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# 创建具有更高日志层级的控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# 创建格式化器并将其添加到处理器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# 将处理器添加到日志记录器
logger.addHandler(ch)
logger.addHandler(fh)
# '应用程序'代码
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

运行代码后,spam.log文件会记录所有级别的日志,控制台则只会输出错误及以上级别的日志。

  • 扩展
    在实际项目中,还可以根据需求添加更多类型的处理器,如将日志发送到远程服务器的SocketHandler,或者用于邮件通知的SMTPHandler等。在使用多个处理器时,要注意处理器的优先级和日志级别设置,避免出现重复记录或记录丢失的情况。以下是添加SocketHandlerSMTPHandler的示例代码:
import logging
import logging.handlers
logger = logging.getLogger('advanced_example')
logger.setLevel(logging.DEBUG)
# 文件处理器
fh = logging.FileHandler('advanced.log')
fh.setLevel(logging.DEBUG)
# 控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# 远程服务器处理器
socket_handler = logging.handlers.SocketHandler('remote_server_ip', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
socket_handler.setLevel(logging.INFO)
# 邮件处理器
smtp_handler = logging.handlers.SMTPHandler(
mailhost=('smtp.example.com', 587),
fromaddr='your_email@example.com',
toaddrs=['admin@example.com'],
subject='日志通知',
credentials=('your_email@example.com', 'your_password')
)
smtp_handler.setLevel(logging.CRITICAL)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
socket_handler.setFormatter(formatter)
smtp_handler.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
logger.addHandler(socket_handler)
logger.addHandler(smtp_handler)
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

在多个地方记录日志

  • 原理:通过配置不同的处理器和格式化器,可以将日志以不同格式写入控制台和文件,并且根据日志级别进行过滤。
  • 示例
import logging
# 设置日志记录到文件
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s',
datefmt='%m-%d %H:%M', filename='/tmp/myapp.log', filemode='w')
# 定义一个将INFO或更高层级消息写到sys.stderr的处理器
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 设置一个适用于控制台的更简单格式
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# 告诉处理器使用此格式
console.setFormatter(formatter)
# 将处理器添加到根日志记录器
logging.getLogger('').addHandler(console)
# 现在我们可以写入根记录器或任何其他记录器。首先是根记录器...
logging.info('Jackdaws love my big sphinx of quartz.')
# 现在,定义几个可以代表你的应用程序中不同组成部分的其他日志记录器
logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')
logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

运行后,控制台会输出 INFO 及以上级别的日志,/tmp/myapp.log文件会记录 DEBUG 及以上级别的日志。

  • 扩展:可以根据项目需求进一步细化日志记录的规则,比如按日期分割日志文件,或者为不同模块的日志设置不同的输出路径。在按日期分割日志文件时,使用logging.handlers.TimedRotatingFileHandler,可以方便地实现每天、每周或每月生成新的日志文件,便于管理和查找历史日志。示例代码如下:
import logging
from logging.handlers import TimedRotatingFileHandler
# 按天分割日志文件
file_handler = TimedRotatingFileHandler('daily_log.log', when='D', interval=1, backupCount=7)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s'))
root_logger = logging.getLogger('')
root_logger.setLevel(logging.DEBUG)
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler)
logging.debug('debug message')
logging.info('info message')
logging.warning('warn message')
logging.error('error message')

自定义处理级别

  • 原理:使用过滤器可以实现不同于标准级别处理方式的自定义处理。例如,将不同级别的消息发送到不同的目标。
  • 示例
import json
import logging
import logging.config
def filter_maker(level):
level = getattr(logging, level)
def filter(record):
return record.levelno <= level
return filter
CONFIG = '''
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(levelname)-8s - %(message)s"
}
},
"filters": {
"warnings_and_below": {
"()": "__main__.filter_maker",
"level": "WARNING"
}
},
"handlers": {
"stdout": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "simple",
"stream": "ext://sys.stdout",
"filters": ["warnings_and_below"]
},
"stderr": {
"class": "logging.StreamHandler",
"level": "ERROR",
"formatter": "simple",
"stream": "ext://sys.stderr"
},
"file": {
"class": "logging.FileHandler",
"formatter": "simple",
"filename": "app.log",
"mode": "w"
}
},
"root": {
"level": "DEBUG",
"handlers": [
"stderr",
"stdout",
"file"
]
}
}
'''
logging.config.dictConfig(json.loads(CONFIG))
logging.debug('A DEBUG message')
logging.info('An INFO message')
logging.warning('A WARNING message')
logging.error('An ERROR message')
logging.critical('A CRITICAL message')

运行代码后,stdout.log文件会记录 INFO 和 WARNING 级别的日志,stderr.log文件会记录 ERROR 和 CRITICAL 级别的日志,app.log文件会记录所有级别的日志。

  • 扩展:在复杂的项目中,自定义处理级别可以更好地控制日志的输出,比如在一个金融系统中,根据交易的重要性和风险级别来设置不同的日志处理规则。在自定义处理级别时,要确保过滤器函数的逻辑准确无误,避免因过滤错误导致重要日志丢失或错误输出。假设在金融系统中,交易金额大于 10000 元的操作记录为 CRITICAL 级别,示例代码如下:
import logging
import logging.config
def financial_filter(record):
if hasattr(record, 'transaction_amount') and record.transaction_amount > 10000:
record.levelno = logging.CRITICAL
record.levelname = logging.getLevelName(logging.CRITICAL)
return True
CONFIG = '''
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"financial": {
"format": "%(asctime)s - %(levelname)s - %(message)s"
}
},
"filters": {
"financial_transaction": {
"()": "__main__.financial_filter"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "financial",
"filters": ["financial_transaction"]
},
"file": {
"class": "logging.FileHandler",
"level": "DEBUG",
"formatter": "financial",
"filename": "financial.log",
"filters": ["financial_transaction"]
}
},
"root": {
"level": "DEBUG",
"handlers": [
"console",
"file"
]
}
}
'''
logging.config.dictConfig(json.loads(CONFIG))
# 模拟交易操作日志记录
transaction_log = logging.getLogger('financial_transactions')
transaction_log.info('交易金额5000元', extra={'transaction_amount': 5000})
transaction_log.info('交易金额15000元', extra={'transaction_amount': 15000})

日志配置服务器示例

  • 原理:通过日志配置服务器,可以动态修改日志配置。
  • 示例
    • 日志配置服务器端(server.py)
import logging
import logging.config
import time
import os
# 读取初始配置文件
logging.config.fileConfig('logging.conf')
# 在9999端口上创建并启动监听器
t = logging.config.listen(9999)
t.start()
logger = logging.getLogger('simpleExample')
try:
# 循环遍历日志记录调用以查看新配置进行的修改,直到按下Ctrl+C
while True:
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
time.sleep(5)
except KeyboardInterrupt:
# 清理
logging.config.stopListening()
t.join()
  • 日志配置客户端(client.py),用于发送新的配置文件到服务器
#!/usr/bin/env python
import socket
import sys
import struct
with open(sys.argv[1], 'rb') as f:
data_to_send = f.read()
HOST = 'localhost'
PORT = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('connecting...')
s.connect((HOST, PORT))
print('sending config...')
s.send(struct.pack('>L', len(data_to_send)))
s.send(data_to_send)
s.close()
print('complete')

假设logging.conf是初始的日志配置文件,运行server.py启动日志配置服务器,然后可以通过运行client.py并传入新的配置文件路径,动态修改服务器的日志配置。

  • 扩展

    :在分布式系统中,日志配置服务器可以统一管理各个节点的日志配置,提高管理效率。在使用日志配置服务器时,要注意服务器的稳定性和安全性,防止配置信息泄露或被恶意篡改。在一个分布式电商系统中,有多个微服务节点,如用户服务、订单服务、商品服务等。可以通过日志配置服务器统一管理这些节点的日志配置。以下是为不同服务配置不同日志级别的示例:

    • 新的配置文件(new_logging.conf)
[loggers]
keys=root,user_service,order_service,product_service
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=INFO
handlers=consoleHandler,fileHandler
[logger_user_service]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=user_service
propagate=0
[logger_order_service]
level=WARNING
handlers=consoleHandler,fileHandler
qualname=order_service
propagate=0
[logger_product_service]
level=ERROR
handlers=consoleHandler,fileHandler
qualname=product_service
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('distributed_system.log', 'a')
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
  • 客户端发送新配置的代码(修改后的 client.py)
#!/usr/bin/env python
import socket
import sys
import struct
new_config_file = 'new_logging.conf'
with open(new_config_file, 'rb') as f:
data_to_send = f.read()
HOST = 'localhost'
PORT = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('connecting...')
s.connect((HOST, PORT))
print('sending config...')
s.send(struct.pack('>L', len(data_to_send)))
s.send(data_to_send)
s.close()
print('complete')

这样,通过日志配置服务器,不同的微服务可以根据业务需求设置不同的日志级别,方便进行问题排查和系统监控。

处理日志 handler 的阻塞

  • 原理:在 Web 应用等场景中,某些日志处理器(如SMTPHandlerSocketHandler)可能会阻塞线程。可以通过QueueHandlerQueueListener来解决这个问题。
  • 示例
import queue
import logging
from logging.handlers import QueueHandler, QueueListener
from logging import StreamHandler
que = queue.Queue(-1) # 对大小没有限制
queue_handler = QueueHandler(que)
handler = StreamHandler()
listener = QueueListener(que, handler)
root = logging.getLogger()
root.addHandler(queue_handler)
formatter = logging.Formatter('%(threadName)s: %(message)s')
handler.setFormatter(formatter)
listener.start()
root.warning('Look out!')
listener.stop()

运行代码后,日志输出会显示生成事件的线程,避免了因日志处理阻塞线程的问题。

  • 扩展
    在异步代码中,使用这种方式可以避免网络或文件处理器阻塞事件循环,确保异步程序的性能。在处理日志 handler 阻塞时,要合理设置队列的大小,避免队列溢出导致日志丢失。以下是在异步代码中使用QueueHandlerQueueListener的示例:
import asyncio
import queue
import logging
from logging.handlers import QueueHandler, QueueListener
from logging import StreamHandler
que = queue.Queue(-1)
queue_handler = QueueHandler(que)
handler = StreamHandler()
listener = QueueListener(que, handler)
root = logging.getLogger()
root.addHandler(queue_handler)
formatter = logging.Formatter('%(threadName)s: %(message)s')
handler.setFormatter(formatter)
listener.start()
async def async_task():
for _ in range(5):
logging.info('Async task is running')
await asyncio.sleep(1)
async def main():
await asyncio.gather(async_task())
if __name__ == '__main__':
try:
asyncio.run(main())
except KeyboardInterrupt:
listener.stop()

在这个示例中,异步任务async_task中的日志记录通过QueueHandler写入队列,QueueListener在单独的线程中处理日志,避免了阻塞异步事件循环。

通过网络收发日志事件

  • 原理:在发送端,将SocketHandler连接到根日志对象,将日志事件以未格式化的 pickle 形式发送到指定的网络地址和端口。在接收端,使用socketserver模块设置接收器来处理接收到的日志事件。

示例

发送端(sender.py)

import logging
import logging.handlers
rootLogger = logging.getLogger('')
rootLogger.setLevel(logging.DEBUG)
socketHandler = logging.handlers.SocketHandler('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
# 不必设置格式化器,因为套接字处理器会将事件以未格式化的pickle形式发送
rootLogger.addHandler(socketHandler)
# 现在我们可以写入根记录器或任何其他记录器。首先是根记录器...
logging.info('Jackdaws love my big sphinx of quartz.')
# 现在定义几个可以代表你的应用程序中不同组成部分的其他日志记录器:
logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')
logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

接收端(receiver.py)

import pickle
import logging
import logging.handlers
import socketserver
import struct
class LogRecordStreamHandler(socketserver.StreamRequestHandler):
"""Handler for a streaming logging request.
This basically logs the record using whatever logging policy is
configured locally.
"""
def handle(self):
"""
Handle multiple requests - each expected to be a 4-byte length,
followed by the LogRecord in pickle format. Logs the record
according to whatever policy is configured locally.
"""
while True:
chunk = self.connection.recv(4)
if len(chunk) < 4:
break
slen = struct.unpack('>L', chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + self.connection.recv(slen - len(chunk))
obj = pickle.loads(chunk)
record = logging.makeLogRecord(obj)
self.handleLogRecord(record)
def handleLogRecord(self, record):
logger = logging.getLogger(record.name)
logger.handle(record)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
server = socketserver.TCPServer(('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT), LogRecordStreamHandler)
print('Starting logging server...')
server.serve_forever()

先运行receiver.py启动接收端,再运行sender.py,接收端会处理发送端发送过来的日志记录,并按照本地的日志配置进行记录。

扩展:

在分布式系统中,通过网络收发日志事件可以实现集中式的日志管理,方便对整个系统的日志进行分析和监控。在通过网络收发日志事件时,要注意网络的稳定性和安全性,对传输的日志数据进行加密处理,防止数据泄露。以下是使用 SSL 加密传输日志数据的示例:

发送端(ssl_sender.py)

import logging
import logging.handlers
import ssl
rootLogger = logging.getLogger('')
rootLogger.setLevel(logging.DEBUG)
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(certfile='client.crt', keyfile='client.key')
socketHandler = logging.handlers.SocketHandler('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
ssl_socketHandler = logging.handlers.SecureSocketHandler(socketHandler, context)
rootLogger.addHandler(ssl_socketHandler)
logging.info('Encrypted logging message')

接收端(ssl_receiver.py)

import pickle
import logging
import logging.handlers
import socketserver
import struct
import ssl
class LogRecordStreamHandler(socketserver.StreamRequestHandler):
def handle(self):
while True:
chunk = self.connection.recv(4)
if len(chunk) < 4:
break
slen = struct.unpack('>L', chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + self.connection.recv(slen - len(chunk))
obj = pickle.loads(chunk)
record = logging.makeLogRecord(obj)
self.handleLogRecord(record)
def handleLogRecord(self, record):
logger = logging.getLogger(record.name)
logger.handle(record)
if __name__ == '__main__':
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(certfile='server.crt', keyfile='server.key')
server = socketserver.TCPServer(('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT), LogRecordStreamHandler)
ssl_server = ssl.wrap_socket(server, server_side=True, ssl_context=context)
logging.basicConfig(level=logging.DEBUG)
print('Starting encrypted logging server...')
with ssl_server:
server.serve_forever()

在这个示例中,发送端和接收端通过 SSL 加密进行通信,提高了日志传输的安全性。

日志处理最佳实践

  1. 日志级别设置:根据不同的环境(开发、测试、生产)设置合适的日志级别。开发环境中,可将日志级别设置为 DEBUG,方便获取详细的调试信息;生产环境中,一般设置为 INFO 或 WARNING,避免过多的日志信息影响系统性能。比如在一个 Web 应用开发时,开发阶段将日志级别设为 DEBUG,能输出函数的入参、出参等信息,帮助定位代码问题;上线到生产环境后,调整为 INFO 级别,只记录关键业务操作日志。如下是不同环境下设置日志级别的示例代码:
# 开发环境
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug('This is a debug message for development')
# 生产环境
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info('This is an info message for production')
  1. 日志格式规范:采用统一且有意义的日志格式,包含时间戳、线程名、日志级别、模块名和日志消息等关键信息,便于快速定位问题和分析系统运行情况。以%(asctime)s - %(threadName)s - %(levelname)s - %(module)s - %(message)s格式为例,能清晰看到日志产生的时间、所属线程、级别、来源模块及具体内容,方便排查多线程环境下的并发问题。以下是设置日志格式的代码示例:
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(threadName)s - %(levelname)s - %(module)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
logger.debug('This is a debug message with formatted log')
  1. 日志文件管理:定期清理和归档日志文件,避免日志文件过大占用过多磁盘空间。可以结合系统的任务调度工具(如 Linux 的 Cron Job),按照一定的时间周期(如每周、每月)对日志文件进行压缩和备份。如每月 1 号凌晨 2 点,通过 Cron Job 执行压缩脚本,将上个月的日志文件压缩为.gz格式保存。在 Python 中,使用logging.handlers.RotatingFileHandler可以实现日志文件按大小切割,示例代码如下:
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 每个日志文件最大为10MB,最多保留5个备份文件
handler = RotatingFileHandler('app.log', maxBytes=10*1024*1024, backupCount=5)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
  1. 异常处理与日志记录:在代码中,对可能出现异常的地方进行捕获,并在捕获异常时记录详细的异常信息,包括异常类型、异常堆栈等,这对于排查问题至关重要。使用try - except语句捕获异常,通过logging.exception()记录异常,它会自动记录异常类型、堆栈信息等,方便追溯问题根源。示例代码如下:
import logging
logger = logging.getLogger(__name__)
try:
result = 1 / 0
except ZeroDivisionError:
logger.exception('An error occurred during division')

实际项目案例

  1. 电商系统:在一个电商项目中,使用多模块日志记录用户操作、订单处理、支付流程等关键环节的信息。当用户下单时,订单模块记录订单创建、支付状态更新等日志;支付模块记录支付请求、支付结果等日志。通过这些日志,开发团队可以快速定位订单处理过程中的问题,如支付失败的原因、订单状态异常等。同时,利用日志配置服务器,在系统出现问题时,可以动态调整日志级别,获取更详细的信息进行故障排查。例如,当某个地区的用户频繁出现支付失败问题时,运维人员通过日志配置服务器将支付模块的日志级别临时调整为 DEBUG,获取详细的支付请求和响应日志,快速定位到是支付接口的参数传递问题。假设电商系统使用 Flask 框架,以下是订单模块和支付模块的部分日志记录代码示例:
# 订单模块(order_module.py)
from flask import Flask
import logging
app = Flask(__name__)
order_logger = logging.getLogger('ecommerce.order')
order_logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('order.log')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
order_logger.addHandler(file_handler)
@app.route('/create_order', methods=['POST'])
def create_order():
# 模拟订单创建逻辑
order_id = '12345'
user_id = 'user1'
order_logger.info(f'用户 {user_id} 创建订单,订单ID: {order_id}')
return 'Order created successfully'
@app.route('/update_order_status', methods=['POST'])
def update_order_status():
# 模拟订单状态更新逻辑
order_id = '12345'
new_status = 'paid'
order_logger.info(f'订单 {order_id} 状态更新为: {new_status}')
return 'Order status updated successfully'
if __name__ == '__main__':
app.run()
# 支付模块(payment_module.py)
from flask import Flask
import logging
app = Flask(__name__)
payment_logger = logging.getLogger('ecommerce.payment')
payment_logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('payment.log')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
payment_logger.addHandler(file_handler)
@app.route('/payment_request', methods=['POST'])
def payment_request():
# 模拟支付请求逻辑
user_id = 'user1'
amount = 100.0
payment_logger.info(f'用户 {user_id} 发起支付请求,金额: {amount}')
return 'Payment request received'
@app.route('/payment_result', methods=['POST'])
def payment_result():
# 模拟支付结果处理逻辑
user_id = 'user1'
status ='success'
payment_logger.info(f'用户 {user_id} 支付结果: {status}')
return 'Payment result processed'
if __name__ == '__main__':
app.run()
  1. 分布式爬虫系统:在分布式爬虫项目中,多线程日志记录发挥了重要作用。每个爬虫线程负责抓取不同的网页,通过记录每个线程的日志,可以了解每个线程的抓取进度、是否遇到反爬虫机制等情况。并且,通过网络收发日志事件,将各个节点的日志集中收集到一个日志服务器上,便于对整个爬虫系统进行监控和管理。在处理日志 handler 阻塞时,采用QueueHandlerQueueListener的方式,确保爬虫线程的高效运行,避免因日志处理导致的性能瓶颈。比如,当爬虫遇到大量 403 禁止访问错误时,从集中收集的日志中能快速发现问题,分析是 IP 被封禁还是请求头设置不当等原因;同时,QueueHandlerQueueListener保证了在高并发抓取时,日志处理不会阻塞爬虫线程,维持系统的抓取效率。假设使用requests库进行网页抓取,以下是分布式爬虫的部分代码示例:
import asyncio
import logging
import queue
import requests
from concurrent.futures import ThreadPoolExecutor
from logging.handlers import QueueHandler, QueueListener
from logging import StreamHandler
que = queue.Queue(-1)
queue_handler = QueueHandler(que)
handler = StreamHandler()
listener = QueueListener(que, handler)
root = logging.getLogger()
root.addHandler(queue_handler)
formatter = logging.Formatter('%(threadName)s: %(message)s')
handler.setFormatter(formatter)
listener.start()
def crawl(url):
try:
response = requests.get(url)
if response.status_code == 200:
logging.info(f'成功抓取 {url}')
else:
logging.warning(f'抓取 {url} 失败,状态码: {response.status_code}')
except Exception as e:
logging.error(f'抓取 {url} 时发生错误: {e}')
urls = ['http://example.com', 'http://example2.com', 'http://example3.com']
with ThreadPoolExecutor(max_workers=3) as executor:
executor.map(crawl, urls)
if __name__ == '__main__':
try:
pass
except KeyboardInterrupt:
listener.stop()

总结

本文全面介绍了 Python 日志处理的各种技巧,涵盖了多模块、多线程日志使用,多种处理器和格式化器配置,自定义处理级别,日志配置服务器应用,处理日志阻塞以及网络收发日志事件等内容。同时,分享了日志处理的最佳实践,并结合电商系统、分布式爬虫系统等实际项目案例,展示了日志处理在真实场景中的应用。通过这些知识的学习,开发者可以更好地掌控程序的日志记录,提高程序的可维护性和可调试性。

TAG:Python 日志处理;多模块日志;多线程日志;日志处理器;日志格式化器;自定义日志级别;日志配置服务器;日志阻塞处理;网络日志收发;最佳实践;实际项目案例

官方文档Python 官方日志教程,提供丰富示例和详细说明,是深入学习的基础。

posted on   TekinTian  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示