feat: usedefault python logging configuration format

This commit is contained in:
Éloi Rivard 2024-03-15 19:55:12 +01:00
parent 4edffcaa9f
commit dc81832159
No known key found for this signature in database
GPG key ID: 7EDA204EA57DD184
13 changed files with 171 additions and 104 deletions

View file

@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_,
and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
Changed
*******
- Use default python logging configuration format. :issue:`188` :pr:`165`
[0.0.42] - 2023-12-29
=====================

View file

@ -1,5 +1,6 @@
import datetime
from logging.config import dictConfig
from logging.config import fileConfig
from flask import Flask
from flask import request
@ -25,20 +26,9 @@ def setup_sentry(app): # pragma: no cover
def setup_logging(app):
log_level = app.config.get("LOGGING", {}).get("LEVEL", "WARNING")
if not app.config.get("LOGGING", {}).get("PATH"):
handler = {
"class": "logging.StreamHandler",
"stream": "ext://flask.logging.wsgi_errors_stream",
"formatter": "default",
}
else:
handler = {
"class": "logging.handlers.WatchedFileHandler",
"filename": app.config["LOGGING"]["PATH"],
"formatter": "default",
}
conf = app.config.get("LOGGING")
if conf is None:
log_level = "DEBUG" if app.debug else "INFO"
dictConfig(
{
"version": 1,
@ -47,15 +37,28 @@ def setup_logging(app):
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
}
},
"handlers": {"wsgi": handler},
"handlers": {
"wsgi": {
"class": "logging.StreamHandler",
"stream": "ext://flask.logging.wsgi_errors_stream",
"formatter": "default",
}
},
"root": {"level": log_level, "handlers": ["wsgi"]},
"loggers": {
"faker": {"level": "WARNING"},
"mail.log": {"level": "WARNING"},
},
"disable_existing_loggers": False,
}
)
elif isinstance(conf, dict):
dictConfig(conf)
else:
fileConfig(conf, disable_existing_loggers=False)
def setup_jinja(app):
app.jinja_env.filters["len"] = len

View file

@ -67,15 +67,13 @@ SECRET_KEY = "change me before you go in production"
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
# [BACKENDS.SQL]
# The SQL database connection string

View file

@ -67,16 +67,13 @@ ENABLE_REGISTRATION = true
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
# [BACKENDS.SQL]
# The SQL database connection string

View file

@ -67,16 +67,13 @@ ENABLE_REGISTRATION = true
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
# [BACKENDS.SQL]
# The SQL database connection string

View file

@ -67,16 +67,13 @@ ENABLE_REGISTRATION = true
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
[BACKENDS.SQL]
# The SQL database connection string

View file

@ -67,16 +67,13 @@ ENABLE_REGISTRATION = true
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
# [BACKENDS.SQL]
# The SQL database connection string

View file

@ -67,16 +67,13 @@ ENABLE_REGISTRATION = true
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
# [BACKENDS.SQL]
# The SQL database connection string

View file

@ -67,16 +67,13 @@ ENABLE_REGISTRATION = true
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800
[LOGGING]
# LEVEL can be one value among:
# DEBUG, INFO, WARNING, ERROR, CRITICAL
# Defaults to WARNING
# LEVEL = "WARNING"
LEVEL = "DEBUG"
# The path of the log file. If not set (the default) logs are
# written in the standard error output.
# PATH = ""
# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
# the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
# https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
# https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig
[BACKENDS.SQL]
# The SQL database connection string

View file

@ -45,12 +45,60 @@ def test_no_configuration():
assert "No configuration file found." in str(exc)
def test_logging_to_file(configuration, backend, tmp_path, smtpd, admin):
def test_file_log_config(configuration, backend, tmp_path, smtpd, admin):
assert len(smtpd.messages) == 0
log_path = os.path.join(tmp_path, "canaille.log")
log_path = os.path.join(tmp_path, "canaille-by-file.log")
file_content = LOGGING_CONF_FILE_CONTENT.format(log_path=log_path)
config_file_path = tmp_path / "logging.conf"
with open(config_file_path, "w") as fd:
fd.write(file_content)
logging_configuration = {**configuration, "LOGGING": config_file_path}
app = create_app(logging_configuration, backend=backend)
testclient = TestApp(app)
with testclient.session_transaction() as sess:
sess["user_id"] = [admin.id]
res = testclient.get("/admin/mail")
res.form["email"] = "test@test.com"
res = res.form.submit()
assert len(smtpd.messages) == 1
assert "Test email from" in smtpd.messages[0].get("Subject")
with open(log_path) as fd:
log_content = fd.read()
assert "Sending a mail to test@test.com: Test email from" in log_content
def test_dict_log_config(configuration, backend, tmp_path, smtpd, admin):
assert len(smtpd.messages) == 0
log_path = os.path.join(tmp_path, "canaille-by-dict.log")
logging_configuration = {
**configuration,
"LOGGING": {"LEVEL": "DEBUG", "PATH": log_path},
"LOGGING": {
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
}
},
"handlers": {
"wsgi": {
"class": "logging.handlers.WatchedFileHandler",
"filename": log_path,
"formatter": "default",
}
},
"root": {"level": "DEBUG", "handlers": ["wsgi"]},
"loggers": {
"faker": {"level": "WARNING"},
},
"disable_existing_loggers": False,
},
}
app = create_app(logging_configuration, backend=backend)
@ -69,3 +117,27 @@ def test_logging_to_file(configuration, backend, tmp_path, smtpd, admin):
log_content = fd.read()
assert "Sending a mail to test@test.com: Test email from" in log_content
LOGGING_CONF_FILE_CONTENT = """
[loggers]
keys=root
[handlers]
keys=wsgi
[formatters]
keys=default
[logger_root]
level=DEBUG
handlers=wsgi
[handler_wsgi]
class=logging.handlers.WatchedFileHandler
args=('{log_path}',)
formatter=default
[formatter_default]
format=[%(asctime)s] %(levelname)s in %(module)s: %(message)s
"""

View file

@ -207,7 +207,9 @@ def test_first_login_form_error(testclient, backend, smtpd):
res = testclient.get("/firstlogin/temp", status=200)
res.form["csrf_token"] = "invalid"
res = res.form.submit(name="action", value="sendmail", status=400)
res = res.form.submit(
name="action", value="sendmail", status=400, expect_errors=True
)
assert len(smtpd.messages) == 0
u.delete()

View file

@ -289,7 +289,12 @@ def test_password_reset_email_failed(SMTP, smtpd, testclient, backend, logged_ad
def test_admin_bad_request(testclient, logged_admin):
testclient.post("/profile/admin/settings", {"action": "foobar"}, status=400)
res = testclient.get("/profile/admin/settings")
testclient.post(
"/profile/admin/settings",
{"action": "foobar", "csrf_token": res.form["csrf_token"].value},
status=400,
)
testclient.get("/profile/foobar/settings", status=404)

View file

@ -379,7 +379,7 @@ def test_no_jwt_bad_csrf(testclient, backend, logged_user, client):
form = res.form
form["csrf_token"] = "foobar"
res = form.submit(name="answer", value="logout", status=400)
res = form.submit(name="answer", value="logout", status=400, expect_errors=True)
def test_end_session_already_disconnected(testclient, backend, user, client, id_token):