Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adopt Loggia logger utility #84

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/deploy/XX-logs-traces.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ FireFighter uses the [logging](https://docs.python.org/3/library/logging.html) m

#### Production JSON

FireFighter logs to stdout using a [JSON formatter][firefighter.logging.custom_json_formatter.CustomJsonFormatter].
FireFighter logs to stdout using [loggia](https://github.com/ManoManoTech/loggia) and its [JSON Formatter][loggia.stdlib_formatters.json_formatter.CustomJsonFormatter].

The JSON formats adheres to DataDog's [JSON log format](https://docs.datadoghq.com/logs/log_collection/python/?tab=standard#json-format).

#### Development

In development mode, FireFighter logs to the console using a [Pretty formatter][firefighter.logging.pretty_formatter.PrettyFormatter]
In development mode, FireFighter logs to the console using [loggia](https://github.com/ManoManoTech/loggia)'s [Pretty formatter][loggia.stdlib_formatters.pretty_formatter.PrettyFormatter]

### Further Configuration

You should check [loggia's documentation](https://manomanotech.github.io/loggia/latest/config/) for more details.

Loggia can mostly be configured using environment variables.

You can configure the logging manually by editing the `LOGGING` Python setting, or by running your configuration code in your custom settings.

## Tracing
Expand Down
2 changes: 0 additions & 2 deletions docs/reference/logging.md

This file was deleted.

2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ nav:
- slack: reference/slack.md
- jira_app: reference/jira_app.md
- raid: reference/raid.md
- logging: reference/logging.md


markdown_extensions:
Expand Down Expand Up @@ -203,6 +202,7 @@ plugins:
- url: https://docs.djangoproject.com/en/4.2/_objects/
base_url: https://docs.djangoproject.com/en/4.2/
domains: [std, py]
- https://manomanotech.github.io/loggia/latest/objects.inv
paths: [src] # search packages in the src folder
options:
docstring_options:
Expand Down
15 changes: 14 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies = [
"aiohttp>=3.9.1", # Transitive dependency of slack_sdk
"nh3>=0.2.15",
"django-import-export>=4.0.0",
"loggia>=0.3.0",
]
requires-python = ">=3.11,<4.0"
license = {file = "LICENSE"}
Expand Down
121 changes: 45 additions & 76 deletions src/firefighter/firefighter/settings/components/logging.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from __future__ import annotations

from logging import Filter, Formatter, LogRecord
from typing import Any
from logging import Filter, LogRecord
from typing import TYPE_CHECKING

from firefighter.firefighter.settings.settings_utils import ENV, config
from firefighter.logging.custom_json_formatter import (
CustomJsonEncoder,
CustomJsonFormatter,
)
from firefighter.logging.pretty_formatter import PrettyFormatter
from loggia.conf import FlexibleFlag, LoggerConfiguration
from loggia.logger import initialize
from loggia.utils.logrecordutils import STANDARD_FIELDS, popattr

from firefighter.firefighter.settings.settings_utils import ENV

if TYPE_CHECKING:
from collections.abc import Iterable


class AccessLogFilter(Filter):
Expand All @@ -32,74 +34,41 @@ def filter(self, record: LogRecord) -> bool:
return True


def get_json_formatter() -> dict[str, type[Formatter] | Any]:
attr_whitelist = {"name", "levelname", "pathname", "lineno", "funcName"}
attrs = [x for x in CustomJsonFormatter.RESERVED_ATTRS if x not in attr_whitelist]
return {
"()": CustomJsonFormatter,
"json_indent": None,
"json_encoder": CustomJsonEncoder,
"reserved_attrs": attrs,
"timestamp": True,
}


base_level = "DEBUG" if ENV == "dev" else "INFO"
base_level_override = config("LOG_LEVEL", cast=str, default="")
if base_level_override and base_level_override != "":
base_level = base_level_override.upper()
class RemoveExtraDev(Filter):
def __init__(self, name: str = "", to_ignore: Iterable[str] = ()) -> None:
self.to_ignore = to_ignore
super().__init__(name)

formatter: dict[str, type[Formatter] | Any]
formatter = {"()": PrettyFormatter} if ENV == "dev" else get_json_formatter()
def filter(self, record: LogRecord) -> bool:
for extra in set(record.__dict__.keys() - STANDARD_FIELDS) & set(
self.to_ignore
):
popattr(record, extra, None)
return True

FF_LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"dynamicfmt": formatter,
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "dynamicfmt"},
},
"loggers": {
"django": {
"handlers": ["console"],
"propagate": False,
},
"watchfiles.main": {"level": "INFO"},
"django.utils.autoreload": {"level": "INFO"},
"django.request": {"handlers": ["console"], "propagate": False},
"django.server": {"handlers": ["console"], "propagate": False},
"django.template": {"handlers": ["console"], "propagate": False},
"django.db.backends": {
"handlers": ["console"],
"propagate": False,
"level": base_level,
},
"django.db.backends.schema": {"handlers": ["console"], "propagate": False},
"gunicorn.access": {
"handlers": ["console"],
"filters": ["accessfilter"],
"propagate": False,
},
"gunicorn.error": {"handlers": ["console"], "propagate": False},
"ddtrace": {
"handlers": ["console"],
"level": "WARNING",
},
"faker.factory": {
"level": "INFO",
},
"fsevents": {
"level": "INFO",
},
},
"filters": {"accessfilter": {"()": AccessLogFilter}},
"root": {
"handlers": ["console"],
"level": base_level,
"propagate": False,
},
}

LOGGING = FF_LOGGING
# Explicitely tell Django that we don't want to use its default logging config
LOGGING_CONFIG: None = None
log_cfg = LoggerConfiguration()
log_cfg.capture_loguru = FlexibleFlag.DISABLED
# From https://docs.djangoproject.com/en/4.2/ref/logging/#django-s-default-logging-configuration
if ENV == "dev":
log_cfg.set_logger_level("django", "INFO")
log_cfg.set_logger_level("django.db.backends", "DEBUG")
log_cfg.set_logger_level("django.server", "INFO")
log_cfg.set_logger_level("ddtrace", "WARNING")
log_cfg.set_logger_level("faker.factory", "INFO")
log_cfg.set_logger_level("fsevents", "INFO")
log_cfg.set_logger_level("celery.utils.functional", "INFO")
log_cfg.set_logger_level("celery.bootsteps", "INFO")
log_cfg.set_logger_level("psycopg.pq", "INFO")
log_cfg.add_log_filter("gunicorn.access", AccessLogFilter())
log_cfg.add_log_filter(
"django.server",
RemoveExtraDev(to_ignore=("server_time", "status_code", "request")),
)
log_cfg.add_log_filter(
"django.db.backends",
RemoveExtraDev(to_ignore=("params", "alias", "sql", "duration")),
)
initialize(log_cfg)
1 change: 0 additions & 1 deletion src/firefighter/logging/__init__.py

This file was deleted.

159 changes: 0 additions & 159 deletions src/firefighter/logging/custom_json_formatter.py

This file was deleted.

Loading