除了傳遞給日志記錄函數的參數(如msg)外,有時候我們還想在日志輸出中包含一些額外的上下文信息。比如,在一個網絡應用中,可能希望在日志中記錄客戶端的特定信息,如:遠程客戶端的IP地址和用戶名。這里我們來介紹以下幾種實現方式:
> 通過向日志記錄函數傳遞一個extra參數引入上下文信息
> 使用LoggerAdapters引入上下文信息
> 使用Filters引入上下文信息
## 一、通過向日志記錄函數傳遞一個extra參數引入上下文信息
前面我們提到過,可以通過向日志記錄函數傳遞一個extra參數來實現向日志輸出中添加額外的上下文信息,如:
~~~
import logging
import sys
fmt = 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 contextual
information in logging output.
"""
def __init__(self, logger, extra):
"""
Initialize the adapter with a logger and a dict-like object which
provides contextual information. This constructor signature allows
easy stacking of LoggerAdapters, if so desired.
You can effectively pass keyword arguments as shown in the
following example:
adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))
"""
self.logger = logger
self.extra = extra
def process(self, msg, kwargs):
"""
Process the logging message and keyword arguments passed in to
a logging call to insert contextual information. You can either
manipulate the message itself, the keyword args or both. Return
the message and kwargs modified (or not) to suit your needs.
Normally, you'll only need to override this one method in a
LoggerAdapter subclass for your specific needs.
"""
kwargs["extra"] = self.extra
return msg, kwargs
def debug(self, msg, *args, **kwargs):
"""
Delegate a debug call to the underlying logger, after adding
contextual 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 sys
class MyLoggerAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs):
if 'extra' not in kwargs:
kwargs["extra"] = self.extra
return msg, kwargs
if __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!
~~~
## 實例
~~~
__author__ = 'dailin'
import logging
import sys
class LogTest(object):
def __init__(self, name=None, **kwargs):
if name is not None:
self.name = name
elif not getattr(self, 'name', None):
raise ValueError("%s must have a name" % type(self).__name__)
self.__dict__.update(kwargs)
@property
def logger(self):
logger = logging.getLogger(self.name) # 獲取一個logger對象
logger.setLevel(logging.INFO) # 設置日志級別
fmt = logging.Formatter("%(asctime)s - %(spider)s - %(message)s") # 輸出格式
h_console = logging.StreamHandler(sys.stdout) # 處理器
h_console.setFormatter(fmt) # 向處理器中添加輸出格式
logger.addHandler(h_console) # 把處理器添加到logger對象當中
return logging.LoggerAdapter(logger, {'spider': self})
if __name__ == '__main__':
log = LogTest("tuna",hell='mimi')
print(log.__dict__)
print(log.logger.info("hello"))
~~~
輸出:
~~~
{'name': 'tuna', 'hell': 'mimi'}
2018-04-08 17:06:56,464 - <__main__.LogTest object at 0x0000000002587390> - hello
None
~~~
- Docker
- 什么是docker
- Docker安裝、組件啟動
- docker網絡
- docker命令
- docker swarm
- dockerfile
- mesos
- 運維
- Linux
- Linux基礎
- Linux常用命令_1
- Linux常用命令_2
- ip命令
- 什么是Linux
- SELinux
- Linux GCC編譯警告:Clock skew detected. 錯誤解決辦法
- 文件描述符
- find
- 資源統計
- LVM
- Linux相關配置
- 服務自啟動
- 服務器安全
- 字符集
- shell腳本
- shell命令
- 實用腳本
- shell 數組
- 循環與判斷
- 系統級別進程開啟和停止
- 函數
- java調用shell腳本
- 發送郵件
- Linux網絡配置
- Ubuntu
- Ubuntu發送郵件
- 更換apt-get源
- centos
- 防火墻
- 虛擬機下配置網絡
- yum重新安裝
- 安裝mysql5.7
- 配置本地yum源
- 安裝telnet
- 忘記root密碼
- rsync+ crontab
- Zabbix
- Zabbix監控
- Zabbix安裝
- 自動報警
- 自動發現主機
- 監控MySQL
- 安裝PHP常見錯誤
- 基于nginx安裝zabbix
- 監控Tomcat
- 監控redis
- web監控
- 監控進程和端口號
- zabbix自定義監控
- 觸發器函數
- zabbix監控mysql主從同步狀態
- Jenkins
- 安裝Jenkins
- jenkins+svn+maven
- jenkins執行shell腳本
- 參數化構建
- maven區分環境打包
- jenkins使用注意事項
- nginx
- nginx認證功能
- ubuntu下編譯安裝Nginx
- 編譯安裝
- Nginx搭建本地yum源
- 文件共享
- Haproxy
- 初識Haproxy
- haproxy安裝
- haproxy配置
- virtualbox
- virtualbox 復制新的虛擬機
- ubuntu下vitrualbox安裝redhat
- centos配置雙網卡
- 配置存儲
- Windows
- Windows安裝curl
- VMware vSphere
- 磁盤管理
- 增加磁盤
- gitlab
- 安裝
- tomcat
- Squid
- bigdata
- FastDFS
- FastFDS基礎
- FastFDS安裝及簡單實用
- api介紹
- 數據存儲
- FastDFS防盜鏈
- python腳本
- ELK
- logstash
- 安裝使用
- kibana
- 安準配置
- elasticsearch
- elasticsearch基礎_1
- elasticsearch基礎_2
- 安裝
- 操作
- java api
- 中文分詞器
- term vector
- 并發控制
- 對text字段排序
- 倒排和正排索引
- 自定義分詞器
- 自定義dynamic策略
- 進階練習
- 共享鎖和排它鎖
- nested object
- 父子關系模型
- 高亮
- 搜索提示
- Redis
- redis部署
- redis基礎
- redis運維
- redis-cluster的使用
- redis哨兵
- redis腳本備份還原
- rabbitMQ
- rabbitMQ安裝使用
- rpc
- RocketMQ
- 架構概念
- 安裝
- 實例
- 好文引用
- 知乎
- ACK
- postgresql
- 存儲過程
- 編程語言
- 計算機網絡
- 基礎_01
- tcp/ip
- http轉https
- Let's Encrypt免費ssl證書(基于haproxy負載)
- what's the http?
- 網關
- 網絡IO
- http
- 無狀態網絡協議
- Python
- python基礎
- 基礎數據類型
- String
- List
- 遍歷
- Python基礎_01
- python基礎_02
- python基礎03
- python基礎_04
- python基礎_05
- 函數
- 網絡編程
- 系統編程
- 類
- Python正則表達式
- pymysql
- java調用python腳本
- python操作fastdfs
- 模塊導入和sys.path
- 編碼
- 安裝pip
- python進階
- python之setup.py構建工具
- 模塊動態導入
- 內置函數
- 內置變量
- path
- python模塊
- 內置模塊_01
- 內置模塊_02
- log模塊
- collections
- Twisted
- Twisted基礎
- 異步編程初探與reactor模式
- yield-inlineCallbacks
- 系統編程
- 爬蟲
- urllib
- xpath
- scrapy
- 爬蟲基礎
- 爬蟲種類
- 入門基礎
- Rules
- 反反爬蟲策略
- 模擬登陸
- problem
- 分布式爬蟲
- 快代理整站爬取
- 與es整合
- 爬取APP數據
- 爬蟲部署
- collection for ban of web
- crawlstyle
- API
- 多次請求
- 向調度器發送請求
- 源碼學習
- LinkExtractor源碼分析
- 構建工具-setup.py
- selenium
- 基礎01
- 與scrapy整合
- Django
- Django開發入門
- Django與MySQL
- java
- 設計模式
- 單例模式
- 工廠模式
- java基礎
- java位移
- java反射
- base64
- java內部類
- java高級
- 多線程
- springmvc-restful
- pfx數字證書
- 生成二維碼
- 項目中使用log4j
- 自定義注解
- java發送post請求
- Date時間操作
- spring
- 基礎
- spring事務控制
- springMVC
- 注解
- 參數綁定
- springmvc+spring+mybatis+dubbo
- MVC模型
- SpringBoot
- java配置入門
- SpringBoot基礎入門
- SpringBoot web
- 整合
- SpringBoot注解
- shiro權限控制
- CommandLineRunner
- mybatis
- 靜態資源
- SSM整合
- Aware
- Spring API使用
- Aware接口
- mybatis
- 入門
- mybatis屬性自動映射、掃描
- 問題
- @Param 注解在Mybatis中的使用 以及傳遞參數的三種方式
- mybatis-SQL
- 逆向生成dao、model層代碼
- 反向工程中Example的使用
- 自增id回顯
- SqlSessionDaoSupport
- invalid bound statement(not found)
- 脈絡
- beetl
- beetl是什么
- 與SpringBoot整合
- shiro
- 什么是shiro
- springboot+shrio+mybatis
- 攔截url
- 枚舉
- 圖片操作
- restful
- java項目中日志處理
- JSON
- 文件工具類
- KeyTool生成證書
- 兼容性問題
- 開發規范
- 工具類開發規范
- 壓縮圖片
- 異常處理
- web
- JavaScript
- 基礎語法
- 創建對象
- BOM
- window對象
- DOM
- 閉包
- form提交-文件上傳
- td中內容過長
- 問題1
- js高級
- js文件操作
- 函數_01
- session
- jQuery
- 函數01
- data()
- siblings
- index()與eq()
- select2
- 動態樣式
- bootstrap
- 表單驗證
- 表格
- MUI
- HTML
- iframe
- label標簽
- 規范編程
- layer
- sss
- 微信小程序
- 基礎知識
- 實踐
- 自定義組件
- 修改自定義組件的樣式
- 基礎概念
- appid
- 跳轉
- 小程序發送ajax
- 微信小程序上下拉刷新
- if
- 工具
- idea
- Git
- maven
- svn
- Netty
- 基礎概念
- Handler
- SimpleChannelInboundHandler 與 ChannelInboundHandler
- 網絡編程
- 網絡I/O
- database
- oracle
- 游標
- PLSQL Developer
- mysql
- MySQL基準測試
- mysql備份
- mysql主從不同步
- mysql安裝
- mysql函數大全
- SQL語句
- 修改配置
- 關鍵字
- 主從搭建
- centos下用rpm包安裝mysql
- 常用sql
- information_scheme數據庫
- 值得學的博客
- mysql學習
- 運維
- mysql權限
- 配置信息
- 好文mark
- jsp
- jsp EL表達式
- C
- test