python logging日誌處理詳解

1.介紹

在 Python 中有一個標準的 logging 模塊,我們可以使用它來進行標註的日誌記錄,利用它我們可以更方便地進行日誌記錄,同時還可以做更方便的級別區分以及一些額外日誌信息的記錄,如時間、運行模塊信息等。

1.1 架構

在這裏插入圖片描述

整個日誌記錄的框架可以分爲這麼幾個部分:

  • Logger 記錄器,暴露了應用程序代碼能直接使用的接口。
  • Handler 處理器,將(記錄器產生的)日誌記錄發送至合適的目的地。
  • Filter 過濾器,提供了更好的粒度控制,它可以決定輸出哪些日誌記錄。
  • Formatter 格式化器,指明瞭最終輸出中日誌記錄的佈局。

1.1.1 Logger 記錄器

Logger是一個樹形層級結構,在使用接口debug,info,warn,error,critical之前必須創建Logger實例,即創建一個記錄器,如果沒有顯式的進行創建,則默認創建一個root logger,並應用默認的日誌級別(WARN),處理器Handler(StreamHandler,即將日誌信息打印輸出在標準輸出上),和格式化器Formatter(默認的格式即爲第一個簡單使用程序中輸出的格式)。

創建方法:

logger = logging.getLogger(logger_name)

創建Logger實例後,可以使用以下方法進行日誌級別設置,增加處理器Handler。

logger.setLevel(logging.ERROR)    #設置日誌級別爲ERROR,即只有日誌級別大於等於ERROR的日誌纔會輸出
logger.addHandler(handler_name) # 爲Logger實例增加一個處理器
logger.removeHandler(handler_name) # 爲Logger實例刪除一個處理器

1.1.2 Handler 處理器

Handler處理器類型有很多種,比較常用的有三個,StreamHandlerFileHandlerNullHandler
創建StreamHandler之後,可以通過使用以下方法設置日誌級別,設置格式化器Formatter,增加或刪除過濾器Filter。

ch.setLevel(logging.WARN)     #指定日誌級別,低於WARN級別的日誌將被忽略
ch.setFormatter(formatter_name)   #設置一個格式化器formatter
ch.addFilter(filter_name)    #增加一個過濾器,可以增加多個
ch.removeFilter(filter_name)   #刪除一個過濾器

StreamHandler
創建方法:

sh = logging.StreamHandler(stream=None)

FileHandler
創建方法:

fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)

NullHandler
NullHandler類位於核心logging包,不做任何的格式化或者輸出。
本質上它是個“什麼都不做”的handler,由庫開發者使用。
logging 模塊提供的 全部Handler 有:

StreamHandler:logging.StreamHandler;  日誌輸出到流,可以是 sys.stderr,sys.stdout 或者文件。
FileHandler:logging.FileHandler;    日誌輸出到文件。
BaseRotatingHandler:logging.handlers.BaseRotatingHandler; 基本的日誌回滾方式。
RotatingHandler:logging.handlers.RotatingHandler;日誌回滾方式,支持日誌文件最大數量和日誌文件回滾。
TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日誌回滾方式,在一定時間區域內回滾日誌文件。
SocketHandler:logging.handlers.SocketHandler;遠程輸出日誌到TCP/IP sockets。
DatagramHandler:logging.handlers.DatagramHandler;遠程輸出日誌到UDP sockets。
SMTPHandler:logging.handlers.SMTPHandler;遠程輸出日誌到郵件地址。
SysLogHandler:logging.handlers.SysLogHandler;日誌輸出到syslog。
NTEventLogHandler:logging.handlers.NTEventLogHandler;遠程輸出日誌到Windows NT/2000/XP的事件日誌。
MemoryHandler:logging.handlers.MemoryHandler;日誌輸出到內存中的指定buffer。
HTTPHandler:logging.handlers.HTTPHandler;通過”GET”或者”POST”遠程輸出到HTTP服務器。

1.1.3 Formatter 格式化器

使用Formatter對象設置日誌信息最後的規則、結構和內容,默認的時間格式爲%Y-%m-%d %H:%M:%S

創建方法:

formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,將使用’%(message)s’。如果不指明datefmt,將使用ISO8601日期格式。

1.1.4 Filter 過濾器

Handlers和Loggers可以使用Filters來完成比級別更復雜的過濾。Filter基類只允許特定Logger層次以下的事件。例如用‘A.B’初始化的Filter允許Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等記錄的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串來初始化,所有的事件都接受。

