From f501acf8393bf3ded20488d68c4de6dad4803170 Mon Sep 17 00:00:00 2001 From: Julian Lobbes Date: Thu, 1 Dec 2022 19:58:40 +0100 Subject: [PATCH] feat(logging): implement request logger --- config.py | 7 +++++++ docker-compose.yml | 3 ++- lumi2/__init__.py | 24 +++++++++++++++++++++++- lumi2/default_configuration.py | 2 ++ lumi2/logging.py | 15 +++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 lumi2/logging.py diff --git a/config.py b/config.py index 29cf1b3..65328bb 100644 --- a/config.py +++ b/config.py @@ -30,6 +30,13 @@ LDAP_USERS_OU = 'ou=users,dc=example,dc=com' # DN of the organizational unit beneath which groups are located. LDAP_GROUPS_OU = 'ou=groups,dc=example,dc=com' +# If specified, the HTTP access log is saved to this file. +# Make sure the directory for the log file exists and is writeable by lumi2. +#LOG_FILE_PATH = '/path/to/file.log' +# Maximum log file size in Bytes. When exceeded, the log gets rotated. +# Set to 0 to disable log file rotation (can eat up disk space!) +#LOG_FILE_MAX_SIZE = 32_000_000 + # Maximum size in Bytes for incoming requests, both for improved security and # to limit the size of uploaded user profile pictures. MAX_CONTENT_LENGTH = 8_000_000 diff --git a/docker-compose.yml b/docker-compose.yml index 3c74e80..56dd669 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,11 +8,13 @@ services: container_name: lumi2 command: flask --app /app/lumi2 --debug run --host 0.0.0.0 --port 80 volumes: + - ./config.py/:/app/config.py:ro - ./lumi2/__init__.py:/app/lumi2/__init__.py:ro - ./lumi2/auth.py:/app/lumi2/auth.py:ro - ./lumi2/default_configuration.py:/app/lumi2/default_configuration.py:ro - ./lumi2/exceptions.py:/app/lumi2/exceptions.py:ro - ./lumi2/ldap.py:/app/lumi2/ldap.py:ro + - ./lumi2/logging.py:/app/lumi2/logging.py:ro - ./lumi2/static/css:/app/lumi2/static/css:ro - ./lumi2/static/images/base:/app/lumi2/static/images/base:ro - ./lumi2/static/images/default:/app/lumi2/static/images/default:ro @@ -22,7 +24,6 @@ services: - ./lumi2/usermodel.py:/app/lumi2/usermodel.py:ro - ./lumi2/webapi.py:/app/lumi2/webapi.py:ro - ./tests/fakedata.py/:/app/tests/fakedata.py:ro - - ./config.py/:/app/config.py:ro environment: - LUMI_CONFIG=/app/config.py ports: diff --git a/lumi2/__init__.py b/lumi2/__init__.py index 4895651..1cb922b 100644 --- a/lumi2/__init__.py +++ b/lumi2/__init__.py @@ -1,4 +1,5 @@ -import os +import os, logging +from logging.handlers import RotatingFileHandler from flask import Flask from flask_restful import Api @@ -30,6 +31,27 @@ def create_app(test_config=None): except OSError: pass + # Set up logging + if not app.config['DEBUG']: + app.logger = logging.getLogger('lumi2') + app.logger.setLevel(logging.INFO) + + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + app.logger.addHandler(console_handler) + + if 'LOG_FILE_PATH' in app.config: + file_handler = RotatingFileHandler( + app.config['LOG_FILE_PATH'], + maxBytes=app.config['LOG_FILE_MAX_SIZE'], + backupCount=1, + ) + file_handler.setLevel(logging.INFO) + app.logger.addHandler(file_handler) + + from lumi2.logging import log_request + app.after_request(log_request) + from . import auth app.register_blueprint(auth.bp) diff --git a/lumi2/default_configuration.py b/lumi2/default_configuration.py index 864d553..ff7e6f8 100644 --- a/lumi2/default_configuration.py +++ b/lumi2/default_configuration.py @@ -19,4 +19,6 @@ LDAP_BASE_DN = 'dc=example,dc=com' LDAP_USERS_OU = 'ou=users,dc=example,dc=com' LDAP_GROUPS_OU = 'ou=groups,dc=example,dc=com' +LOG_FILE_MAX_SIZE = 0 + MAX_CONTENT_LENGTH = 8_000_000 diff --git a/lumi2/logging.py b/lumi2/logging.py new file mode 100644 index 0000000..4946222 --- /dev/null +++ b/lumi2/logging.py @@ -0,0 +1,15 @@ +"""Logging operations for lumi2.""" + +import logging as l +from time import strftime + +from flask import request + + +def log_request(response): + logger = l.getLogger('lumi2') + + timestamp = strftime('[%Y-%b-%d %H:%M]') + logger.info(f"{timestamp} {request.remote_addr} {request.method} {request.scheme} {request.full_path} {response.status_code}") + + return response