Django日志詳解
Django對於日志輸出的信息是很完善的,request的信息,setting配置,trackback的信息,一應俱全,足夠我們調試了。但是在線上環境,如果讓用戶看到這些信息,是很不安全的(暴露代碼)。所以在線上我們要關閉Debug,但是又不能扔掉這些調試信息,這就要用到日志了。Django使用Python內建的logging
模塊打印日志,但在Django中有很多本地化的支持。關於該模塊具體介紹及使用方式可參考logging。
首先要理解logging的工作,這里面主要有四個東西:格式器formatter,過濾器filter,處理器handler,記錄器logger。
1
2
3
|
formatter
logger ----> handler ----------------> files, emails
filter
|
- Loggers
logger 是日志系統的入口。每個 logger 都是命名了的 bucket,消息寫入 bucket 以便進一步處理。
logger 可以配置日志級別。日志級別描述了由該 logger 處理的消息的嚴重性。Python 定義了下面幾種日志級別:
DEBUG:排查故障時使用的低級別系統信息
INFO:一般的系統信息
WARNING:描述系統發生了一些小問題的信息
ERROR:描述系統發生了大問題的信息
CRITICAL:描述系統發生嚴重問題的信息
每一條寫入 logger 的消息都是一條*日志記錄*。每一條日志記錄也包含*日志級別*,代表對應消息的嚴重程度。日志記錄還包含有用的元數據,來描述被記錄了日志的事件細節,例如堆棧跟蹤或者錯誤碼。
當 logger 處理一條消息時,會將自己的日志級別和這條消息的日志級別做對比。如果消息的日志級別匹配或者高於 logger 的日志級別,它就會被進一步處理。否則這條消息就會被忽略掉。
當 logger 確定了一條消息需要處理之后,會把它傳給 Handler。
- Handlers
Handler 是決定如何處理 logger 中每一條消息的引擎。它描述特定的日志行為,比如把消息輸出到屏幕、文件或網絡 socket。和 logger 一樣,handler 也有日志級別的概念。如果一條日志記錄的級別不匹配或者低於 handler 的日志級別,對應的消息會被 handler 忽略。
一個 logger 可以有多個 handler,每一個 handler 可以有不同的日志級別。這樣就可以根據消息的重要性不同,來提供不同格式的輸出。例如,你可以添加一個 handler 把 ERROR 和 CRITICAL 消息發到尋呼機,再添加另一個 handler 把所有的消息(包括 ERROR 和 CRITICAL 消息)保存到文件里以便日后分析。
- Filters
在日志記錄從 logger 傳到 handler 的過程中,使用 Filter 來做額外的控制。
默認情況下,只要級別匹配,任何日志消息都會被處理。不過,也可以通過添加 filter 來給日志處理的過程增加額外條件。例如,可以添加一個 filter 只允許某個特定來源的 ERROR 消息輸出。
Filter 還被用來在日志輸出之前對日志記錄做修改。例如,可以寫一個 filter,當滿足一定條件時,把日志記錄從 ERROR 降到 WARNING 級別。
Filter 在 logger 和 handler 中都可以添加;多個 filter 可以鏈接起來使用,來做多重過濾操作。
- Formatters
日志記錄最終是需要以文本來呈現的。Formatter 描述了文本的格式。一個 formatter 通常由包含 :ref:`LogRecord attributes `<python:logrecord-attributes> 的 Python 格式化字符串組成,不過你也可以為特定的格式來配置自定義的 formatter。
一、使用logging模塊
配置好了 logger,handler,filter 和 formatter 之后,需要在代碼里發起 logging 的調用。使用 logging 框架非常簡單,下面是個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import logging
# 生成一個以當前模塊名為名字的logger實例
logger = logging.getLogger(__name__)
# 生成一個名為mysite.polls.views的logger實例
CUSTOM_LOGGER = logging.getLogger("mysite.polls.views")
def index(request):
CUSTOM_LOGGER.debug("debug log")
CUSTOM_LOGGER.info("info log")
CUSTOM_LOGGER.error("error log")
try:
mysql = connect('127.0.0.1', '3306', 'david')
except Exception as e:
# 寫入日志文件異常名及異常信息
CUSTOM_LOGGER.error(f'{e.__class__.__name__}: {e}')
|
為 logger 命名
對logging.getLogger()
的調用會獲取(必要時會創建)一個 logger 的實例。不同的 logger 實例用名字來區分。這個名字是為了在配置的時候指定 logger。
按照慣例,logger 的名字通常是包含該 logger 的 Python 模塊名,即 __name__
。這樣可以基於模塊來過濾和處理日志請求。不過,如果你有其他的方式來組織你的日志消息,可以為 logger 提供點號分割的名字來標識它:
1
2
|
# 獲取特定命名記錄器的實例
logger = logging.getLogger('project.interesting.stuff')
|
這種 logger 的名字,用點號分隔的路徑定義了一種層次結構。project.interesting
這個 logger 是 project.interesting.stuff
logger 的上級;而 project
logger 是 project.interesting
logger 的上級。
為什么這種層級結構是重要的呢?因為 logger 可以設置為將日志的請求 *傳播* 給上級。這樣就可以在 logger 樹結構的頂層定義一組單獨的 handler,來捕獲所有下層的日志請求。在 project
命名空間中定義的 logger handler 將會捕獲 project.interesting
和 project.interesting.stuff
這兩個 logger 中的所有日志請求。
可以基於 logger 來控制傳播的行為,如果你不希望某個 logger 傳播給上級,可以關閉它。
發起 logging 調用
logger 實例包含了每種默認日志級別的入口方法:
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
還有兩種其他的調用方法:
logger.log()
:手動輸出一條指定日志級別的日志消息。logger.exception()
:創建一個包含當前異常堆棧幀的 ERROR 級別日志消息。
二、日志模塊的配置
當然,僅僅在代碼里調用 logging 是不夠的。還需要配置 logger、handler、filter 和 formatter 來確保日志框架能有效地輸出日志。
Python的logging
模塊提供了好幾種配置方式。默認情況下,Django使用dictConfig format,也就是字典方式。在Django中通過在settings文件中使用LOGGING
來定制日志輸出。
為了配置 logging ,用字典的格式定義一個 LOGGING
配置項,這些配置描述了你想要的 logger、handler、filter 和 formatter,以及它們的日志級別和其他你想要的屬性。
默認情況下 LOGGING
配置和 “Django的默認日志配置” 按照下面的方式合並在一起:
如果LOGGING
中的disable_existing_loggers
值為True
(默認值),那么默認配置中的所有 logger 都將禁用。logger 的禁用與刪除不同,logger仍然存在,但是將默默丟棄任何傳遞給它的信息,也不會傳播給上一級 logger。所以,你應該非常小心使用'disable_existing_loggers': True
,它可能不是你想要的。相反你應該把disable_existing_loggers
設置為False
,然后再重新定義其中的一些默認 loggers。
或者你也可以將LOGGING_CONFIG
設置為None
,這將禁用Django默認日志記錄。這是一個禁用Django日志配置然后手動配置日志記錄的示例:
1
2
3
4
5
|
# settings.py
LOGGING_CONFIG = None
import logging.config
logging.config.dictConfig(...)
|
logging 被配置成了 Django setup()
函數的一部分。因此,你可以確定的是,logger 一直都可以在項目代碼里使用。
這是一個簡單的配置,它將來自django記錄器的所有日志記錄寫入本地文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/path/to/django/debug.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
|
如果你使用此示例,請確保將“filename”路徑更改為運行Django應用程序的用戶可寫的位置。
其次,這是一個如何使日志系統將Django的日志記錄打印到控制台的示例。它可能在本地開發期間很有用。
默認情況下,此配置僅向控制台發送級別為 INFO 或更高級別的消息(與Django的默認日志記錄配置相同,但默認情況下僅在 DEBUG=True 時顯示日志記錄)。 Django沒有記錄很多這樣的消息。但是,使用此配置,你還可以設置環境變量 DJANGO_LOG_LEVEL = DEBUG 以查看 Django 的所有調試日志記錄,因為它包含所有數據庫查詢,因此非常詳細:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import os
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
},
},
}
|
最后,這是一個相當復雜的日志設置示例:
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
|
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': {
'special': {
'()': 'project.logging.SpecialFilter',
'foo': 'bar',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'filters': ['special']
}
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
'myproject.custom': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
'filters': ['special']
}
}
}
|
此日志記錄配置執行以下操作:
將配置標識為“dictConfig version 1”格式。目前,這是唯一的dictConfig格式版本。
定義兩個 formatters:
simple,只輸出日志級別名稱(例如,DEBUG)和日志消息。
verbose,輸出日志級別名稱,日志消息,以及生成日志消息的時間,進程,線程和模塊。
其中格式字符串是一個普通的Python格式化字符串,用於描述要在每個日志記錄行上輸出的詳細信息。而 style 參數默認為 “%” 百分符,這表示前面的格式化字符串參數應該是一個%(<dictionary key>)s
格式的字符串,這里將 style 參數設置為 “{” 花括號,就可以將格式化字符串參數寫成 {(<dictionary key>)}
格式。而可以使用的logging內置的keys,如下表所示:
屬性 | 格式 | 描述 |
---|---|---|
asctime | %(asctime)s | 日志產生的時間,默認格式為2003-07-08 16:49:45,896 |
created | %(created)f | time.time()生成的日志創建時間戳 |
filename | %(filename)s | 生成日志的程序名 |
funcName | %(funcName)s | 調用日志的函數名 |
levelname | %(levelname)s | 日志級別 (‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’) |
levelno | %(levelno)s | 日志級別對應的數值 |
lineno | %(lineno)d | 日志所針對的代碼行號(如果可用的話) |
module | %(module)s | 生成日志的模塊名 |
msecs | %(msecs)d | 日志生成時間的毫秒部分 |
message | %(message)s | 具體的日志信息 |
name | %(name)s | 日志調用者 |
pathname | %(pathname)s | 生成日志的文件的完整路徑 |
process | %(process)d | 生成日志的進程ID(如果可用) |
processName | %(processName)s | 進程名(如果可用) |
thread | %(thread)d | 生成日志的線程ID(如果可用) |
threadName | %(threadName)s | 線程名(如果可用) |
定義兩個 filters:
project.logging.SpecialFilter,使用 special 別名。如果此過濾器需要其他參數,則可以將它們作為過濾器配置字典中的附加鍵提供。在這個例子中,在實例化 SpecialFilter 時,參數foo將被賦予bar值。
django.utils.log.RequireDebugTrue,當DEBUG為True時傳遞記錄。
定義兩個 handlers:
console,一個StreamHandler,它向sys.stderr輸出任何INFO(或更高版本)消息。此處理程序使用簡單的輸出格式。
mail_admins,一個AdminEmailHandler,它將任何ERROR(或更高版本)消息通過電子郵件發送到站點ADMINS。此處理程序使用特殊過濾器。
定義三個 loggers:
django,它將所有消息傳遞給控制台處理程序。
django.request,它將所有ERROR消息傳遞給mail_admins處理程序。此外,此記錄器標記為不傳播消息。這意味着django記錄器不會處理寫入django.request的日志消息。
myproject.custom,它傳遞INFO或更高級別的所有消息,這些消息也將 special 過濾器傳遞給兩個 handler 程序 – console和mail_admins。這意味着所有INFO級別消息(或更高級別)將打印到console;ERROR和CRITICAL消息也將通過郵件輸出。
最后再給一個目前項目中使用的日志案例:
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
74
75
76
77
78
79
80
81
82
83
84
|
LOGGING = {
'version': 1,
'disable_existing_loggers': Flase,
# 日志文件的格式
'formatters': {
# 詳細的日志格式
'standard': {
'format': '%(asctime)s '
'%(process)s:'
'%(threadName)s:'
'%(thread)d '
'%(name)s:%(module)s:%(funcName)s:%(lineno)d '
'%(levelname)s '
'%(message)s'
},
# 簡單的日志格式
'simple': {
'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
# 定義一個特殊的日志格式
'collect': {
'format': '%(message)s'
}
},
# 過濾器
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
# 處理器
'handlers': {
# 在終端打印
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug為True時才在屏幕打印日志
'class': 'logging.StreamHandler', #
'formatter': 'simple'
},
# 默認的
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自動切割
'filename': 'logs/info.log', # 日志文件位置,當前項目logs目錄下
'maxBytes': 1024 * 1024 * 50, # 日志大小50M,不定義就一直增長
'backupCount': 3, # 定義日志大小情況下,默認保留幾個
'formatter': 'standard', # 日志格式
'encoding': 'utf-8', # 日志編碼
},
# 專門用來記錯誤日志
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/error.log',
'maxBytes': 1024 * 1024 * 50,
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
# 專門定義一個收集特定信息的日志
'collect': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/collect.log',
'maxBytes': 1024 * 1024 * 50,
'backupCount': 5,
'formatter': 'collect',
'encoding': "utf-8"
}
},
'loggers': {
# 默認的logger應用如下配置
'django.server': {
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': True, # 向不向更高級別的logger傳遞
},
# 名為'mysite.polls.views'的logger單獨處理
'mysite.polls.views': {
'handlers': ['error', 'console'],
'propagate': True,
}
},
}
|
三、Django對logging模塊的擴展
Django對logging模塊進行了一定的擴展,用來滿足Web服務器專門的日志記錄需求。
1. 記錄器Loggers
Django額外提供了幾個其內建的logger。
- django
不要使用這個記錄器,用下面的。這是一個被供起來的記錄器。
- django.request
記錄與請求處理相關的消息。HTTP 5XX響應記錄為ERROR消息,4XX響應記錄為WARNING消息。記錄到django.security記錄器的請求不會記錄到django.request。
另外,發送給此記錄器的消息具有以下額外的信息:
status_code:與請求關聯的HTTP響應狀態碼。
request:生成日志消息的請求對象。
- django.server
記錄與runserver命令調用的服務器接收的請求的處理相關的消息。 HTTP 5XX響應記錄為ERROR消息,4XX響應記錄為WARNING消息,其他所有響應記錄為INFO。同樣,也具有 status_code 與 request 信息。
- django.template
記錄與渲染模板相關的日志。
- django.db.backends
記錄與數據庫交互相關的消息。例如,請求執行的每個應用程序級SQL語句都會在DEBUG級別記錄到此記錄器。
另外,發送給此記錄器的消息具有以下額外的信息:
duration:執行SQL語句所需的時間。
sql:已執行的SQL語句。
params:SQL調用中使用的參數。
出於性能原因,只有在settings.DEBUG設置為True時才會啟用SQL日志記錄,而不管安裝的日志記錄級別或處理程序如何。
此日志記錄不包括框架級初始化(例如SET TIMEZONE)或事務管理查詢(例如BEGIN,COMMIT和ROLLBACK)。如果要查看所有數據庫查詢,請在數據庫中啟用查詢日志記錄。
- django.security
記錄任何與安全相關的錯誤。
- django.security.csrf
記錄CSRF驗證失敗日志。
- django.db.backends.schema
當遷移框架執行的SQL 查詢會改變數據庫的模式時,則記錄這些SQL 查詢。注意,它不會記錄 RunPython 執行的查詢。
2. 處理器Handlers
- class AdminEmailHandler(include_html=False, email_backend=None)
Django額外提供了一個handler,AdminEmailHandler。這個處理器將它收到的每個日志信息用郵件發送給站點管理員。
如果日志記錄包含 request 屬性,該請求的完整細節都將包含在郵件中。如果日志記錄包含棧追蹤信息,該棧追蹤信息也將包含在郵件中。
AdminEmailHandler 的 include_html 參數用於控制郵件中是否包含 HTML 附件,這個附件包含 DEBUG 為 True 時的完整網頁。若要在配置中設置這個值,可以將它包含在 django.utils.log.AdminEmailHandler handler 的定義中,像下面這樣:
1
2
3
4
5
6
7
|
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
}
},
|
注意,郵件中的 HTML 包含完整的跟蹤棧,包括棧每個層級局部變量的名稱和值以及你的 Django 設置。這些信息可能非常敏感,你也許不想通過郵件發送它們。此時可以考慮使用類似 Sentry 這樣的東西。
通過設置 AdminEmailHandler 的 email_backend 參數,可以覆蓋 handler 使用的 email backend,像這樣:
1
2
3
4
5
6
7
|
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
}
},
|
默認情況下,將使用EMAIL_BACKEND 中指定的郵件后端。
- send_mail(subject, message, *args, **kwargs)
向管理員用戶發送電子郵件。若要自定義此行為,你可以繼承AdminEmailHandler類並重寫此方法。
3. 過濾器Filters
Django還額外提供兩個過濾器。
- class CallbackFilter(callback)
這個過濾器接受一個回調函數,並對每個傳遞給過濾器的記錄調用它。如果回調函數返回False,將不會進行記錄的處理。
例如,要從管理員電子郵件中過濾掉UnreadablePostError(在用戶取消上載時引發),你將創建一個過濾器函數:
1
2
3
4
5
6
7
8
|
from django.http import UnreadablePostError
def skip_unreadable_post(record):
if record.exc_info:
exc_type, exc_value = record.exc_info[:2]
if isinstance(exc_value, UnreadablePostError):
return False
return True
|
然后把它添加到你的日志配置中:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
'filters': {
'skip_unreadable_posts': {
'()': 'django.utils.log.CallbackFilter',
'callback': skip_unreadable_post,
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['skip_unreadable_posts'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
|
- class RequireDebugFalse
此過濾器僅在settings.DEBUG為False時傳遞記錄。
此過濾器在默認LOGGING配置中使用如下,以確保AdminEmailHandler僅在DEBUG為False時向管理員發送錯誤電子郵件:
1
2
3
4
5
6
7
8
9
10
11
12
|
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
|
- class RequireDebugTrue
此過濾器類似於RequireDebugFalse,但只有在DEBUG為True時才傳遞記錄。
四、Django的默認日志配置
當DEBUG為True時:
django logger 將 INFO 級別或更高級別的django層次結構(django.server除外)中的消息發送到控制台。
當DEBUG為False時:
django logger 將具有 ERROR 或 CRITICAL 級別的Django層次結構(django.server除外)中的消息發送到AdminEmailHandler。
獨立於DEBUG的值:
django.server logger 將 INFO 級別或更高級別的消息發送到控制台。
除 django.server 之外的所有記錄器都會將日志記錄傳播到其父級,直到根django記錄器。 console和mail_admins處理程序附加到根記錄器以提供上述行為。
五、源碼跟蹤
上面說了,logging 被配置成了 django.setup() 函數的一部分。這個方法在 get_wsgi_application() 方法中被調用,及執行 runserver 時在 utility.execute() 方法中被調用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def setup(set_prefix=True):
"""
Configure the settings (this happens as a side effect of accessing the
first setting), configure logging and populate the app registry.
Set the thread-local urlresolvers script prefix if `set_prefix` is True.
"""
from django.apps import apps
from django.conf import settings
from django.urls import set_script_prefix
from django.utils.log import configure_logging
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
if set_prefix:
set_script_prefix(
'/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME
)
apps.populate(settings.INSTALLED_APPS)
|
可以看到 configure_logging 傳進去了上面提到的 settings.LOGGING_CONFIG 和 settings.LOGGING 參數。這里注意一下,如果我們沒有在 settings 文件中定義這兩個參數,那么就會從默認 settings 中獲取,看一下默認 django/conf/global_settings.py 對這兩個參數的定義(Django會把settings文件與默認全局settings文件進行合並):
1
2
3
4
5
6
7
8
9
|
###########
# LOGGING #
###########
# The callable to use to configure logging
LOGGING_CONFIG = 'logging.config.dictConfig'
# Custom logging configuration.
LOGGING = {}
|
知道了這個簡單的因果關系,接下來看一下 configure_logging 方法主要干嘛了,代碼如下:
1
2
3
4
5
6
7
8
9
10
|
def configure_logging(logging_config, logging_settings):
if logging_config:
# First find the logging configuration function ...
logging_config_func = import_string(logging_config)
logging.config.dictConfig(DEFAULT_LOGGING)
# ... then invoke it with the logging settings
if logging_settings:
logging_config_func(logging_settings)
|
邏輯很簡單,如果 logging_config 為真就返回 dictConfig() 方法,並且使用 logging.conf 的 dictConfig() 方法,獲取默認日志配置信息。如果我們在 settings 中設置了 settings.LOGGING,那么就會應用用戶自定義設置。這也順便說明了我們沒有設置 LOGGING 時,日志輸出的來源。
看一下默認的 LOGGING 設置:
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
|
DEFAULT_LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'formatters': {
'django.server': {
'()': 'django.utils.log.ServerFormatter',
'format': '[{server_time}] {message}',
'style': '{',
}
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
},
'django.server': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'django.server',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
},
'django.server': {
'handlers': ['django.server'],
'level': 'INFO',
'propagate': False,
},
}
}
|
都配置完成后,就涉及到 logger 的調用了。調用處理邏輯在 WSGIRequestHandler 類中,這是創建 WSGIServer 實例時指定的 HTTP 請求類,其中 log_message 方法就是處理請求日志的,此方法是重寫了 simple_server.WSGIRequestHandler(http.server.BaseHTTPRequestHandler) 中的 log_message 方法。
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
|
# django/core/servers/basehttp.py
logger = logging.getLogger('django.server')
class WSGIRequestHandler(simple_server.WSGIRequestHandler):
protocol_version = 'HTTP/1.1'
def address_string(self):
# Short-circuit parent method to not call socket.getfqdn
return self.client_address[0]
def log_message(self, format, *args):
extra = {
'request': self.request,
'server_time': self.log_date_time_string(),
}
if args[1][0] == '4':
# 0x16 = Handshake, 0x03 = SSL 3.0 or TLS 1.x
if args[0].startswith('\x16\x03'):
extra['status_code'] = 500
logger.error(
"You're accessing the development server over HTTPS, but "
"it only supports HTTP.\n", extra=extra,
)
return
if args[1].isdigit() and len(args[1]) == 3:
status_code = int(args[1])
extra['status_code'] = status_code
if status_code >= 500:
level = logger.error
elif status_code >= 400:
level = logger.warning
else:
level = logger.info
else:
level = logger.info
level(format, *args, extra=extra)
|
然后日志在 WSGIServer 響應時處理,簡單的邏輯就是視圖處理完后,最終會調用 finish_response() 完成響應。而在 finish_response() 方法中返回 content 后會調用 close() 方法,那么 close() 方法又調用了 log_request() 請求日志處理方法,最終 log_request() 會調用 log_message() 方法進行日志處理。
1
2
3
4
5
6
7
8
9
10
11
|
class BaseHTTPRequestHandler():
def log_request(self, code='-', size='-'):
"""Log an accepted request.
This is called by send_response().
"""
if isinstance(code, HTTPStatus):
code = code.value
self.log_message('"%s" %s %s',
self.requestline, str(code), str(size))
|
如果是返回錯誤,那么就會調用 log_error() 方法,同樣調用 log_message() 方法。
日志處理只是 Django 從請求到響應中的一個環節,所以理解從請求到響應整個流程才更有利於明白日志處理這個環節到底是如何工作的,這里也說的不是很詳細,大致帶過。
原文鏈接:http://www.ywnds.com/?p=14990