創建方法:

 filter = logging.Filter(name='')

熟悉了這些概念之後,有另外一個比較重要的事情必須清楚,即Logger是一個樹形層級結構;
Logger可以包含一個或多個Handler和Filter,即Logger與Handler或Fitler是一對多的關係;
一個Logger實例可以新增多個Handler,一個Handler可以新增多個格式化器或多個過濾器,而且日誌級別將會繼承。

2. 優點

總的來說 logging 模塊相比 print 有這麼幾個優點:

  • 可以在 logging 模塊中設置日誌等級,在不同的版本(如開發環境、生產環境)上通過設置不同的輸出等級來記錄對應的日誌,非常靈活。
  • print 的輸出信息都會輸出到標準輸出流中,而 logging 模塊就更加靈活,可以設置輸出到任意位置,如寫入文件、寫入遠程服務器等。
  • logging 模塊具有靈活的配置和格式化功能,如配置輸出當前模塊信息、運行時間等,相比 print 的字符串格式化更加方便易用。

3. 用法

3.1 默認輸出

高於WARNING的日誌信息纔會輸出

$ cat log1.py 
import logging

logging.basicConfig()
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

輸出:

$ python log1.py 
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message

3.2 日誌配置格式輸出

配置方式

  • 顯式創建記錄器Logger、處理器Handler和格式化器Formatter,並進行相關設置;
  • 通過簡單方式進行配置,使用basicConfig()函數直接進行配置;
  • 通過配置文件進行配置,使用fileConfig()函數讀取配置文件;
  • 通過配置字典進行配置,使用dictConfig()函數讀取配置信息;
  • 通過網絡進行配置,使用listen()函數進行網絡配置。

basicConfig關鍵字參數

關鍵字	描述
filename	創建一個FileHandler,使用指定的文件名,而不是使用StreamHandler。
filemode	如果指明瞭文件名,指明打開文件的模式(如果沒有指明filemode,默認爲'a')。
format	handler使用指明的格式化字符串。
datefmt	使用指明的日期/時間格式。
level	指明根logger的級別。
stream	使用指明的流來初始化StreamHandler。該參數與'filename'不兼容,如果兩個都有,'stream'被忽略。

format格式

格式	描述
%(levelno)s	打印日誌級別的數值
%(levelname)s	打印日誌級別名稱
%(pathname)s	打印當前執行程序的路徑
%(filename)s	打印當前執行程序名稱
%(funcName)s	打印日誌的當前函數
%(lineno)d	打印日誌的當前行號
%(asctime)s	打印日誌的時間
%(thread)d	打印線程id
%(threadName)s	打印線程名稱
%(process)d	打印進程ID
%(message)s	打印日誌信息

3.2.1 配置輸出級別並指定到文件

$ cat log2.py 
#!/usr/local/bin/python
# -*- coding:utf-8 -*-
import logging

# 通過下面的方式進行簡單配置輸出方式與日誌級別
logging.basicConfig(filename='logger.log', level=logging.INFO)

logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message')

輸出:

$ python log2.py
$ cat logger.log
INFO:root:info message  #info級別也能輸出
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

3.2.2 聲明瞭一個 Logger 對象,它就是日誌輸出的主類

$ cat log3.py 
#!/usr/local/bin/python
# -*- coding:utf-8 -*-
import logging

# 通過下面的方式進行簡單配置輸出方式與日誌級別
logging.basicConfig(filename='logger.log', level=logging.INFO)
logger = logging.getLogger(__name__)
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

輸出:

$ python log3.py
$ cat logger.log 
INFO:__main__:info message
WARNING:__main__:warn message
ERROR:__main__:error message
CRITICAL:__main__:critical message

3.2.3 格式化輸出

$ cat log4.py 
import logging

