当前位置: 首页 > 编程日记 > 正文

Python之向日志输出中添加上下文信息

除了传递给日志记录函数的参数(如msg)外,有时候我们还想在日志输出中包含一些额外的上下文信息。比如,在一个网络应用中,可能希望在日志中记录客户端的特定信息,如:远程客户端的IP地址和用户名。这里我们来介绍以下几种实现方式:

  • 通过向日志记录函数传递一个extra参数引入上下文信息
  • 使用LoggerAdapters引入上下文信息
  • 使用Filters引入上下文信息

一、通过向日志记录函数传递一个extra参数引入上下文信息


前面我们提到过,可以通过向日志记录函数传递一个extra参数来实现向日志输出中添加额外的上下文信息,如:

import logging
import sysfmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")
h_console = logging.StreamHandler(sys.stdout)
h_console.setFormatter(fmt)
logger = logging.getLogger("myPro")
logger.setLevel(logging.DEBUG)
logger.addHandler(h_console)extra_dict = {"ip": "113.208.78.29", "username": "Petter"}
logger.debug("User Login!", extra=extra_dict)extra_dict = {"ip": "223.190.65.139", "username": "Jerry"}
logger.info("User Access!", extra=extra_dict)

输出:

2017-05-14 15:47:25,562 - myPro - 113.208.78.29 - Petter - User Login!
2017-05-14 15:47:25,562 - myPro - 223.190.65.139 - Jerry - User Access!

但是用这种方式来传递信息并不是那么方便,因为每次调用日志记录方法都要传递一个extra关键词参数。即便没有需要插入的上下文信息也是如此,因为该logger设置的formatter格式中指定的字段必须要存在。所以,我们推荐使用下面两种方式来实现上下文信息的引入。

也许可以尝试创建许多不同的Logger实例来解决上面存在的问题,但是这显然不是一个好的解决方案,因为这些Logger实例并不会进行垃圾回收。尽管这在实践中不是个问题,但是当Logger数量变得不可控将会非常难以管理。

二、使用LoggerAdapters引入上下文信息


使用LoggerAdapter类来传递上下文信息到日志事件的信息中是一个非常简单的方式,可以把它看做第一种实现方式的优化版--因为它为extra提供了一个默认值。这个类设计的类似于Logger,因此我们可以像使用Logger类的实例那样来调用debug(), info(), warning(),error(), exception(), critical()log()方法。当创建一个LoggerAdapter的实例时,我们需要传递一个Logger实例和一个包含上下文信息的类字典对象给该类的实例构建方法。当调用LoggerAdapter实例的一个日志记录方法时,该方法会在对日志日志消息和字典对象进行处理后,调用构建该实例时传递给该实例的logger对象的同名的日志记录方法。下面是LoggerAdapter类中几个方法的定义:

class LoggerAdapter(object):"""An adapter for loggers which makes it easier to specify contextualinformation in logging output."""def __init__(self, logger, extra):"""Initialize the adapter with a logger and a dict-like object whichprovides contextual information. This constructor signature allowseasy stacking of LoggerAdapters, if so desired.You can effectively pass keyword arguments as shown in thefollowing example:adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))"""self.logger = loggerself.extra = extradef process(self, msg, kwargs):"""Process the logging message and keyword arguments passed in toa logging call to insert contextual information. You can eithermanipulate the message itself, the keyword args or both. Returnthe message and kwargs modified (or not) to suit your needs.Normally, you'll only need to override this one method in aLoggerAdapter subclass for your specific needs."""kwargs["extra"] = self.extrareturn msg, kwargsdef debug(self, msg, *args, **kwargs):"""Delegate a debug call to the underlying logger, after addingcontextual information from this adapter instance."""msg, kwargs = self.process(msg, kwargs)self.logger.debug(msg, *args, **kwargs)

通过分析上面的代码可以得出以下结论:

  • 上下文信息是在LoggerAdapter类的process()方法中被添加到日志记录的输出消息中的,如果要实现自定义需求只需要实现LoggerAdapter的子类并重写process()方法即可;
  • process()方法的默认实现中,没有修改msg的值,只是为关键词参数插入了一个名为extra的 key,这个extra的值为传递给LoggerAdapter类构造方法的参数值;
  • LoggerAdapter类构建方法所接收的extra参数,实际上就是为了满足logger的formatter格式要求所提供的默认上下文信息。

