Django之优雅的使用logging

Django默认使用python内置的logging模块来记录日志,其内置的日志处理模块实现了常用的日志处理功能,帮助开发者快速的使用日志功能,让开发者专注于编写业务逻辑代码,本文介绍如何在Django中优雅的处理日志。

Django中使用python内置的logging模块来记录日志,因此,我们可以直接在Django中使用logging的日志功能,包括:

  • 日志输出到文件
  • 日志输出到流
  • 远程输出日志至tcp sockets
  • 远程输出日志到UDP sockets
  • 远程输出日志到邮件地址
  • 日志输出到syslog
  • 远程输出日志到Windows NT/2000/XP的事件日志
  • 日志输出到内存中的制定buffer
  • 通过”GET”或”POST”远程输出到HTTP服务器

为了更好的处理程序中出现的异常情况,需要记录日志并发送至统一的日志管理平台,目前标准的日志记录方案之一是file + filebeat + ELK,本文不对该方案做任何介绍,仅介绍django项目中如何记录日志。

创建并初始化项目

创建Django项目:django-admin startproject django_sample,目录结构如图
创建项目

进入项目目录,安装django依赖项:pip install django,如图
安装依赖

安装django app:python manage.py startapp log_sample_normal,如图
创建app

创建视图:打开log_sample_normal/views.py文件,写入如下代码

1
2
3
4
5
6
7
from django.http import JsonResponse
from django.views import View


class SampleLogEndPoint(View):
def get(self, request):
return JsonResponse({'status': 201, 'msg': 'success'})

为了访问上述视图,需要创建url到视图文件的路由,首先,创建log_sample_normal/urls.py文件,写入如下代码

1
2
3
4
5
6
7
from django.urls import path

from log_sample_normal.views import SampleLogEndPoint

urlpatterns = [
path('sample_log', SampleLogEndPoint.as_view())
]

上述代码创建了app内部sample_log到视图SampleLogEndPoint之间的映射,接下来,我们将urlpatterns注册到项目django_sample中。

打开django_sample/urls.py文件,添加如下代码:

1
2
3
4
5
6
7
8
9
10
from django.contrib import admin
from django.urls import path
# 新增该行代码
from django.urls import include

urlpatterns = [
path('admin/', admin.site.urls),
# 新增该行代码
path('log/', include('log_sample_normal.urls')),
]

目前为止,已经完成了项目的基础配置,接下来,启动django项目并尝试访问创建的视图:python manage.py runserver
启动项目

访问视图,结果如下
访问视图

日志的基本配置

django中默认的日志配置位于django/utils/log.py文件中,Django项目在启动时,调用该文件的configure_logging方法并传入django_sample.settings包中的LOGGING字段作为logging_settings参数,格式为dict。

因此,只需要在django_sample.setting包中配置LOGGING字段即可。为了避免django_sample/settings.py文件过大导致配置文件难以维护,我们将django_sample/settings.py文件改为django_sample/settings包,默认配置放入django.py文件,日志相关的配置放入logging.py文件,如下:
访问视图

创建django_sample/settings/logging.py文件并写入配置

1
2
3
4
5
6
7
8
9
10
11
12
13
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
}

django_sample/settings/django.pydjango_sample/settings/logging.py下的配置导入setting包的__init__.py文件
setting包配置

日志写入文件

logging中存在很多handler,可根据需要进行配置和调用,此处以写入固定大小的文件为例。

logs/sample_log.log文件中创建filehandler并创建sample_log的logger

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
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/sample_log.log',
'backupCount': 5,
'maxBytes': 128,
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
'loggers': {
'sample_log': {
'handlers': ['file'],
'level': 'INFO',
},
}
}

在视图SampleLogEndPoint中获取sample_log的logger实例并记录日志

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.http import JsonResponse
from django.views import View
# 增加该行
import logging
# 增加该行
logger = logging.getLogger('sample_log')


class SampleLogEndPoint(View):
def get(self, request):
# 增加该行
logger.info('access to SampleLogEndPoint')
return JsonResponse({'status': 201, 'msg': 'success'})

访问http://127.0.0.1:8000/log/sample_log触发日志,生成文件
日志秀文件

Django中内置的logger

Django中预定义了djangodjango.server两个日志记录器,所有django.*的日志记录器记录的日志将由django处理。

django.request ¶
该logger记录与处理HTTP请求相关的信息,包括状态码5xx4xx

1
2
3
4
5
6
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())

django.server ¶
处理runserver启动的服务器接收到的HTTP请求相关的日志信息,5XX为error信息、4xx为warn信息,其它的为info信息。如果想要保存访问日志到文件中,可在django_sample/settings/logging.py文件中创建如下日志配置

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
from django.utils.log import DEFAULT_LOGGING

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
# 覆盖django.server的format
'django.server': DEFAULT_LOGGING['formatters']['django.server']
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/sample_log.log',
'backupCount': 5,
'maxBytes': 128,
},
# 覆盖django.server的handler
'django.server': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/access.log',
'backupCount': 5,
'maxBytes': 1024 * 5,
'formatter': 'django.server',
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
'loggers': {
'sample_log': {
'handlers': ['file'],
'level': 'INFO',
},
# 覆盖默认django.server的logger
'django.server': {
'handlers': ['django.server'],
'level': 'INFO',
'propagate': False,
},
}
}

django.template ¶
记录与Django模版渲染相关的日志,由于模板很少被用到,因此不进行该logger的分析。

django.db.backends ¶
记录代码与数据库交互相关的日志,主要是执行的sql语句、查询参数及sql执行时间,但是不包括ORM框架初始化(e.g. SET TIMEZONE)或事务管理查询(e.g. BEGIN, COMMIT, and ROLLBACK),如果需要查看这些日志,需要在数据库打开查询日志。

该日志只有在Debug=True时,才会被打印,实现代码在django/db/backends/utils.py文件中

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
@contextmanager
def debug_sql(self, sql=None, params=None, use_last_executed_query=False, many=False):
start = time.monotonic()
try:
yield
finally:
stop = time.monotonic()
duration = stop - start
if use_last_executed_query:
sql = self.db.ops.last_executed_query(self.cursor, sql, params)
try:
times = len(params) if many else ''
except TypeError:
# params could be an iterator.
times = '?'
self.db.queries_log.append({
'sql': '%s times: %s' % (times, sql) if many else sql,
'time': '%.3f' % duration,
})
logger.debug(
'(%.3f) %s; args=%s',
duration,
sql,
params,
extra={'duration': duration, 'sql': sql, 'params': params},
)

django.security.* ¶
当发生应用安全相关的异常时,调用该日志记录器记录日志,这里不进行深入分析。

django.db.backends.schema ¶
migrations框架在进行原数据操作时,调用该日志记录器记录日志。

Django提供的更多日志配置

Django提供了AdminEmailHandler日志处理器,该日志处理将发送日志数据到站点管理员的邮箱,默认情况下,django.*下所有ERRORCRITICAL的日志都将调用该日志记录器记录日志。

更多日志相关文档可参考官方文档

owefsad wechat
进击的DevSecOps,持续分享SAST/IAST/RASP的技术原理及甲方落地实践。如果你对 SAST、IAST、RASP方向感兴趣,可以扫描下方二维码关注公众号,获得更及时的内容推送。
0%