公司Django项目的log之前都是写到项目下面的特定文件,然后通过url下载日志文件,再进行分析。现在是通过logging模块直接连接到阿里云日志服务,可以实时直观的访问日志信息,还可以进行各种查询分析。
一、Django中使用日志模块。
1)模块介绍:
主要包括以下四个组件:
Loggers 向应用程序提供log接口
Handlers 将log记录发送到指定的目的地(控制台输出,或写入文件或发向网络等等)
Filters 提供一个分级策略控制 log 输出等级
Formatters 指定输出的最终格式
记录器:
记录器负责管理日志消息的默认行为,包括日志记录级别、输出目标位置、消息格式以及其它基本细节。
关键字参数 | 描述 |
filename | 将日志消息附加到指定文件名的文件 |
filemode | 指定用于打开文件模式 |
format | 用于生成日志消息的格式字符串 |
datefmt | 用于输出日期和时间的格式字符串 |
level | 设置记录器的级别 |
stream | 提供打开的文件,用于把日志消息发送到文件。 |
每一个记录器都会有一个日志等级,每个等级描述了记录器即将处理的信息的严重性,python定义了以下五个等级:
debug:出于调试目的的低层次系统信息
info:普通的系统信息列表内容
warning:描述已经发生的小问题
error:描述已经发生的主要问题
critical:描述已经发生的严重问题
级别 | 值 | 描述 |
CRITICAL | 50 | 关键错误/消息 |
ERROR | 40 | 错误 |
WARNING | 30 | 警告消息 |
INFO | 20 | 通知消息 |
DEBUG | 10 | 调试 |
日志等级关系:CRITICAL>ERROR>WARNING>INFO>DEBUG
format日志消息格式
格式 | 描述 |
%(name)s | 关键错误/消息 |
%(levelno)s | 数字形式的日志记录级别 |
%(levelname)s | 日志记录级别的文本名称 |
%(filename)s | 执行日志记录调用的源文件的文件名称 |
%(pathname)s | 执行日志记录调用的源文件的路径名称 |
%(funcName)s | 执行日志记录调用的函数名称 |
%(module)s | 执行日志记录调用的模块名称 |
%(lineno)s | 执行日志记录调用的行号 |
%(created)s | 执行日志记录的时间 |
%(asctime)s | 日期和时间 |
%(thread)d | 线程ID |
%(msecs)s | 毫秒部分 |
%(threadName)s | 线程名称 |
%(process)d | 进程ID |
%(message)s | 记录的消息 |
详细信息可以参考Django2.0官方文档:日志 | Django 文档 | Django,目前关于logging部分是英文的,可以参考Django1.8.2官方文档:https://yiyibooks.cn/xx/django_182/topics/logging.html。
注意:Django1.8.2和2.0关于日志模块是有区别的,django2.0控制台默认不输出debug信息,详细设置下面会有说明。
2)Django项目中运用
在settings.py文件中定义logging格式。
# 日志配置
DIRNAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOGDIR = os.path.join(DIRNAME, "log")
if not os.path.exists(LOGDIR):
os.makedirs(LOGDIR) # 创建路径
LOGGING = {
'version': 1, # 指明dictConnfig的版本,目前就只有一个版本
'disable_existing_loggers': False, # 如果为True,默认的配置将被禁用,默认logger中的日志将会被丢弃且不传递到父logger,慎用True
'filters': { # 过滤器
},
'formatters': { # 格式器
'standard': {
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(levelname)s]- %(message)s'
},
'django.server': { # django2.0默认是没有控制台输出信息的,自定义输出规则
'()': 'django.utils.log.ServerFormatter',
'format': '[%(asctime)s] %(message)s',
}
},
'handlers': { # 处理器
'default': { # 所有高于(包括)debug的消息会被传到default
'level': 'DEBUG',
'formatter': 'standard', # 使用哪种formatters日志格式
'class': 'logging.handlers.RotatingFileHandler', # 如果输出到指定文件
'filename': os.path.join(LOGDIR, 'app.log'), # 输出文件(或者直接写路径:'c:\logs\all.log')
'mode': 'w+',
'maxBytes': 1024 * 1024 * 5, # 文件最大5 MB
'backupCount': 5, # 备份数量
},
'console': { # 流处理器,所有的高于(包括)debug的消息会被传到stderr,使用的是simple格式器
'level': 'INFO',
'formatter': 'standard',
'class': 'logging.StreamHandler',
},
'django.server': { # Django2.0特有处理器,输出高于info级别日志到控制台,如果不设置,控制台不会输出请求信息
'level': 'INFO',
'formatter': 'django.server',
'class': 'logging.StreamHandler',
},
},
'loggers': { # 定义了三个记录器 ***注意:loggers的level的级别一定要大于handlers的级别,否则handlers会忽略掉该信息的。
'django': { # 使用default处理器,所有高于(包括)info的消息会被发往default处理器,不向父层次传递信息
'handlers': ['default'],
'level': 'ERROR',
'propagate': False # 是否向父层次传递信息
},
'django.server': {
'handlers': ['django.server'],
'level': 'ERROR',
'propagate': False,
},
'django.request': {
'handlers': ['console'],
'level': 'ERROR',
'propagate': False
}
},
"root": {
'handlers': ['default', 'console'], # 生效的handlers,如果不写入此列表,即使上面定义了,也不会生效
'level': "INFO",
'propagate': False
}
}
视图函数中使用logging:
# coding:utf-8
import logging
from django.shortcuts import render
log = logging.getLogger(__name__)
def test(request):
try:
if request.method == "GET":
user = request.user
except:
log.exception("测试信息")
return render(request, 'test.html')
日志生成,在settings.py文件里面指定的地址中找到写入的日志文件:
二、Django日志结合阿里云日志进行实时分析
首先附上阿里云日志服务文档:日志服务 - 帮助中心 - 阿里云
按照文档提示创建Project,logstore,注意要给账户日志服务权限。
以下是django,settings部分代码。
# 日志配置
DIRNAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOGDIR = os.path.join(DIRNAME, "log")
if not os.path.exists(LOGDIR):
os.makedirs(LOGDIR) # 创建路径
LOGGING = {
'version': 1, # 指明dictConnfig的版本,目前就只有一个版本
'disable_existing_loggers': False, # 如果为True,默认的配置将被禁用,默认logger中的日志将会被丢弃且不传递到父logger,慎用True
'filters': { # 过滤器
'aliyun_filter': { # 阿里云日志专用
'()': 'yh_sls.SlsFilter',
},
},
'formatters': { # 格式器
'standard': {
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(levelname)s]- %(message)s'
},
'django.server': { # django2.0默认是没有控制台输出信息的,自定义输出规则
'()': 'django.utils.log.ServerFormatter',
'format': '[%(asctime)s] %(message)s',
}
},
'handlers': { # 处理器
'default': { # 所有高于(包括)debug的消息会被传到default
'level': 'DEBUG',
'formatter': 'standard', # 使用哪种formatters日志格式
'class': 'logging.handlers.RotatingFileHandler', # 如果输出到指定文件
'filename': os.path.join(LOGDIR, 'app.log'), # 输出文件(或者直接写路径:'c:\logs\all.log')
'mode': 'w+',
'maxBytes': 1024 * 1024 * 5, # 文件最大5 MB
'backupCount': 5, # 备份数量
},
'console': { # 流处理器,所有的高于(包括)debug的消息会被传到stderr,使用的是simple格式器
'level': 'INFO',
'formatter': 'standard',
'class': 'logging.StreamHandler',
},
'django.server': { # Django2.0特有处理器,输出高于info级别日志到控制台
'level': 'INFO',
'formatter': 'django.server',
'class': 'logging.StreamHandler',
},
'aliyun_handler': {
'level': 'INFO',
'filters': ['aliyun_filter'],
'class': 'aliyun.log.QueuedLogHandler', # 专有类,上传至阿里云
# 'class': 'aliyun.log.SimpleLogHandler', # 支持uwsgi,极端情况下测试使用,不推荐生成环境使用
'access_key_id': ALI_SLS_ACCESSID, # access_key_id
'access_key': ALI_SLS_ACCESSKEY, # access_key
'end_point': ALI_SLS_ENDPOINT, # end_point
'project': ALI_SLS_PROJECT, # project
'log_store': ALI_SLS_STORE, # log_store
'topic': ALI_SLS_TOPIC, # log_store
'extract_json': True,
'extract_json_drop_message': True
},
},
'loggers': { # 定义了三个记录器 ***注意:loggers的level的级别一定要大于handlers的级别,否则handlers会忽略掉该信息的。
'django': { # 使用default处理器,所有高于(包括)info的消息会被发往default处理器,不向父层次传递信息
'handlers': ['default', 'aliyun_filter'],
'level': 'ERROR',
'propagate': False # 是否向父层次传递信息
},
'django.server': {
'handlers': ['django.server', 'aliyun_filter'],
'level': 'ERROR',
'propagate': False,
},
'django.request': {
'handlers': ['console', 'aliyun_filter'],
'level': 'ERROR',
'propagate': False
}
},
"root": {
'handlers': ['default', 'console', 'aliyun_filter'],
'level': "INFO",
'propagate': False
}
}
以下是自定义封装filter,yh_sls.SlsFilter
# coding: utf-8
import logging
import sys
log = logging.getLogger(__name__)
class SlsFilter(logging.Filter):
"""
功能说明:自定义logging.Filter,增加request部分信息到日志字段,阿里云日志专用,示例如下:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'aliyun_filter': { # 必须
'()': 'yh_sls.SlsFilter',
},
},
'handlers': {
'aliyun_handler': {
'level': 'INFO',
'filters': ['aliyun_filter'], # 必须
'class': 'aliyun.log.SimpleLogHandler',
'access_key_id': ALI_SLS_ACCESSID,
'access_key': ALI_SLS_ACCESSKEY,
'end_point': ALI_SLS_ENDPOINT,
'project': ALI_SLS_PROJECT,
'log_store': ALI_SLS_STORE,
'topic': ALI_SLS_TOPIC,
'extract_json': True, # 必须
'extract_json_drop_message': True # 可选
},
},
"root": {
'handlers': ['aliyun_handler'], # 必须
'level': "DEBUG",
'propagate': False
}
}
"""
def filter(self, record):
try:
extra = {}
# request信息
if hasattr(record, 'req'):
from django.http import HttpRequest
if isinstance(record.req, HttpRequest):
request = record.req
extra.update(get_request_env(request))
extra['asctime'] = record.asctime
if record.args:
extra['msg'] = record.msg % record.args
else:
extra['msg'] = record.msg
extra['exc_text'] = record.exc_text or ''
exc_text_lines = extra['exc_text'].split("\n")
extra['exc_text_last_line'] = exc_text_lines[-1] if exc_text_lines else ''
record.msg = extra
return True
except:
log.exception("")
def get_request_env(request):
"""获取django的request的部分信息"""
extra = {}
try:
extra['req_path'] = request.path
extra['req_method'] = request.method
extra['req_full_path'] = request.get_full_path()
if sys.version_info < (3, 5):
extra['req_body'] = request.body
else:
extra['req_body'] = request.body.decode()
if not hasattr(request, 'environ'):
request.environ = {}
extra['req_referer'] = request.environ.get('HTTP_REFERER', '')
# 客户端ip
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
if x_forwarded_for:
remote_ip = x_forwarded_for.split(",")[0]
else:
remote_ip = request.META.get('REMOTE_ADDR', '')
extra['req_client_ip'] = remote_ip
# 用户id
uid = ''
if hasattr(request, 'uid'):
uid = request.uid
elif hasattr(request, 'user'):
from django.db.models import Model
if isinstance(request.user, Model):
uid = request.user.id
extra['req_uid'] = uid
except:
pass
return extra
日志分析部分参考官方文档。