关于上面提到的第3个结论,我们来看个例子:

import logging
import sys# 初始化一个要传递给LoggerAdapter构造方法的logger实例
fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")
h_console = logging.StreamHandler(sys.stdout)
h_console.setFormatter(fmt)
init_logger = logging.getLogger("myPro")
init_logger.setLevel(logging.DEBUG)
init_logger.addHandler(h_console)# 初始化一个要传递给LoggerAdapter构造方法的上下文字典对象
extra_dict = {"ip": "IP", "username": "USERNAME"}# 获取一个LoggerAdapter类的实例
logger = logging.LoggerAdapter(init_logger, extra_dict)# 应用中的日志记录方法调用
logger.info("User Login!")
logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"})
logger.extra = {"ip": "113.208.78.29", "username": "Petter"}
logger.info("User Login!")
logger.info("User Login!")

输出结果:

# 使用extra默认值:{"ip": "IP", "username": "USERNAME"}
2017-05-14 17:23:15,148 - myPro - IP - USERNAME - User Login!# info(msg, extra)方法中传递的extra方法没有覆盖默认值
2017-05-14 17:23:15,148 - myPro - IP - USERNAME - User Login!# extra默认值被修改了
2017-05-14 17:23:15,148 - myPro - 113.208.78.29 - Petter - User Login!
2017-05-14 17:23:15,148 - myPro - 113.208.78.29 - Petter - User Login!

根据上面的程序输出结果,我们会发现一个问题:传递给LoggerAdapter类构造方法的extra参数值不能被LoggerAdapter实例的日志记录函数(如上面调用的info()方法)中的extra参数覆盖,只能通过修改LoggerAdapter实例的extra属性来修改默认值(如上面使用的logger.extra=xxx),但是这也就意味着默认值被修改了。

解决这个问题的思路应该是:实现一个LoggerAdapter的子类,重写process()方法。其中对于kwargs参数的操作应该是先判断其本身是否包含extra关键字,如果包含则不使用默认值进行替换;如果kwargs参数中不包含extra关键字则取默认值。来看具体实现:

import logging
import sysclass MyLoggerAdapter(logging.LoggerAdapter):def process(self, msg, kwargs):if 'extra' not in kwargs:kwargs["extra"] = self.extrareturn msg, kwargsif __name__ == '__main__':# 初始化一个要传递给LoggerAdapter构造方法的logger实例fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")h_console = logging.StreamHandler(sys.stdout)h_console.setFormatter(fmt)init_logger = logging.getLogger("myPro")init_logger.setLevel(logging.DEBUG)init_logger.addHandler(h_console)# 初始化一个要传递给LoggerAdapter构造方法的上下文字典对象extra_dict = {"ip": "IP", "username": "USERNAME"}# 获取一个自定义LoggerAdapter类的实例logger = MyLoggerAdapter(init_logger, extra_dict)# 应用中的日志记录方法调用logger.info("User Login!")logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"})logger.info("User Login!")logger.info("User Login!")

输出结果:

# 使用extra默认值:{"ip": "IP", "username": "USERNAME"}
2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!# info(msg, extra)方法中传递的extra方法已覆盖默认值
2017-05-22 17:35:38,499 - myPro - 113.208.78.29 - Petter - User Login!# extra默认值保持不变
2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!
2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!

OK! 问题解决了。

其实,如果我们想不受formatter的限制,在日志输出中实现自由的字段插入,可以通过在自定义LoggerAdapter的子类的process()方法中将字典参数中的关键字信息拼接到日志事件的消息中。很明显,这些上下文中的字段信息在日志输出中的位置是有限制的。而使用'extra'的优势在于,这个类字典对象的值将被合并到这个LogRecord实例的__dict__中,这样就允许我们通过Formatter实例自定义日志输出的格式字符串。这虽然使得上下文信息中的字段信息在日志输出中的位置变得与内置字段一样灵活,但是前提是传递给构造器方法的这个类字典对象中的key必须是确定且明了的。

