mirror of
https://github.com/alerta/alerta.git
synced 2025-01-24 09:19:40 +00:00
6a932465f8
* Fixes #1823 - Replace FLASK_ENV with FLASK_DEBUG
331 lines
12 KiB
Python
331 lines
12 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
from logging.config import dictConfig
|
|
|
|
import flask
|
|
import yaml
|
|
from flask import Flask, current_app, g, request
|
|
|
|
|
|
class Logger:
|
|
|
|
def __init__(self, app: Flask = None) -> None:
|
|
self.app = None
|
|
if app:
|
|
self.setup_logging(app)
|
|
|
|
def setup_logging(self, app: Flask) -> None:
|
|
|
|
def open_file(filename, mode='r'):
|
|
path = os.path.join(os.path.dirname(__file__), filename)
|
|
return open(path, mode)
|
|
|
|
log_config_file = os.path.expandvars(os.path.expanduser(app.config['LOG_CONFIG_FILE']))
|
|
log_level = 'DEBUG' if app.debug else app.config['LOG_LEVEL']
|
|
|
|
if os.path.exists(log_config_file):
|
|
with open_file(log_config_file) as f:
|
|
dictConfig(yaml.safe_load(f.read()))
|
|
else:
|
|
if app.config['LOG_FORMAT'] in ['default', 'simple', 'verbose', 'json', 'syslog']:
|
|
log_format = app.config['LOG_FORMAT']
|
|
custom_format = '' # not used
|
|
else:
|
|
log_format = 'custom'
|
|
custom_format = app.config['LOG_FORMAT']
|
|
|
|
if 'file' in app.config['LOG_HANDLERS']:
|
|
log_file = os.path.expandvars(os.path.expanduser(app.config['LOG_FILE']))
|
|
else:
|
|
log_file = '/dev/null'
|
|
|
|
dictConfig({
|
|
'version': 1,
|
|
'disable_existing_loggers': True,
|
|
'formatters': {
|
|
'default': {
|
|
'()': 'alerta.utils.logging.CustomFormatter'
|
|
},
|
|
'simple': {
|
|
'format': '%(levelname)s %(message)s'
|
|
},
|
|
'verbose': {
|
|
'format': '%(asctime)s - %(name)s[%(process)d]: %(levelname)s - %(message)s [in %(pathname)s:%(lineno)d]'
|
|
},
|
|
'json': {
|
|
'()': 'alerta.utils.logging.JSONFormatter'
|
|
},
|
|
'syslog': {
|
|
'()': 'alerta.utils.logging.SyslogFormatter',
|
|
'facility': app.config['LOG_FACILITY']
|
|
},
|
|
'custom': {
|
|
'format': custom_format
|
|
}
|
|
},
|
|
'filters': {
|
|
'requests': {
|
|
'()': 'alerta.utils.logging.RequestFilter',
|
|
'methods': app.config['LOG_METHODS']
|
|
},
|
|
'context': {
|
|
'()': 'alerta.utils.logging.ContextFilter',
|
|
}
|
|
},
|
|
'handlers': {
|
|
'console': {
|
|
'class': 'logging.StreamHandler',
|
|
'formatter': log_format,
|
|
'level': log_level,
|
|
'filters': ['context', 'requests'],
|
|
'stream': 'ext://sys.stdout'
|
|
},
|
|
'file': {
|
|
'class': 'logging.handlers.RotatingFileHandler',
|
|
'formatter': log_format,
|
|
'level': log_level,
|
|
'filters': ['context', 'requests'],
|
|
'filename': log_file,
|
|
'maxBytes': app.config['LOG_MAX_BYTES'],
|
|
'backupCount': app.config['LOG_BACKUP_COUNT']
|
|
},
|
|
'wsgi': {
|
|
'class': 'logging.StreamHandler',
|
|
'formatter': log_format,
|
|
'filters': ['context', 'requests'],
|
|
'stream': 'ext://flask.logging.wsgi_errors_stream'
|
|
}
|
|
},
|
|
'loggers': {
|
|
'alerta': {
|
|
'level': log_level,
|
|
},
|
|
'flask_cors.core': {
|
|
'level': 'WARNING',
|
|
},
|
|
'mohawk': {
|
|
'level': log_level,
|
|
},
|
|
'requests': {
|
|
'level': log_level,
|
|
},
|
|
'urllib3': {
|
|
'level': log_level,
|
|
},
|
|
'werkzeug': {
|
|
'level': 'WARNING',
|
|
},
|
|
},
|
|
'root': {
|
|
'level': log_level,
|
|
'handlers': app.config['LOG_HANDLERS'],
|
|
},
|
|
})
|
|
|
|
# from logging_tree import printout
|
|
# printout()
|
|
|
|
app.after_request(self.log_response)
|
|
|
|
@staticmethod
|
|
def log_response(response):
|
|
request_protocol = request.environ['SERVER_PROTOCOL']
|
|
current_app.logger.info(f'"{request.method} {request.path} {request_protocol}" {response.status_code} {response.content_length}')
|
|
return response
|
|
|
|
|
|
class RequestFilter(logging.Filter):
|
|
|
|
def __init__(self, methods=None):
|
|
self.methods = methods or []
|
|
super().__init__()
|
|
|
|
def filter(self, record):
|
|
|
|
if hasattr(record, 'method'):
|
|
if record.method == '-' or record.method in self.methods:
|
|
return True
|
|
else:
|
|
return True
|
|
|
|
|
|
class ContextFilter(logging.Filter):
|
|
|
|
def filter(self, record):
|
|
|
|
if flask.has_request_context():
|
|
record.request_id = g.request_id if hasattr(g, 'request_id') else '-'
|
|
record.endpoint = request.endpoint
|
|
record.method = request.method
|
|
record.url = request.url
|
|
record.reqargs = request.args
|
|
record.data = request.get_data(as_text=True)
|
|
record.remote_addr = request.remote_addr
|
|
record.user = g.login if hasattr(g, 'login') else None
|
|
else:
|
|
record.request_id = '-'
|
|
record.endpoint = '-'
|
|
record.method = '-'
|
|
record.url = '-'
|
|
record.reqargs = '-'
|
|
record.data = '-'
|
|
record.remote_addr = '-'
|
|
record.user = '-'
|
|
|
|
return True
|
|
|
|
|
|
class CustomFormatter(logging.Formatter):
|
|
|
|
def __init__(self):
|
|
|
|
self.formatters = {
|
|
'alerta': '%(asctime)s %(name)s[%(process)d]: [%(levelname)s] %(message)s [in %(pathname)s:%(lineno)d]',
|
|
'flask': '%(asctime)s %(name)s[%(process)d]: [%(levelname)s] %(message)s',
|
|
'request': '%(asctime)s %(name)s[%(process)d]: [%(levelname)s] %(message)s request_id=%(request_id)s ip=%(remote_addr)s',
|
|
'urllib3': '%(asctime)s %(name)s[%(process)d]: [%(levelname)s] %(message)s',
|
|
'werkzeug': '%(asctime)s %(name)s[%(process)d]: %(message)s'
|
|
}
|
|
self.default_formatter = logging.BASIC_FORMAT
|
|
super().__init__()
|
|
|
|
def format(self, record):
|
|
|
|
fmt = record.name.split('.').pop(0)
|
|
if flask.has_request_context():
|
|
fmt = 'request'
|
|
|
|
formatter = logging.Formatter(self.formatters.get(fmt, self.default_formatter))
|
|
return formatter.format(record)
|
|
|
|
|
|
class JSONFormatter(logging.Formatter):
|
|
|
|
RECORD_ATTRS = [
|
|
'request_id', 'name', 'levelno', 'levelname', 'pathname', 'filename', 'module',
|
|
'lineno', 'funcName', 'created', 'thread', 'threadName', 'process', # 'message',
|
|
'endpoint', 'method', 'url', 'reqargs', 'data', 'remote_addr', 'user'
|
|
]
|
|
|
|
def format(self, record):
|
|
payload = {
|
|
attr: getattr(record, attr) for attr in self.RECORD_ATTRS if hasattr(record, attr)
|
|
}
|
|
payload['message'] = record.getMessage()
|
|
|
|
# do not assume there's a Flask request context here so must use FLASK_DEBUG env var not app.debug
|
|
indent = 2 if os.environ.get('FLASK_DEBUG', '') == '1' else None
|
|
return json.dumps(payload, indent=indent)
|
|
|
|
|
|
class SyslogFormatter(logging.Formatter):
|
|
|
|
def __init__(self, facility='local0'):
|
|
|
|
self.facility = facility
|
|
super().__init__()
|
|
|
|
def format(self, record):
|
|
|
|
import platform
|
|
|
|
# Numerical Facility
|
|
# Code
|
|
#
|
|
# 0 kernel messages
|
|
# 1 user-level messages
|
|
# 2 mail system
|
|
# 3 system daemons
|
|
# 4 security/authorization messages
|
|
# 5 messages generated internally by syslogd
|
|
# 6 line printer subsystem
|
|
# 7 network news subsystem
|
|
# 8 UUCP subsystem
|
|
# 9 clock daemon
|
|
# 10 security/authorization messages
|
|
# 11 FTP daemon
|
|
# 12 NTP subsystem
|
|
# 13 log audit
|
|
# 14 log alert
|
|
# 15 clock daemon (note 2)
|
|
# 16 local use 0 (local0)
|
|
# 17 local use 1 (local1)
|
|
# 18 local use 2 (local2)
|
|
# 19 local use 3 (local3)
|
|
# 20 local use 4 (local4)
|
|
# 21 local use 5 (local5)
|
|
# 22 local use 6 (local6)
|
|
# 23 local use 7 (local7)
|
|
|
|
syslog_facility = {
|
|
'kern': 0,
|
|
'user': 1,
|
|
'mail': 2,
|
|
'daemon': 3,
|
|
'auth': 4,
|
|
'syslog': 5,
|
|
'lpr': 6,
|
|
'news': 7,
|
|
'uucp': 8,
|
|
'cron': 9,
|
|
'authpriv': 10,
|
|
'ftp': 11,
|
|
'ntp': 12,
|
|
'security': 13,
|
|
'audit': 13,
|
|
'console': 14,
|
|
'alert': 14,
|
|
'clock': 15,
|
|
'local0': 16,
|
|
'local1': 17,
|
|
'local2': 18,
|
|
'local3': 19,
|
|
'local4': 20,
|
|
'local5': 21,
|
|
'local6': 22,
|
|
'local7': 23,
|
|
}
|
|
|
|
# 0 Emergency: system is unusable
|
|
# 1 Alert: action must be taken immediately
|
|
# 2 Critical: critical conditions
|
|
# 3 Error: error conditions
|
|
# 4 Warning: warning conditions
|
|
# 5 Notice: normal but significant condition
|
|
# 6 Informational: informational messages
|
|
# 7 Debug: debug-level messages
|
|
|
|
syslog_severity = {
|
|
'CRITICAL': {'severity': 'Critical', 'code': 2},
|
|
'FATAL': {'severity': 'Critical', 'code': 2},
|
|
'ERROR': {'severity': 'Error', 'code': 3},
|
|
'WARN': {'severity': 'Warning', 'code': 4},
|
|
'WARNING': {'severity': 'Warning', 'code': 4},
|
|
'INFO': {'severity': 'Informational', 'code': 6},
|
|
'DEBUG': {'severity': 'Debug', 'code': 7},
|
|
}
|
|
|
|
record.PRI = syslog_facility[self.facility] * 8 + syslog_severity[record.levelname]['code']
|
|
record.VERSION = '1'
|
|
record.HOSTNAME = platform.node()
|
|
record.APP_NAME = record.name
|
|
record.PROCID = record.process
|
|
record.MSGID = record.funcName
|
|
|
|
if flask.has_request_context():
|
|
record.ip = request.remote_addr
|
|
record.request_id = g.request_id if hasattr(g, 'request_id') else '-'
|
|
else:
|
|
record.ip = '-'
|
|
record.request_id = '-'
|
|
|
|
formatter = logging.Formatter(
|
|
fmt='<%(PRI)d>%(VERSION)s %(asctime)s %(HOSTNAME)s %(APP_NAME)s %(PROCID)s %(MSGID)s'
|
|
' [origin ip="%(ip)s"]'
|
|
' [request@52926 requestId="%(request_id)s"]'
|
|
' [file@52926 filename="%(pathname)s" lineno="%(lineno)d"]'
|
|
' %(levelname)s: %(msg)s',
|
|
datefmt='%Y-%m-%dT%H:%M:%SZ'
|
|
)
|
|
return formatter.format(record)
|