1. 程式人生 > >Python logging 模組和使用經驗

Python logging 模組和使用經驗

記錄下常用的一些東西,每次用總是查文件有點小麻煩。 py2.7
日誌應該是生產應用的重要生命線,誰都不應該掉以輕心

有益原則

級別分離

日誌系統通常有下面幾種級別,看情況是使用

  • FATAL - 導致程式退出的嚴重系統級錯誤,不可恢復,當錯誤發生時,系統管理員需要立即介入,謹慎使用。
  • ERROR - 執行時異常以及預期之外的錯誤,也需要立即處理,但緊急程度低於FATAL,當錯誤發生時,影響了程式的正確執行。需要注意的是這兩種級別屬於服務自己的錯誤,需要管理員介入,使用者輸入出錯不屬於此分類。
  • WARN - 預期之外的執行時狀況,表示系統可能出現問題。對於那些目前還不是錯誤,然而不及時處理也會變成錯誤的情況,也可以記為WARN,如磁碟過低。
  • INFO - 有意義的事件資訊,記錄程式正常的執行狀態,比如收到請求,成功執行。通過檢視INFO,可以快速定位WARN,ERROR, FATAL。INFO不宜過多,通常情況下不超過TRACE的10%。
  • DEBUG - 與程式執行時的流程相關的詳細資訊以及當前變數狀態。
  • TRACE - 更詳細的跟蹤資訊。DEBUG和TRACE這兩種規範由專案組自己定義,通過該種日誌,可以檢視某一個操作每一步的執行過程,可以準確定位是何種操作,何種引數,何種順序導致了某種錯誤的發生

單獨目錄

日誌最好放到單獨的日誌目錄,例如 /var/logs/ 下,按照應用分成不同的目錄,或者是檔案。日誌不要放在應用目錄下,那樣不利於自動化部署和應用升級,備份等。

日誌分類

診斷日誌,統計日誌,審計日誌等等,不同用途等日誌儲存到不同的檔案中,方面後面的查詢,分析。

日誌格式

不管是web日誌,還是應用日誌,最好有一個比較統一的格式(例如時間格式),方面日誌的查詢,入庫,和分析。還有一些應用統一使用json的日誌格式,也挺好的。

不好的做法

  • 日誌中含有使用者敏感資訊
  • 線上程式中使用 print
  • 生產環境使用 debug 級別日誌 ��

日誌切分

日誌可以按照每天,每週或者是檔案的大小,切分之後壓縮。一方面容易按時間回溯,另一方面可以減少磁碟空間,對於很久之前的日誌,可以傳輸到遠端伺服器,或者是刪除。

Python 日誌

好習慣

  • root級別的設定: 日誌格式, 有利於標準化
  • class 中設定logger self.logger = logging.getLogger(type(self).__name__)
  • 模組,檔案中設定 logger logger = logging.getLogger(__name__)
  • 使用JSON YAML等格式來配置logging,感覺比使用程式碼或者 ini格式看起來更方面
  • 錯誤日誌是比較特殊的日誌,因為它需要更多的資訊,例如錯誤產生的上下文,還有錯誤堆疊等資訊。可以通過 python logging context pypi 關鍵詞google一些資訊,或者自己設計一個 logging handler 來實現。

實際問題

  • 簡單的小應用中,單個日誌檔案,同時還要列印控制檯
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                    datefmt='%m-%d %H:%M',
                    filename='/temp/myapp.log',
                    filemode='w')
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger('').addHandler(console)
  • 記錄 Exception 的trace 資訊(很有用哦)
try:
    open('/path/to/does/not/exist', 'rb')
except (SystemExit, KeyboardInterrupt):
    raise
except Exception, e:
    logger.error('Failed to open file', exc_info=True)
  • ini 格式例子

這裡用了第三方的一個handler,ConcurrentRotatingFileHandler, 實現多程序安全

[loggers]
keys=root

[handlers]
keys=stream, rotatingFile, errorFile

[formatters]
keys=form01

[logger_root]
level=DEBUG
handlers=stream, rotatingFile, errorFile

[handler_stream]
class=StreamHandler
level=NOTSET
formatter=form01
args=(sys.stdout,)

[handler_errorFile]
class=FileHandler
level=ERROR
formatter=form01
args=('./logs/portal.log', 'a')

[handler_rotatingFile]
level=INFO
formatter=form01
class=handlers.ConcurrentRotatingFileHandler
args=('./logs/portal.log','a',50240000, 10)

[formatter_form01]
format=%(asctime)s %(name)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter

引用

import logging
import logging.config
import cloghandler
logging.config.fileConfig(join(BASE_DIR, "conf/log.conf"))

logger = logging.getLogger(__name__)

預設會使用 root 這個logger,如果名稱匹配就使用對應的logger。 一個logger也可以指定多個 handdler, 用來處理不同的日誌級別等。

  • JSON格式 例子

配置

{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "simple": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        }
    },

    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "simple",
            "stream": "ext://sys.stdout"
        },

        "info_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "INFO",
            "formatter": "simple",
            "filename": "info.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        },

        "error_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "ERROR",
            "formatter": "simple",
            "filename": "errors.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        }
    },

    "loggers": {
        "my_module": {
            "level": "ERROR",
            "handlers": ["console"],
            "propagate": "no"
        }
    },

    "root": {
        "level": "INFO",
        "handlers": ["console", "info_file_handler", "error_file_handler"]
    }
}

獲取配置

import os
import json
import logging.config

def setup_logging(
    default_path='logging.json',
    default_level=logging.INFO,
    env_key='LOG_CFG'
):
    """Setup logging configuration

    """
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path, 'rt') as f:
            config = json.load(f)
        logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)
  • 把日誌格式化成json的工具
import logging.handlers
from pythonjsonlogger import jsonlogger
import datetime

class JsonFormatter(jsonlogger.JsonFormatter, object):
    def __init__(self,
                 fmt="%(asctime) %(name) %(processName) %(filename)  %(funcName) %(levelname) %(lineno) %(module) %(threadName) %(message)",
                 datefmt="%Y-%m-%dT%H:%M:%SZ%z",
                 style='%',
                 extra={}, *args, **kwargs):
        self._extra = extra
        jsonlogger.JsonFormatter.__init__(self, fmt=fmt, datefmt=datefmt, *args, **kwargs)

    def process_log_record(self, log_record):
        # Enforce the presence of a timestamp
        if "asctime" in log_record:
            log_record["timestamp"] = log_record["asctime"]
        else:
            log_record["timestamp"] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ%z")

        if self._extra is not None:
            for key, value in self._extra.items():
                log_record[key] = value
        return super(JsonFormatter, self).process_log_record(log_record)

參考