三、使用Filters引入上下文信息


另外,我们还可以使用自定义的Filter.Filter实例的方式,在filter(record)方法中修改传递过来的LogRecord实例,把要加入的上下文信息作为新的属性赋值给该实例,这样就可以通过指定formatter的字符串格式来输出这些上下文信息了。

我们模仿上面的实现,在传递个filter(record)方法的LogRecord实例中添加两个与当前网络请求相关的信息:ip和username。

import logging
from random import choiceclass ContextFilter(logging.Filter):ip = 'IP'username = 'USER'def filter(self, record):record.ip = self.iprecord.username = self.usernamereturn Trueif __name__ == '__main__':levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)users = ['Tom', 'Jerry', 'Peter']ips = ['113.108.98.34', '219.238.78.91', '43.123.99.68']logging.basicConfig(level=logging.DEBUG,format='%(asctime)-15s %(name)-5s %(levelname)-8s %(ip)-15s %(username)-8s %(message)s')logger = logging.getLogger('myLogger')filter = ContextFilter()logger.addFilter(filter)logger.debug('A debug message')logger.info('An info message with %s', 'some parameters')for x in range(5):lvl = choice(levels)lvlname = logging.getLevelName(lvl)filter.ip = choice(ips)filter.username = choice(users)logger.log(lvl, 'A message at %s level with %d %s' , lvlname, 2, 'parameters')

输出结果:

2017-05-15 10:21:49,401 myLogger DEBUG    IP              USER     A debug message
2017-05-15 10:21:49,401 myLogger INFO     IP              USER     An info message with some parameters
2017-05-15 10:21:49,401 myLogger INFO     219.238.78.91   Tom      A message at INFO level with 2 parameters
2017-05-15 10:21:49,401 myLogger INFO     219.238.78.91   Peter    A message at INFO level with 2 parameters
2017-05-15 10:21:49,401 myLogger DEBUG    113.108.98.34   Jerry    A message at DEBUG level with 2 parameters
2017-05-15 10:21:49,401 myLogger CRITICAL 43.123.99.68    Tom      A message at CRITICAL level with 2 parameters
2017-05-15 10:21:49,401 myLogger INFO     43.123.99.68    Jerry    A message at INFO level with 2 parameters

需要说明的是: 实际的网络应用程序中,可能还要考虑多线程并发时的线程安全问题,此时可以把连接信息或者自定义过滤器的实例通过threading.local保存到到一个threadlocal中。

  • 参考文档:https://docs.python.org/3.5/howto/logging-cookbook.html#adding-contextual-information-to-your-logging-output

    问题交流群:666948590

转载于:https://www.cnblogs.com/yyds/p/6897964.html

相关文章:

小程序点击图片自动播放视频,停止上一个视频播放

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 通过列表的点击事件自动播放列表对应的视频&#xff0c;同时停止上一个视频的播放 源码&#xff1a; <view><view classvv wx:for{{vedio_data}} wx:key><view classblock stylemargin…

hitchhiker部署_Hitchhiker的React Router v4指南:无限远的递归路径!

hitchhiker部署Welcome to the third part of the Hitchhiker’s Guide to React Router v4. In this article we’re going to focus on recursive paths. If you’ve missed the first two parts, you can find part 1 here and part 2 here.欢迎阅读《 Hitchhiker React Rou…

smbpasswd 和 pdbedit 的区别

smbpasswd 和 pdbedit 的区别 以前我们在windows上共享文件的话&#xff0c;只需右击要共享的文件夹然后选择共享相关的选项设置即可。然而如何实现windows和linux的文件共享呢&#xff1f;这就涉及到了samba服务了&#xff0c;这个软件配置起来也不难&#xff0c;使用也非常简…

DB天气app冲刺二阶段第十一天(完结)

今天最后一天冲刺了&#xff0c;明天就不再冲刺了。。已经把所有的技术的问题还有设计的问题都弄好了吧应该说 至少目前来说是的。因为有的实现不了的或者需要耗费时间的已经果断舍弃了&#xff0c;然后需要完善的也都基本完善了。 现在还需要做的就是素材的收集整理。需要抽半…

