【筆記】html
1.把logging配置文件提取出來,寫在logging.yaml文件中加載,比json加載簡單並且不會報錯;python
2.修改getLogger參數,在每個函數內部設置logger的名稱:react
3.修改配置文件中的日期格式:datefmt: "%m-%d %A %H:%M",效果以下:git
===================================================程序員
Python的logging模塊提供了通用的日誌系統,能夠方便第三方模塊或者是應用使用。這個模塊提供不一樣的日誌級別,並能夠採用不一樣的方式記錄日誌,好比文件,HTTP GET/POST,SMTP,Socket等,甚至能夠本身實現具體的日誌記錄方式。github
logging模塊與log4j的機制是同樣的,只是具體的實現細節不一樣。模塊提供logger,handler,filter,formatter。web
logger:提供日誌接口,供應用代碼使用。logger最長用的操做有兩類:配置和發送日誌消息。能夠經過logging.getLogger(name)獲取logger對象,若是不指定name則返回root對象,屢次使用相同的name調用getLogger方法返回同一個logger對象。算法
handler:將日誌記錄(log record)發送到合適的目的地(destination),好比文件,socket等。一個logger對象能夠經過addHandler方法添加0到多個handler,每一個handler又能夠定義不一樣日誌級別,以實現日誌分級過濾顯示。json
filter:提供一種優雅的方式決定一個日誌記錄是否發送到handler。ubuntu
formatter:指定日誌記錄輸出的具體格式。formatter的構造方法須要兩個參數:消息的格式字符串和日期字符串,這兩個參數都是可選的。
與log4j相似,logger,handler和日誌消息的調用能夠有具體的日誌級別(Level),只有在日誌消息的級別大於logger和handler的級別。
logging的配置能夠採用python代碼或是配置文件。python代碼的方式就是在應用的主模塊中,構建handler,handler,formatter等對象。而配置文件的方式是將這些對象的依賴關係分離出來放在文件中。好比前面的例子就相似於python代碼的配置方式。
這裏還要明確一點,logger對象是有繼承關係的,好比名爲a.b和a.c的logger都是名爲a的子logger,而且全部的logger對象都繼承於root。若是子對象沒有添加handler等一些配置,會從父對象那繼承。這樣就能夠經過這種繼承關係來複用配置。
logging模塊保證在同一個python解釋器內,屢次調用logging.getLogger('log_name')都會返回同一個logger實例,即便是在多個模塊的狀況下。因此典型的多模塊場景下使用logging的方式是在main模塊中配置logging,這個配置會做用於多個的子模塊,而後在其餘模塊中直接經過getLogger獲取Logger對象便可。
轉自:http://blog.csdn.net/chosen0ne/article/details/7319306
在現實生活中,記錄日誌很是重要。銀行轉帳時會有轉帳記錄;飛機飛行過程當中,會有黑盒子(飛行數據記錄器)記錄飛行過程當中的一切。若是有出現什麼問題,人們能夠經過日誌數據來搞清楚到底發生了什麼。
對於系統開發、調試以及運行,記錄日誌都是一樣的重要。若是沒有日誌記錄,程序崩潰時你幾乎就沒辦法弄明白到底發生了什麼事情。舉個例子,當你在寫一個服務器程序時,記錄日誌是很是有必要的。下面展現的就是 EZComet.com 服務器的日誌文件截圖。
服務崩潰後,若是沒有日誌,我幾乎沒辦法知道到底發生了錯誤。日誌不只對於服務器很重要,對於桌面圖形應用一樣十分重要。好比,當你的客戶的 PC 機程序崩潰時,你可讓他們把日誌文件發給你,這樣你就能夠找到問題到底出在哪兒。相信我,在不一樣的 PC 環境下,你永遠不會知道會有怎樣奇怪的問題。我曾經就接收到過這樣的錯誤日誌。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
2011
-
08
-
22
17
:
52
:
54
,
828
-
root
-
ERROR
-
[
Errno
10104
]
getaddrinfo
failed
Traceback
(
most
recent
call
last
)
:
File
"<string>"
,
line
124
,
in
main
File
"<string>"
,
line
20
,
in
__init__
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/wx._core"
,
line
7978
,
in
__init__
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/wx._core"
,
line
7552
,
in
_BootstrapApp
File
"<string>"
,
line
84
,
in
OnInit
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/twisted.internet.wxreactor"
,
line
175
,
in
install
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/twisted.internet._threadedselect"
,
line
106
,
in
__init__
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/twisted.internet.base"
,
line
488
,
in
__init__
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/twisted.internet.posixbase"
,
line
266
,
in
installWaker
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/twisted.internet.posixbase"
,
line
74
,
in
__init__
File
"h:workspaceprojectbuildpyi.win32mrdjoutPYZ1.pyz/socket"
,
line
224
,
in
meth
gaierror
:
[
Errno
10104
]
getaddrinfo
failed
|
我最終發現,這個客戶的 PC 機被一種病毒感染,致使了調用 gethostname 函數失敗。看吧,若是沒有日誌能夠查你怎麼可能知道這些。
儘管記錄日誌很是重要,可是並非全部的開發者都能正確地使用它。我曾看到一些開發者是這樣記錄日誌的,在開發的過程當中插入 print 語句,開發結束後再將這些語句移除。就像這樣:
1
2
3
4
5
6
|
print
'Start reading database'
records
=
model
.
read_recrods
(
)
print
'# records'
,
records
print
'Updating record ...'
model
.
update_records
(
records
)
print
'done'
|
這種方式對於簡單腳本型程序有用,可是若是是複雜的系統,你最好不要使用這樣的方式。首先,你沒辦法作到在日誌文件中只留下極其重要的消息。你會看到大量的消息日誌。可是你卻找不到任何有用的信息。你除了移除這輸出語句這外,沒別的辦法控制代碼,可是極有可能的是你忘記了移出那些沒用的輸出。再者,print 輸出的全部信息都到了標準輸出中,這將嚴重影響到你從標準輸出中查看其它輸出數據。固然,你也能夠把消息輸出到 stderr ,可是用 print 作日誌記錄的方式仍是很差。
那麼,怎麼樣記錄日誌纔是正確的呢?其實很是簡單,使用 python 的標準日誌模塊。多虧 python 社區將日誌作成了一個標準模塊。它很是簡單易用且十分靈活。你能夠像這樣使用日誌系統:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import
logging
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logger
=
logging
.
getLogger
(
__name__
)
logger
.
info
(
'Start reading database'
)
# read database here
records
=
{
'john'
:
55
,
'tom'
:
66
}
logger
.
debug
(
'Records: %s'
,
records
)
logger
.
info
(
'Updating records ...'
)
# update records here
logger
.
info
(
'Finish updating records'
)
|
運行的時候就可看到:
1
2
3
|
INFO
:
__main__
:
Start
reading
database
INFO
:
__main__
:
Updating
records
.
.
.
INFO
:
__main__
:
Finish
updating
records
|
你可能會問這與使用 print 有什麼不一樣呢。它有如下的優點:
有許多的重要性別級可供選擇,debug、info、warning、error 以及 critical。經過賦予 logger 或者 handler 不一樣的級別,你就能夠只輸出錯誤消息到特定的記錄文件中,或者在調試時只記錄調試信息。讓咱們把 logger 的級別改爲 DEBUG 再看一下輸出結果:
1
|
logging
.
basicConfig
(
level
=
logging
.
DEBUG
)
|
輸出變成了:
1
2
3
4
|
INFO
:
__main__
:
Start
reading
database
DEBUG
:
__main__
:
Records
:
{
'john'
:
55
,
'tom'
:
66
}
INFO
:
__main__
:
Updating
records
.
.
.
INFO
:
__main__
:
Finish
updating
records
|
正如看到的那樣,咱們把 logger 的等級改成 DEBUG 後,調試記錄就出如今了輸出當中。你也能夠選擇怎麼處理這些消息。例如,你可使用 FileHandler 把記錄寫進文件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import
logging
logger
=
logging
.
getLogger
(
__name__
)
logger
.
setLevel
(
logging
.
INFO
)
# create a file handler
handler
=
logging
.
FileHandler
(
'hello.log'
)
handler
.
setLevel
(
logging
.
INFO
)
# create a logging format
formatter
=
logging
.
Formatter
(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler
.
setFormatter
(
formatter
)
# add the handlers to the logger
logger
.
addHandler
(
handler
)
logger
.
info
(
'Hello baby'
)
|
標準庫模塊中提供了許多的 handler ,你能夠將記錄發送到郵箱甚至發送到一個遠程的服務器。你也能夠實現本身的記錄 handler 。這裏將不具體講述實現的細節,你能夠參考官方文檔:Basci Turial、Advanced Tutorial 與 Logging Cookbook。
有了靈活的日誌記錄模塊後,你能夠按適當的等級將日誌記錄輸出到任何地方而後配置它們。那麼你可能會問,什麼是合適的等級呢?在這兒我將分享一些個人經驗。
大多數的狀況下,你都不想閱讀日誌中的太多細節。所以,只有你在調試過程當中纔會使用 DEBUG 等級。我只使用 DEBUG 獲取詳細的調試信息,特別是當數據量很大或者頻率很高的時候,好比算法內部每一個循環的中間狀態。
1
2
3
4
5
|
def
complex_algorithm
(
items
)
:
for
i
,
item
in
enumerate
(
items
)
:
# do some complex algorithm computation
logger
.
debug
(
'%s iteration, item=%s'
,
i
,
item
)
|
在處理請求或者服務器狀態變化等平常事務中,我會使用 INFO 等級。
1
2
3
4
5
6
7
8
9
10
11
|
def
handle_request
(
request
)
:
logger
.
info
(
'Handling request %s'
,
request
)
# handle request here
result
=
'result'
logger
.
info
(
'Return result: %s'
,
result
)
def
start_service
(
)
:
logger
.
info
(
'Starting service at port %s ...'
,
port
)
service
.
start
(
)
logger
.
info
(
'Service is started'
)
|
當發生很重要的事件,可是並非錯誤時,我會使用 WARNING 。好比,當用戶登陸密碼錯誤時,或者鏈接變慢時。
1
2
3
4
5
|
def
authenticate
(
user_name
,
password
,
ip_address
)
:
if
user_name
!=
USER_NAME
and
password
!=
PASSWORD
:
logger
.
warn
(
'Login attempt to %s from IP %s'
,
user_name
,
ip_address
)
return
False
# do authentication here
|
有錯誤發生時確定會使用 ERROR 等級了。好比拋出異常,IO 操做失敗或者鏈接問題等。
1
2
3
4
5
6
|
def
get_user_by_id
(
user_id
)
:
user
=
db
.
read_user
(
user_id
)
if
user
is
None
:
logger
.
error
(
'Cannot find user with user_id=%s'
,
user_id
)
return
user
return
user
|
我不多使用 CRITICAL 。當一些特別糟糕的事情發生時,你可使用這個級別來記錄。比方說,內存耗盡,磁盤滿了或者核危機(但願永遠別發生 :S)。
雖然不是非得將 logger 的名稱設置爲 __name__ ,可是這樣作會給咱們帶來諸多益處。在 python 中,變量 __name__ 的名稱就是當前模塊的名稱。好比,在模塊 「foo.bar.my_module」 中調用 logger.getLogger(__name__) 等價於調用logger.getLogger(「foo.bar.my_module」) 。當你須要配置 logger 時,你能夠配置到 「foo」 中,這樣包 foo 中的全部模塊都會使用相同的配置。當你在讀日誌文件的時候,你就可以明白消息到底來自於哪個模塊。
出問題的時候記錄下來是個好習慣,可是若是沒有 traceback ,那麼它一點兒用也沒有。你應該捕獲異常並用 traceback 把它們記錄下來。好比下面這個例子:
1
2
3
4
5
6
|
try
:
open
(
'/path/to/does/not/exist'
,
'rb'
)
except
(
SystemExit
,
KeyboardInterrupt
)
:
raise
except
Exception
,
e
:
logger
.
error
(
'Failed to open file'
,
exc_info
=
True
)
|
使用參數 exc_info=true 調用 logger 方法, traceback 會輸出到 logger 中。你能夠看到下面的結果:
1
2
3
4
5
|
ERROR
:
__main__
:
Failed
to
open
file
Traceback
(
most
recent
call
last
)
:
File
"example.py"
,
line
6
,
in
<
module
>
open
(
'/path/to/does/not/exist'
,
'rb'
)
IOError
:
[
Errno
2
]
No
such
file
or
directory
:
'/path/to/does/not/exist'
|
你也能夠調用 logger.exception(msg, _args),它等價於 logger.error(msg, exc_info=True, _args)。
千萬不要在模塊層次獲取 Logger,除非 disable_existing_loggers 被設置爲 False
你能夠看到不少在模塊層次獲取 logger 的例子(在這篇文章我也使用了不少,但這僅僅爲了讓示例更短一些)。它們看上去沒什麼壞處,但事實上,這兒是有陷阱的 – 若是你像這樣在模塊中使用 Logger,Python 會保留從文件中讀入配置前全部建立的全部 logger。
my_module.py
1
2
3
4
5
6
7
8
9
10
|
import
logging
logger
=
logging
.
getLogger
(
__name__
)
def
foo
(
)
:
logger
.
info
(
'Hi, foo'
)
class
Bar
(
object
)
:
def
bar
(
self
)
:
logger
.
info
(
'Hi, bar'
)
|
main.py
1
2
3
4
5
6
7
8
9
10
|
import
logging
logger
=
logging
.
getLogger
(
__name__
)
def
foo
(
)
:
logger
.
info
(
'Hi, foo'
)
class
Bar
(
object
)
:
def
bar
(
self
)
:
logger
.
info
(
'Hi, bar'
)
|
logging.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[
loggers
]
keys
=
root
[
handlers
]
keys
=
consoleHandler
[
formatters
]
keys
=
simpleFormatter
[
logger_root
]
level
=
DEBUG
handlers
=
consoleHandler
[
handler_consoleHandler
]
class
=
StreamHandler
level
=
DEBUG
formatter
=
simpleFormatter
args
=
(
sys
.
stdout
,
)
[
formatter_simpleFormatter
]
format
=
%
(
asctime
)
s
-
%
(
name
)
s
-
%
(
levelname
)
s
-
%
(
message
)
s
datefmt
=
|
本應該在日誌中看到記錄,可是你卻什麼也沒有看到。爲何呢?這就是由於你在模塊層次建立了 logger,而後你又在加載日誌配置文件以前就導入了模塊。logging.fileConfig 與 logging.dictConfig 默認狀況下會使得已經存在的 logger 失效。因此,這些配置信息不會應用到你的 Logger 上。你最好只在你須要 logger 的時候纔得到它。反正建立或者取得 logger 的成本很低。你能夠這樣寫你的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
import
logging
def
foo
(
)
:
logger
=
logging
.
getLogger
(
__name__
)
logger
.
info
(
'Hi, foo'
)
class
Bar
(
object
)
:
def
__init__
(
self
,
logger
=
None
)
:
self
.
logger
=
logger
or
logging
.
getLogger
(
__name__
)
def
bar
(
self
)
:
self
.
logger
.
info
(
'Hi, bar'
)
|
這樣,logger 就會在你加載配置後纔會被建立。這樣配置信息就能夠正常應用。
python2.7 以後的版本中 fileConfg 與 dictConfig 都新添加了 「disable_existing_loggers」 參數,將其設置爲 False,上面提到的問題就能夠解決了。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import
logging
import
logging.config
logger
=
logging
.
getLogger
(
__name__
)
# load config from file
# logging.config.fileConfig('logging.ini', disable_existing_loggers=False)
# or, for dictConfig
logging.config
.
dictConfig
(
{
'version'
:
1
,
'disable_existing_loggers'
:
False
,
# this fixes the problem
'formatters'
:
{
'standard'
:
{
'format'
:
'%(asctime)s [%(levelname)s] %(name)s: %(message)s'
}
,
}
,
'handlers'
:
{
'default'
:
{
'level'
:
'INFO'
,
'class'
:
'logging.StreamHandler'
,
}
,
}
,
'loggers'
:
{
''
:
{
'handlers'
:
[
'default'
]
,
'level'
:
'INFO'
,
'propagate'
:
True
}
}
}
)
logger
.
info
(
'It works!'
)
|
雖然你能夠在 python 代碼中配置你的日誌系統,可是這樣並不夠靈活。最好的方法是使用一個配置文件來配置。在 Python2.7 及以後的版本中,你能夠從字典中加載 logging 配置。這也就意味着你能夠從 JSON 或者 YAML 文件中加載日誌的配置。儘管你還能用原來 .ini 文件來配置,可是它既很難讀也很難寫。下面我給大家看一個用 JSON 和 YAML 文件配置的例子:
logging.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
{
"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"
]
}
}
|
logging.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
--
-
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
# 10MB
backupCount
:
20
encoding
:
utf8
error_file_handler
:
class
:
logging.handlers
.
RotatingFileHandler
level
:
ERROR
formatter
:
simple
filename
:
errors
.
log
maxBytes
:
10485760
# 10MB
backupCount
:
20
encoding
:
utf8
loggers
:
my_module
:
level
:
ERROR
handlers
:
[
console
]
propagate
:
no
root
:
level
:
INFO
handlers
:
[
console
,
info_file_handler
,
error_file_handler
]
.
.
.
|
接下來將展現怎樣從 JSON 文件中讀入日誌的配置信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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 的一個優勢就是 json是一個標準庫,你不須要額外安裝它。可是從我我的來講,我比較喜歡 YAML 一些。它不管是讀起來仍是寫起來都比較容易。你也可使用下面的方法來加載一個 YAML 配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import
os
import
logging.config
import
yaml
def
setup_logging
(
default_path
=
'logging.yaml'
,
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
=
yaml
.
load
(
f
.
read
(
)
)
logging.config
.
dictConfig
(
config
)
else
:
lo
|
接下來,你就能夠在運行程序的時候調用 setup_logging 來啓動日誌記錄了。它默認會讀取 logging.json 或 logging.yaml 文件 。你也能夠設置環境變量 LOG_CCFG 從指定路徑加載日誌配置。例如:
1
|
LOG_CFG
=
my_logging
.
json
python
my_server
.
py
|
若是你喜歡 YAML:
1
|
LOG_CFG
=
my_logging
.
yaml
python
my_server
.
py
|
若是你用 FileHandler 寫日誌,文件的大小會隨着時間推移而不斷增大。最終有一天它會佔滿你全部的磁盤空間。爲了不這種狀況出現,你能夠在你的生成環境中使用 RotatingFileHandler 替代 FileHandler。