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

原文地址:https://www.cnblogs.com/shengulong/p/10473260.html