如何超越console.log并充分利用浏览器的调试控制台

by Gilad Dayagi通过吉拉德达亚吉 The console object is a very useful feature of browsers that has been around for many years. It provides access to the browser’s debugging console.Most web developers know how to print messages to the console using console…

区域设置 ID (LCID) 表, 及获取方法

区域设置 ID (LCID) 表, 及获取方法 中国的区域设置 ID 是 2052, 如果经常打开微软软件的安装目录应该经常见到.获取很简单, 有现成的 API 函数: GetThreadLocale.beginShowMessage(IntToStr(GetThreadLocale)); //2052 end; 区域设置 ID (LCID) 表区域设置描述简写十六进制值十…

E201700525-hm

skeleton n. 骨骼; &#xff08;建筑物等的&#xff09; 骨架; 梗概; 骨瘦如柴的人&#xff08;或动物&#xff09;;adj. 骨骼的; 骨瘦如柴的; 概略的; 基本的; cloud n. 云; 云状物; invoke vt. 乞灵&#xff0c;祈求; 提出或授引…以支持或证明; 召鬼; 借助;render …

php不显示报错

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 error_reporting(E_ALL & ~E_NOTICE);

致谢 开源开发者的贡献_对开源做出的贡献如何使我成为更好的开发人员,以及如何做到这一点...

致谢 开源开发者的贡献by Luciano Strika通过卢西亚诺斯特里卡(Luciano Strika) 对开源做出的贡献如何使我成为更好的开发人员&#xff0c;以及如何做到这一点 (How contributing to open source made me a better developer — and how you can do it, too) So you’ve been …

欲精一行,必先通十行

将前端开发和服务器端开发做一个比较&#xff0c;前端开发没有服务器端开发“深”&#xff0c;服务器端开发没有前端开发“广”。经常听到做前端的同行抱怨需要学的东西太 多&#xff0c;东学一点西学一点&#xff0c;什么都会&#xff0c;但也什么都不精。很直接的结果就是沦为…

LeetCode 228: Summary Ranges

Given a sorted integer array without duplicates, return the summary of its ranges. For example, given [0,1,2,4,5,7], return ["0->2","4->5","7"]. 代码要求对数组中的元素进行分段。 首先给出字符串格式化函数&#xff0c;假设be…

JQ+ajax 提交表单不跳转页面

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 代码 <div class"apply_box"><h1>合作申请</h1><div class"apply_l"><input type"text" maxlength"20" id"name" name&q…

node.js是开源的吗_为开源做贡献并不难:我为Node.js项目做贡献的旅程

node.js是开源的吗As a developer, you should consider contributing to open source software. Many of your potential employers will look favorably on these contributions.作为开发人员&#xff0c;您应该考虑为开源软件做贡献。 您的许多潜在雇主将对这些供款看好。 …

超级简单的jquery轮播图demo

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>轮播图demo</title><script type"text/javascript" src"js/jquery-3.2.1.slim.js" ></script><link rel"stylesheet" …

Mysql 操作技巧