logging.basicConfig(filename="logger.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

輸出:

$ python log4.py
$ cat logger.log 
17-49-2020 13:49:16 __main__:DEBUG:debug message
17-49-2020 13:49:16 __main__:INFO:info message
17-49-2020 13:49:16 __main__:WARNING:warn message
17-49-2020 13:49:16 __main__:ERROR:error message
17-49-2020 13:49:16 __main__:CRITICAL:critical message

3.3 Handler配置

3.3.1 handler配置格式輸出

$ cat log5.py 
import logging
 
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
handler = logging.FileHandler('output.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
 
logger.info('This is a log info')
logger.debug('Debugging')
logger.warning('Warning exists')
logger.info('Finish')

沒有再使用 basicConfig 全局配置,而是先聲明瞭一個 Logger 對象,然後指定了其對應的 Handler 爲 FileHandler 對象,然後 Handler 對象還單獨指定了 Formatter 對象單獨配置輸出格式,最後給 Logger 對象添加對應的 Handler 即可,最後可以發現日誌就會被輸出到 output.log 中.

$ python log5.py 
$ cat output.log 
2020-05-17 13:58:41,039 - __main__ - INFO - This is a log info
2020-05-17 13:58:41,039 - __main__ - WARNING - Warning exists
2020-05-17 13:58:41,039 - __main__ - INFO - Finish
2020-05-17 14:01:15,909 - __main__ - INFO - This is a log info
2020-05-17 14:01:15,909 - __main__ - WARNING - Warning exists
2020-05-17 14:01:15,909 - __main__ - INFO - Finish

3.3.2 多種handle輸出

$ cat log6.py 
import logging
from logging.handlers import HTTPHandler
import sys
 
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
 
# StreamHandler
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=logging.DEBUG)
logger.addHandler(stream_handler)
 
# FileHandler
file_handler = logging.FileHandler('output.log')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
 
# HTTPHandler
http_handler = HTTPHandler(host='localhost:80', url='log', method='POST')
logger.addHandler(http_handler)
 
# Log
logger.info('This is a log info')
logger.debug('Debugging')
logger.warning('Warning exists')
logger.info('Finish')

輸出:

$ python log6.py
This is a log info
Debugging
Warning exists
Finish
$ cat output.log
2020-05-17 14:05:48,809 - __main__ - INFO - This is a log info
2020-05-17 14:05:48,849 - __main__ - WARNING - Warning exists
2020-05-17 14:05:48,851 - __main__ - INFO - Finish

3.4 捕獲 Traceback

$ cat log7.py
import logging
 
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
 
# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
# FileHandler
file_handler = logging.FileHandler('result.log')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
 
# StreamHandler
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
 
# Log
logger.info('Start')
logger.warning('Something maybe fail.')
try:
    result = 10 / 0
except Exception:
    logger.error('Faild to get result', exc_info=True)
    #logger.error('Faild to get result')
logger.info('Finished')

在 error() 方法中添加了一個參數,將 exc_info 設置爲了 True,這樣我們就可以輸出執行過程中的信息了,即完整的 Traceback 信息。

$ python log7.py
2020-05-17 14:13:02,217 - __main__ - INFO - Start
2020-05-17 14:13:02,217 - __main__ - WARNING - Something maybe fail.
2020-05-17 14:13:02,217 - __main__ - ERROR - Faild to get result
Traceback (most recent call last):
  File "log7.py", line 23, in <module>
    result = 10 / 0
ZeroDivisionError: integer division or modulo by zero
2020-05-17 14:13:02,217 - __main__ - INFO - Finished

3.5 配置共享

在寫項目的時候,我們肯定會將許多配置放置在許多模塊下面,這時如果我們每個文件都來配置 logging 配置那就太繁瑣了,logging 模塊提供了父子模塊共享配置的機制,會根據 Logger 的名稱來自動加載父模塊的配置。

$ vim main.py
import logging
import core
 
logger = logging.getLogger('main')
logger.setLevel(level=logging.DEBUG)
 
# Handler
handler = logging.FileHandler('result.log')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
 
logger.info('Main Info')
logger.debug('Main Debug')
logger.error('Main Error')
core.run()

這裏我們配置了日誌的輸出格式和文件路徑,同時定義了 Logger 的名稱爲 main,然後引入了另外一個模塊 core,最後調用了 core 的 run() 方法。

$ vim core.py
import logging
 
logger = logging.getLogger('main.core')
 
def run():
    logger.info('Core Info')
    logger.debug('Core Debug')
    logger.error('Core Error')

這裏我們定義了 Logger 的名稱爲 main.core,注意這裏開頭是 main,即剛纔我們在 main.py 裏面的 Logger 的名稱,這樣 core.py 裏面的 Logger 就會複用 main.py 裏面的 Logger 配置,而不用再去配置一次了.

$ python main.py
$ cat result.log 
2020-05-17 14:30:48,559 - main - INFO - Main Info
2020-05-17 14:30:48,559 - main - ERROR - Main Error
2020-05-17 14:30:48,559 - main.core - INFO - Core Info
2020-05-17 14:30:48,559 - main.core - ERROR - Core Error

3.6 文件配置

常見的配置文件有 ini 格式、yaml 格式、JSON 格式,這裏以yaml爲例。將配置寫入到配置文件,然後運行時讀取配置文件裏面的配置,這樣是更方便管理和維護.

3.6.1 yaml配置文件

$ vim config.yaml
version: 1
formatters:
  brief:
    format: "%(asctime)s - %(message)s"
  simple:
    format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
  console:
    class : logging.StreamHandler
    formatter: brief
    level   : INFO
    stream  : ext://sys.stdout
  file:
    class : logging.FileHandler
    formatter: simple
    level: DEBUG
    filename: debug.log
  error:
    class: logging.handlers.RotatingFileHandler
    level: ERROR
    formatter: simple
    filename: error.log
    maxBytes: 10485760
    backupCount: 20
    encoding: utf8
loggers:
  main.core:
    level: DEBUG
    handlers: [console, file, error]
root:
  level: DEBUG
  handlers: [console]

這裏我們定義了 formatters、handlers、loggers、root 等模塊,實際上對應的就是各個 Formatter、Handler、Logger 的配置,參數和它們的構造方法都是相同的。
定義一個主入口文件,main.py.

$ vim main.py
import logging
import core
import yaml
import logging.config
import os
import io 
 
def setup_logging(default_path='config.yaml', default_level=logging.INFO):
    path = default_path
    if os.path.exists(path):
        with io.open(path, 'r', encoding='utf-8') as f:
            config = yaml.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)
 
 
