Skip to content

Commit 3e052cc

Browse files
authored
Merges #60 Closes #60
2 parents 74b770a + 3fb5072 commit 3e052cc

File tree

15 files changed

+421
-113
lines changed

15 files changed

+421
-113
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ $ pip install -e .
8080
```
8181

8282
#### Poetry
83-
We use [poetry](https://python-poetry.org/) for dependency management and
83+
We use [poetry](https://python-poetry.org/) for dependency management and
8484
packaging. You can install it following its [documentation](https://python-poetry.org/docs/#installation).
8585
Once you have installed it, you can install grimoirelab-core and the dependencies
8686
in a project isolated environment using:
@@ -134,7 +134,7 @@ Commands:
134134

135135
## Configuration
136136

137-
The first step is to run a Redis server, a MySQL database and an OpenSearch
137+
The first step is to run a Redis server, a MySQL database and an OpenSearch
138138
container that will be used for communicating components and storing results.
139139
Please refer to their documentation to know how to install and run them.
140140

@@ -202,3 +202,6 @@ Valkey, and OpenSearch).
202202
```
203203
(.venv)$ pytest
204204
```
205+
206+
Set the environment variable `GRIMOIRELAB_TESTING_VERBOSE` to activate the
207+
verbose mode.

config/settings/testing.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,41 @@
1-
from grimoirelab.core.config.settings import * # noqa: F403,F401
2-
from grimoirelab.core.config.settings import INSTALLED_APPS, _RQ_DATABASE, RQ
3-
1+
import os
42
import warnings
53

4+
from grimoirelab.core.config.settings import * # noqa: F403,F401
5+
from grimoirelab.core.config.settings import (
6+
INSTALLED_APPS,
7+
_RQ_DATABASE,
8+
RQ,
9+
LOGGING,
10+
)
11+
612
import django_rq.queues
713

814
from fakeredis import FakeRedis, FakeStrictRedis
915

16+
1017
INSTALLED_APPS.append("tests")
1118

12-
LOGGING = {
13-
"version": 1,
14-
"disable_existing_loggers": True,
15-
"loggers": {
16-
"grimoirelab.core": {"level": "CRITICAL"},
17-
},
19+
GRIMOIRELAB_TESTING_VERBOSE = os.environ.get(
20+
"GRIMOIRELAB_TESTING_VERBOSE",
21+
"False",
22+
).lower() in ("true", "1")
23+
24+
# Logging configuration for testing
25+
#
26+
# By default, logging is silent and doesn't print messages to the console.
27+
# Set 'GRIMOIRELAB_TESTING_VERBOSE' to 'True' to print messages to the console.
28+
#
29+
LOGGING["handlers"]["testing"] = {
30+
"class": "logging.NullHandler",
31+
}
32+
33+
LOGGING["loggers"] = {
34+
"": {
35+
"handlers": ["default"] if GRIMOIRELAB_TESTING_VERBOSE else ["testing"],
36+
"level": "DEBUG",
37+
"propagate": True,
38+
}
1839
}
1940

2041
SQL_MODE = [

poetry.lock

Lines changed: 65 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ opensearch-py = "^3.0.0"
6161
djangorestframework-simplejwt = "^5.4.0"
6262
django-storages = {version = "^1.14.6", extras = ["google"]}
6363
drf-spectacular = "^0.28.0"
64+
django-structlog = "^9.0.1"
65+
structlog = "^25.2.0"
6466
grimoirelab-toolkit = {version = ">=1.2.0", allow-prereleases = true}
6567
perceval = {version = ">=1.3.4", allow-prereleases = true}
6668
sortinghat = {version = ">=1.11.0", allow-prereleases = true}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: struclog added for logging platform messages
3+
category: other
4+
author: Santiago Dueñas <[email protected]>
5+
issue: null
6+
notes: >
7+
Organizing log data in a structured format makes easier
8+
to read and analyze it. After adding 'structlog',
9+
log messages will always have a structured format.
10+
11+
The default mode prints the messages to the console
12+
in plain format, but it can also be configured to print
13+
them in JSON format by setting the environment variable
14+
'GRIMOIRELAB_LOGS_JSON' to 1 or 'true'.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# GrimoireLab logging setup.
4+
#
5+
# Logging is configured using 'structlog', 'django-structlog',
6+
# and the standard library 'logging' module.
7+
#
8+
# More info:
9+
#
10+
# https://django-structlog.readthedocs.io/en/latest/
11+
# https://docs.djangoproject.com/en/4.2/topics/logging/#configuring-logging
12+
#
13+
14+
import structlog
15+
16+
17+
# Default column styles for the console renderer.
18+
19+
styles = structlog.dev._ColorfulStyles
20+
21+
logger_name_formatter = structlog.dev.KeyValueColumnFormatter(
22+
key_style=None,
23+
value_style=styles.bright + styles.logger_name,
24+
reset_style=styles.reset,
25+
value_repr=str,
26+
prefix="[",
27+
postfix="]",
28+
)
29+
30+
console_columns = [
31+
structlog.dev.Column(
32+
"timestamp",
33+
structlog.dev.KeyValueColumnFormatter(
34+
key_style=None,
35+
value_style=styles.timestamp,
36+
reset_style=styles.reset,
37+
value_repr=str,
38+
),
39+
),
40+
structlog.dev.Column(
41+
"level",
42+
structlog.dev.LogLevelColumnFormatter(
43+
structlog.dev.ConsoleRenderer.get_default_level_styles(),
44+
reset_style=styles.reset,
45+
),
46+
),
47+
structlog.dev.Column("logger", logger_name_formatter),
48+
structlog.dev.Column("logger_name", logger_name_formatter),
49+
structlog.dev.Column(
50+
"event",
51+
structlog.dev.KeyValueColumnFormatter(
52+
key_style=None,
53+
value_style=styles.bright,
54+
reset_style=styles.reset,
55+
value_repr=str,
56+
),
57+
),
58+
# Default formatter for all keys not explicitly mentioned.
59+
structlog.dev.Column(
60+
"",
61+
structlog.dev.KeyValueColumnFormatter(
62+
key_style=styles.kv_key,
63+
value_style=styles.kv_value,
64+
reset_style=styles.reset,
65+
value_repr=str,
66+
),
67+
),
68+
]
69+
70+
71+
# Configuration of chain processors for logs not generated by structlog.
72+
# This will add default fields such the log level, timestamp, etc.
73+
74+
pre_chain_processors = [
75+
structlog.processors.TimeStamper(fmt="iso", utc=True),
76+
structlog.stdlib.add_log_level,
77+
structlog.stdlib.add_logger_name,
78+
structlog.stdlib.ExtraAdder(),
79+
]
80+
81+
82+
# Default logging configuration for GrimoireLab
83+
84+
_GRIMOIRELAB_LOGGING_CONFIG = {
85+
"version": 1,
86+
"disable_existing_loggers": False,
87+
"formatters": {
88+
"plain": {
89+
"()": structlog.stdlib.ProcessorFormatter,
90+
"processors": [
91+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
92+
structlog.dev.ConsoleRenderer(columns=console_columns),
93+
],
94+
"foreign_pre_chain": pre_chain_processors,
95+
},
96+
"json": {
97+
"()": structlog.stdlib.ProcessorFormatter,
98+
"processors": [
99+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
100+
structlog.processors.JSONRenderer(),
101+
],
102+
"foreign_pre_chain": pre_chain_processors,
103+
},
104+
"not_structured": {
105+
"format": "[%(asctime)s - %(name)s - %(levelname)s] - %(message)s",
106+
},
107+
},
108+
"handlers": {
109+
"default": {
110+
"class": "logging.StreamHandler",
111+
"formatter": "plain",
112+
},
113+
"json": {
114+
"class": "logging.StreamHandler",
115+
"formatter": "json",
116+
},
117+
},
118+
}
119+
120+
121+
def configure_grimoirelab_logging(
122+
json_mode: bool = False,
123+
debug: bool = False,
124+
) -> None:
125+
"""
126+
Set up the GrimoireLab logging settings.
127+
128+
:param json_mode: If True, use JSON format for logging.
129+
:param debug: If True, set logging level to DEBUG.
130+
"""
131+
structlog.configure(
132+
processors=[
133+
structlog.contextvars.merge_contextvars,
134+
structlog.stdlib.filter_by_level,
135+
structlog.stdlib.PositionalArgumentsFormatter(),
136+
structlog.processors.TimeStamper(fmt="iso", utc=True),
137+
structlog.stdlib.add_log_level,
138+
structlog.stdlib.add_logger_name,
139+
structlog.processors.StackInfoRenderer(),
140+
structlog.processors.format_exc_info,
141+
structlog.processors.UnicodeDecoder(),
142+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
143+
],
144+
logger_factory=structlog.stdlib.LoggerFactory(),
145+
wrapper_class=structlog.stdlib.BoundLogger,
146+
cache_logger_on_first_use=True,
147+
)
148+
149+
logging_settings = dict(_GRIMOIRELAB_LOGGING_CONFIG)
150+
logging_settings["loggers"] = {
151+
"": {
152+
"handlers": ["json"] if json_mode else ["default"],
153+
"level": "DEBUG" if debug else "INFO",
154+
},
155+
}
156+
157+
return logging_settings

0 commit comments

Comments
 (0)