复制表结构 表数据Mysql> create tables t2 like t1;Mysql> insert into t2 select * from t1; mysql 索引a、Alert Table 用来创建普通索引、Unique 唯一索引 (当前列数值不可重复) 或 Primary Key 主键索引Mysql> alter table table_name add index index_name(col…

JS实现复制到剪切板效果

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><meta cha…

如何管理多个Python版本和虚拟环境

Addition January 2019: If you are coming back to this blog after upgrading to macOS Mojave please see this github issue for a solution to the common pyenv ‘zlib not available’ problem.此外&#xff0c;2019年1月&#xff1a;如果在升级到macOS Mojave之后又回到…

linux 下byte,char,unsigned char的区别

在linux中&#xff0c;对byte的定义为无符号char&#xff0c;而char默认为有符号char。 #ifndef BYTE #define BYTE unsigned char #endif以下ZZ百度知道&#xff1a; 在C中&#xff0c;默认的基础数据类型均为signed&#xff0c;现在我们以char为例&#xff0c;说明(signed) c…

词法作用域和动态作用域

JavaScript采用的是词法作用域 1.词法作用域 即函数定义时&#xff0c;即确定的作用域。js中的作用域链&#xff0c;在函数声明时候&#xff0c;就已经确定了&#xff0c;无论函数在何处调用&#xff0c;其作用域变量的查找都是按照定义是包含关系去查找。 2.动态作用域 变量的…

10-18 JS基础复习笔记

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 1.JS类型 Numbel String Boolean Symbol Null Undefined Object(Funtion,Array,Data,Regexp); 2.字符串转数字类型 "122" //122 var a 1 2; console.log(a) //3 3.null 和 u…

vue.js crud_如何使用VS Code和ADO.NET使用ASP.NET Core执行CRUD操作

vue.js crud介绍 (Introduction) In this article we are going to create a web application using ASP.NET Core MVC with the help of Visual Studio Code and ADO.NET. We will be creating a sample Employee Record Management System and performing CRUD operations on…

redis事物命令示例

命令示例&#xff1a; 1. 事务被正常执行&#xff1a;#在Shell命令行下执行Redis的客户端工具。/> redis-cli#在当前连接上启动一个新的事务。redis 127.0.0.1:6379>multiOK#执行事务中的第一条命令&#xff0c;从该命令的返回结果可以看出&#xff0c;该命令并没有立即执…

JS 函数 函数递归

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 重要&#xff1a;函数也是对象&#xff0c;你可以给它们添加属性或者更改它们的属性。 函数内部对象&#xff1a;arguments 解析&#xff1a;函数实际上是访问了函数体中一个名为 arguments 的内部对象…

swift设置启动图不现实_如何通过装饰房屋来开始在Swift中使用增强现实

swift设置启动图不现实by Ranadhir Dey由Ranadhir Dey 如何通过装饰房屋来开始在Swift中使用增强现实 (How to get started with augmented reality in Swift by decorating your home) If you’ve read my previous post, you already have a beautiful AR floor in your din…

可用于nodejs的SuperAgent(ajax API)

简单示例&#xff1a; import request from superagent;//引用声明 request.post(api).withCredentials()//跨域.end((err, res) > {if (res.ok) {const json JSON.parse(res.text);} else {console.log(获取失败);}}); 1、get 方式 当使用get请求传递查询字符串的时候&…

Java第四次实验

一、实验内容&#xff1a; 结对编程。运行TCP代码&#xff0c;结对进行&#xff0c;一人服务器&#xff0c;一人客户端&#xff1b;利用加解密代码包&#xff0c;编译运行代码&#xff0c;一人加密&#xff0c;一人解密&#xff1b; 集成代码&#xff0c;密后通过TCP发送。 二、…

JS 面向对象编程之原型(prototype)

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 function Person(first, last) {this.first first;this.last last; } Person.prototype.fullName function() {return this.first this.last; } Person.prototype.fullNameReversed function() {…

idea自动捕获_Smilefie:如何通过检测微笑来自动捕获自拍

idea自动捕获by Rishav Agarwal通过里沙夫阿加瓦尔 Smilefie&#xff1a;如何通过检测微笑来自动捕获自拍 (Smilefie: how you can auto-capture selfies by detecting a smile) Ten second takeaway: use Python and OpenCV to create an app that automatically captures a …

hiho 1015 KMP算法 CF 625 B. War of the Corporations

#1015 : KMP算法 时间限制:1000ms单点时限:1000ms内存限制:256MB描述 小Hi和小Ho是一对好朋友&#xff0c;出生在信息化社会的他们对编程产生了莫大的兴趣&#xff0c;他们约定好互相帮助&#xff0c;在编程的学习道路上一同前进。 这一天&#xff0c;他们遇到了一只河蟹&#…

React 组件绑定点击事件,并且传参完整Demo

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 1.传数组下标给点击事件Demo&#xff1a; const A 65 // ASCII character codeclass Alphabet extends React.Component {constructor(props) {super(props);this.handleClick this.handleClick.bind…