def log():
    logging.debug('Start')
    logging.info('Exec')
    logging.info('Finished')
 
 
if __name__ == '__main__':
    yaml_path = 'config.yaml'
    setup_logging(yaml_path)
    log()
    core.run()

這裏我們定義了一個 setup_logging() 方法,裏面讀取了 yaml 文件的配置,然後通過 dictConfig() 方法將配置項傳給了 logging 模塊進行全局初始化。

另外這個模塊還引入了另外一個模塊 core,所以我們定義 core.py 如下:

import logging
 
logger = logging.getLogger('main.core')
 
def run():
    logger.info('Core Info')
    logger.debug('Core Debug')
    logger.error('Core Error')

輸出:

$ python main.py 
2020-05-17 14:47:09,905 - Exec
2020-05-17 14:47:09,905 - Finished
2020-05-17 14:47:09,905 - Core Info
2020-05-17 14:47:09,905 - Core Info
2020-05-17 14:47:09,905 - Core Error
2020-05-17 14:47:09,905 - Core Error

$ cat  error.log 
2020-05-17 14:47:09,905 - main.core - ERROR - Core Error

$  cat debug.log 
2020-05-17 14:47:09,905 - main.core - INFO - Core Info
2020-05-17 14:47:09,905 - main.core - DEBUG - Core Debug
2020-05-17 14:47:09,905 - main.core - ERROR - Core Error

3.6.2 ini配置文件

下面只展示了 ini 格式和 yaml 格式的配置。

test.ini 文件
[loggers]
keys=root,sampleLogger

[handlers]
keys=consoleHandler

[formatters]
keys=sampleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)

