Usage (Gunicorn)ΒΆ
Using seqlog with Gunicorn <https://gunicorn.org/> involves some additional configuration because of the way Gunicorn uses fork
to create new worker processes.
A custom JSONEncoder
is also used to handle objects that are not JSON serializable.
api/__init__.py
:
from flask import Flask
app = Flask(__name__)
api/gunicorn.py
:
import multiprocessing
from api.logging_provider import (
configure_server_logging,
configure_worker_logging,
get_log_config_dict)
bind = '0.0.0.0:8000'
workers = (2 * multiprocessing.cpu_count()) + 1
# Configure logging
logconfig_dict = get_log_config_dict()
def when_ready(server):
configure_server_logging(logconfig_dict)
def post_worker_init(worker):
configure_worker_logging(worker, logconfig_dict)
api/logging_provider.py
:
import logging
import seqlog
import yaml
from api import app
def configure_server_logging(log_config_dict):
seqlog.configure_from_dict(log_config_dict)
def configure_worker_logging(worker, log_config_dict):
seqlog.configure_from_dict(log_config_dict)
with app.app_context():
logger = logging.getLogger('gunicorn.error')
app.logger.handlers = logger.handlers
app.logger.propagate = False
app.logger.setLevel(logger.level)
configure_logger(worker.log.access_log)
configure_logger(worker.log.error_log)
def configure_logger(logger):
for handler in logger.handlers:
if handler.get_name() == 'seq':
try:
handler.consumer.stop()
except:
pass
try:
handler.consumer.start()
except:
pass
def get_log_config_dict():
with open('log_config.yml', 'r') as log_config_file:
log_config_dict = yaml.safe_load(log_config_file.read())
api/log_config.yml
:
version: 1
disable_existing_loggers: True
root:
level: DEBUG
handlers:
- console
- seq
loggers:
gunicorn.access:
qualname: gunicorn.access
propagate: False
level: DEBUG
handlers:
- console
- seq
gunicorn.error:
qualname: gunicorn.error
propagate: False
level: DEBUG
handlers:
- console
- seq
handlers:
console:
class: seqlog.structured_logging.ConsoleStructuredLogHandler
formatter: standard
seq:
class: seqlog.structured_logging.SeqLogHandler
formatter: seq
server_url: 'http://localhost:5341'
api_key: ''
batch_size: 1
auto_flush_timeout: 5
json_encoder_class: api.utilities.json_extensions.LoggingJSONEncoder
formatters:
seq:
style: '{'
standard:
format: '[%(asctime)s] [%(process)d] [%(levelname)s] %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
api/utilities/json_extensions.py
import json
class LoggingJSONEncoder(json.JSONEncoder):
def default(self, obj):
try:
return json.JSONEncoder.default(self, obj)
except:
return str(obj)