django的日志发往http server
配置示例:
# https://docs.djangoproject.com/zh-hans/2.1/topics/logging/ LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { # 格式器 'verbose': { # 后缀d表示数据格式是整数,s表示数据格式是字符串 'format': '[%(levelname)s] [%(asctime)s] [%(module)s] %(filename)s:%(lineno)d %(funcName)s ' '%(processName)s:[%(process)d] %(threadName)s:[%(thread)d] %(message)s' # 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', # 'style': '{', }, 'simple': { 'format': '[%(levelname)s] [%(asctime)s] %(message)s', # 'format': '[%(asctime)s] %(message)s', # 后缀d表示数据格式是整数,s表示数据格式是字符串 # 'format': '[%(levelname)s] [%(asctime)s] [%(module)s] %(filename)s:%(lineno)d %(funcName)s ' # '%(processName)s:[%(process)d] %(threadName)s:[%(thread)d] %(message)s', # 'style': '{', }, 'standard': { # 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s', 'format': '{asctime} [{levelname:6}] {name:30}: {message}', # 设置上面格式样式;{lineno:3}是行号,至少显示3个字符,少则补空格 # 这里style选择{,是指{asctime}这种形式。 # 如果选择%,则是%(asctime)s这种形式。 # 还有一种选择,是$,是$asctime或${asctime}这种形式。 'style': '{', # 设置时间格式 'datefmt': '%Y-%m-%d %H:%M:%S', }, 'operation': { 'format': '%(message)s' } }, # 'filters': { # # 'special': { # # '()': 'erebus.logging.SpecialFilter', # # 'foo': 'bar', # # }, # 'require_debug_true': { # '()': 'django.utils.log.RequireDebugTrue', # }, # }, # Handler是决定如何处理logger中每一条消息的引擎。它描述特定的日志行为,比如把消息输出到屏幕、文件或网络socket。 # 和 logger 一样,handler 也有日志级别的概念。如果一条日志记录的级别不匹配或者低于 handler 的日志级别, # 对应的消息会被 handler 忽略。 'handlers': { # 处理器 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filename': 'logs/default.log', 'maxBytes': 1024*1024*5*20, # 5*20 MB # 'maxBytes': 1024*5, # 5 KB # 保留7天的日志,没份5M,5份大概是一个小时的日志内容,主要是kafka日志 'backupCount': int(5*1*24*7/20), 'formatter': 'standard', }, 'kafka': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filename': 'logs/kafka.log', 'maxBytes': 1024*1024*5*20, # 5*20 MB # 'maxBytes': 1024*5, # 5 KB # 保留7天的日志,没份5M,5份大概是一个小时的日志内容,主要是kafka日志 'backupCount': int(5*1*24*7/20), 'formatter': 'standard', }, 'output_to_server': { # 输出到http server,参数来自类HTTPHandler初始化函数里的参数 'level': 'WARNING', # 忽略debug/info信息 'class': 'logging.handlers.HTTPHandler', 'host': '127.0.0.1:8088', 'url': '/api/v1/log', # 使用GET方法遇到url最大长度限制 'method': 'POST', 'formatter': 'verbose', }, 'django': { 'level': 'INFO', # 忽略debug信息 'class': 'logging.FileHandler', 'filename': '{}/{}.log'.format(BASE_LOG_DIR, conf.get('log', 'name')), 'formatter': 'simple' if DEBUG else 'verbose', 'encoding': 'utf8', }, 'console': { 'level': 'DEBUG', # 所有的日志都会被输出到console # 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'operation': { 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': '{}/{}.log'.format(BASE_LOG_DIR, 'operation'), 'formatter': 'operation', 'encoding': 'utf8' }, 'test': { 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': '{}/{}.log'.format(BASE_LOG_DIR, 'test'), 'formatter': 'standard', 'encoding': 'utf8' } # 'mail_admins': { # 'level': 'ERROR', # 'class': 'django.utils.log.AdminEmailHandler', # # 'filters': ['special'] # } }, 'loggers': { # 记录器 # 可以通过使用空字符串:''来设置'catch all' logger # 在以下设置中,将所有日志事件级别为WARNING及以上的日志发送给日志服务器,但配置为'propagate': False日志事件除外, '': { 'handlers': ['default', 'output_to_server'], # 这样情况下的level设置是无效的,所有级别的信息都会传给handlers处理,由handlers的level界别决定 'level': 'ERROR', 'propagate': False }, # 记录所有kakfa相关的日志 'kafka': { 'handlers': ['kafka'], 'level': 'DEBUG', 'propagate': True }, # 这里必须使用名字django和django.request,目的是为了捕获django框架的日志内容 'django': { 'handlers': ['django', 'console'], # 当 logger 处理一条消息时,会将自己的日志级别和这条消息的日志级别做对比。 # 如果消息的日志级别匹配或者高于 logger 的日志级别,它就会被进一步处理。 # 否则这条消息就会被忽略掉。当 logger 确定了一条消息需要处理之后,会把它传给 Handler。 # 把INFO及以上级别的日志传给handlers,然后由handlers根据handlers的level进一步处理日志输出 'level': 'INFO', 'propagate': True, # 若值为False,表示日志不会传到上个层级,自然也不会传到default.log里 }, # 使用logger = logging.getLogger('django.request'), logger.info('info'), # 可以把日志输出到'handlers': ['django', 'console'], 'django.request': { # 即使和django的handlers一样,level也一样,也并不会产生2次相同的日志内容,应该是个并集。 'handlers': ['django', 'console'], 'level': 'DEBUG', # 会把日志向django.request的上层django传播 'propagate': True, }, # sql语句 'django.db.backends': { # 即使和django的handlers一样,level也一样,也并不会产生2次相同的日志内容,应该是个并集。 'handlers': ['django', 'console'], 'level': 'DEBUG', # 会把日志向django.request的上层django传播 'propagate': True, }, # 'erebus.custom': { # 'handlers': ['console', 'mail_admins'], # 'level': 'INFO', # # 'filters': ['special'] # }, # 名字随意起,用时,使用logger = logging.getLogger(conf.get('log', 'name'))获取,传到相应的loggers里就可以 'operation': { 'handlers': ['operation'], 'level': 'INFO', 'propagate': True, }, 'test': { 'handlers': ['console', 'test'], 'level': 'INFO', 'propagate': False, # 不要传给上一层级 } } }
查看使用的class HTTPHandler,注意参数对应
class HTTPHandler(logging.Handler): """ A class which sends records to a Web server, using either GET or POST semantics. """ def __init__(self, host, url, method="GET", secure=False, credentials=None, context=None): """ Initialize the instance with the host, the request URL, and the method ("GET" or "POST") """ logging.Handler.__init__(self) method = method.upper() if method not in ["GET", "POST"]: raise ValueError("method must be GET or POST") if not secure and context is not None: raise ValueError("context parameter only makes sense " "with secure=True") self.host = host self.url = url self.method = method self.secure = secure self.credentials = credentials self.context = context def mapLogRecord(self, record): """ Default implementation of mapping the log record into a dict that is sent as the CGI data. Overwrite in your class. Contributed by Franz Glasner. """ return record.__dict__ def emit(self, record): """ Emit a record. Send the record to the Web server as a percent-encoded dictionary """ try: import http.client, urllib.parse host = self.host if self.secure: h = http.client.HTTPSConnection(host, context=self.context) else: h = http.client.HTTPConnection(host) url = self.url data = urllib.parse.urlencode(self.mapLogRecord(record)) if self.method == "GET": if (url.find('?') >= 0): sep = '&' else: sep = '?' url = url + "%c%s" % (sep, data) h.putrequest(self.method, url) # support multiple hosts on one IP address... # need to strip optional :port from host, if present i = host.find(":") if i >= 0: host = host[:i] # See issue #30904: putrequest call above already adds this header # on Python 3.x. # h.putheader("Host", host) if self.method == "POST": h.putheader("Content-type", "application/x-www-form-urlencoded") h.putheader("Content-length", str(len(data))) if self.credentials: import base64 s = ('%s:%s' % self.credentials).encode('utf-8') s = 'Basic ' + base64.b64encode(s).strip().decode('ascii') h.putheader('Authorization', s) h.endheaders() if self.method == "POST": h.send(data.encode('utf-8')) h.getresponse() #can't do anything with the result except Exception: self.handleError(record)
参考:https://cloud.tencent.com/developer/ask/183866