[formatter_sampleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

testinit.py 文件

import logging.config

logging.config.fileConfig(fname='test.ini', disable_existing_loggers=False)
logger = logging.getLogger("sampleLogger")
logger.info('Core Info')
logger.debug('Core Debug')
logger.error('Core Error')

輸出:

$ python test.py 
2020-05-17 15:07:47,328 - sampleLogger - INFO - Core Info
2020-05-17 15:07:47,328 - sampleLogger - DEBUG - Core Debug
2020-05-17 15:07:47,328 - sampleLogger - ERROR - Core Error

3.7 字符串拼接

format() 構造與佔位符用%

$ log8.py
import logging
 
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
# bad
logging.debug('Hello {0}, {1}!'.format('World', 'Congratulations'))
# good
logging.debug('Hello %s, %s!', 'World', 'Congratulations')

運行結果如下:

$ python log8.py 
2020-05-17 14:55:29,709 - root - DEBUG - Hello World, Congratulations!
2020-05-17 14:55:29,709 - root - DEBUG - Hello World, Congratulations!

3.8 異常處理

$ cat log9.py
import logging
 
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
try:
    result = 5 / 0
except Exception as e:
    # bad
    logging.error('Error: %s', e)
    # good
    logging.error('Error', exc_info=True)
    # good
    logging.exception('Error')

結果輸出:

$ python log9.py
2020-05-17 14:56:51,027 - root - ERROR - Error: integer division or modulo by zero
2020-05-17 14:56:51,027 - root - ERROR - Error
Traceback (most recent call last):
  File "log9.py", line 6, in <module>
    result = 5 / 0
ZeroDivisionError: integer division or modulo by zero
2020-05-17 14:56:51,028 - root - ERROR - Error
Traceback (most recent call last):
  File "log9.py", line 6, in <module>
    result = 5 / 0
ZeroDivisionError: integer division or modulo by zero

3.9 日誌文件按照時間劃分或者按照大小劃分

如果將日誌保存在一個文件中,那麼時間一長,或者日誌一多,單個日誌文件就會很大,既不利於備份,也不利於查看。我們會想到能不能按照時間或者大小對日誌文件進行劃分呢?答案肯定是可以的,並且還很簡單,logging 考慮到了我們這個需求。logging.handlers 文件中提供了 TimedRotatingFileHandler 和 RotatingFileHandler 類分別可以實現按時間和大小劃分。打開這個 handles 文件,可以看到還有其他功能的 Handler 類,它們都繼承自基類 BaseRotatingHandler。

3.9.1 TimedRotatingFileHandler構造函數

定義如下:

TimedRotatingFileHandler(filename [,when [,interval [,backupCount]]])

示例:

# 每隔 1小時 劃分一個日誌文件,interval 是時間間隔,備份文件爲 10 個
handler2 = logging.handlers.TimedRotatingFileHandler("test.log", when="H", interval=1, backupCount=10)
  • filename: 是輸出日誌文件名的前綴,比如log/myapp.log
  • when :是一個字符串的定義如下:
    “S”: Seconds
    “M”: Minutes
    “H”: Hours
    “D”: Days
    “W”: Week day (0=Monday)
    “midnight”: Roll over at midnight
  • interval :是指等待多少個單位when的時間後,Logger會自動重建文件
  • backupCount:是保留日誌個數。默認的0是不會自動刪除掉日誌。若設3,則在文件的創建過程中庫會判斷是否有超過這個3,若超過,則會從最先創建的開始刪除。
$ cat log10.py 
#!/usr/bin/python
#---coding:utf-8

import logging
import logging.handlers
import datetime,time

#logging    初始化工作
logger = logging.getLogger("zjlogger")
logger.setLevel(logging.DEBUG)

# 添加TimedRotatingFileHandler
# 定義一個1秒換一次log文件的handler
# 保留3箇舊log文件
rf_handler = logging.handlers.TimedRotatingFileHandler(filename="all.log",when='S',interval=1,\
                                                       backupCount=3)
rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))

#在控制檯打印日誌
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))

logger.addHandler(rf_handler)
logger.addHandler(handler)

while True:
    logger.debug('debug message')
    logger.info('info message')
    logger.warning('warning message')
    logger.error('error message')
    logger.critical('critical message')
    time.sleep(1)
$ ls
all.log  all.log.2020-05-17_15-21-55  all.log.2020-05-17_15-21-56  all.log.2020-05-17_15-21-57  log10.py

3.9.2 RotatingFileHandler 構造函數

示例:

# 每隔 1000 Byte 劃分一個日誌文件,備份文件爲 3 個
file_handler = logging.handlers.RotatingFileHandler("test.log", mode="w", maxBytes=1000, backupCount=3, encoding="utf-8")
複製代碼

$ cat log11.py 
#!coding:utf-8
#!/usr/bin/env python
import time
import logging
import logging.handlers
# logging初始化工作
logging.basicConfig()
# myapp的初始化工作
myapp = logging.getLogger('myapp')
myapp.setLevel(logging.INFO)
# 寫入文件,如果文件超過100個Bytes,僅保留5個文件。
handler = logging.handlers.RotatingFileHandler(
              'logs/myapp.log', maxBytes=100, backupCount=5)
myapp.addHandler(handler)
while True:
    time.sleep(0.01)
    myapp.info("file test")

輸出:

$ python log11.py
INFO:myapp:file test
INFO:myapp:file test
INFO:myapp:file test
INFO:myapp:file test
.....
$ ls logs
myapp.log  myapp.log.1  myapp.log.2  myapp.log.3  myapp.log.4  myapp.log.5

參考鏈接:
進擊的Coder

vigny的先生

好吃的野菜

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章