Skip to content

Commit 3fb5072

Browse files
sduenasjjmerchante
authored andcommitted
[core] Modify logs messages to be structured
Some messages were updated to follow the structured format. Signed-off-by: Santiago Dueñas <[email protected]>
1 parent ef3246a commit 3fb5072

File tree

8 files changed

+141
-66
lines changed

8 files changed

+141
-66
lines changed
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'.

src/grimoirelab/core/consumers/consumer.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from multiprocessing import Event as ProcessEvent
2828

2929
import redis
30+
import structlog
3031

3132
if typing.TYPE_CHECKING:
3233
from typing import Iterable
@@ -170,7 +171,9 @@ def fetch_new_entries(self) -> Iterable[Entry]:
170171
def recover_stream_entries(self, recover_idle_time: int = RECOVER_IDLE_TIME) -> Iterable[Entry]:
171172
"""Transfers ownership of pending entries idle for 'recover_idle_time'."""
172173

173-
self.logger.debug(f"Recovering events from '{self.stream_name}:{self.consumer_group}'")
174+
self.logger.debug(
175+
"recovering events", stream=self.stream_name, consumer_group=self.consumer_group
176+
)
174177

175178
while True:
176179
response = self.connection.xautoclaim(
@@ -199,6 +202,10 @@ def recover_stream_entries(self, recover_idle_time: int = RECOVER_IDLE_TIME) ->
199202
if self._stop_event.is_set():
200203
break
201204

205+
self.logger.debug(
206+
"events recovered", stream=self.stream_name, consumer_group=self.consumer_group
207+
)
208+
202209
def process_entries(self, entries: Iterable[Entry], recovery: bool = False):
203210
"""Process entries (implement this method in subclasses).
204211
@@ -222,12 +229,11 @@ def ack_entries(self, message_ids: list):
222229
def stop(self):
223230
"""Stop the consumer gracefully."""
224231

225-
self.logger.info(f"Stopping consumer '{self.consumer_name}'.")
226-
227232
self._stop_event.set()
233+
self.logger.info("consumer stopped", consumer=self.consumer_name)
228234

229235
def _create_logger(self):
230-
logger = logging.getLogger(name=f"{self.__class__.__name__}")
236+
logger = structlog.get_logger(self.__class__.__name__)
231237
logger.setLevel(self.logging_level)
232238
return logger
233239

src/grimoirelab/core/consumers/consumer_pool.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@
2727

2828
import redis
2929
from rq.connections import parse_connection
30+
import structlog
3031

3132
from .consumer import Consumer
3233

3334

3435
ConsumerData = namedtuple("ConsumerData", ["name", "pid", "process"])
3536

3637

38+
logger = structlog.get_logger(__name__)
39+
40+
3741
class ConsumerPool:
3842
"""Base class to create a pool of consumers.
3943
@@ -198,7 +202,7 @@ def cleanup_consumers(self):
198202
self._consumers.pop(name)
199203

200204
def _create_logger(self):
201-
logger = logging.getLogger(self.__class__.__name__)
205+
logger = structlog.get_logger(self.__class__.__name__)
202206
logger.setLevel(self.log_level)
203207
return logger
204208

@@ -265,5 +269,5 @@ def _run_consumer(
265269
try:
266270
consumer = consumer_class(connection=connection, *args, **kwargs)
267271
consumer.start(burst=burst)
268-
except Exception as e:
269-
logging.error(f"Consumer {consumer_class.__name__} failed: {e}")
272+
except Exception as exc:
273+
logger.error(f"Consumer {consumer_class.__name__} failed", err=exc)

src/grimoirelab/core/runner/commands/run.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import django_rq
3232
import opensearchpy
3333
import redis
34+
import structlog
3435

3536
from django.conf import settings
3637
from django.db import connections, OperationalError
@@ -44,6 +45,9 @@
4445
DEFAULT_MAX_RETRIES = 10
4546

4647

48+
logger = structlog.get_logger(__name__)
49+
50+
4751
@click.group()
4852
@click.pass_context
4953
def run(ctx: Context):
@@ -122,17 +126,17 @@ def periodic_maintain_tasks(interval):
122126
while True:
123127
try:
124128
maintain_tasks()
125-
logging.info("Maintenance tasks executed successfully.")
126-
except redis.exceptions.ConnectionError as e:
127-
logging.error(f"Redis connection error during maintenance tasks: {e}")
128-
except django.db.utils.OperationalError as e:
129-
logging.error(f"Database connection error during maintenance tasks: {e}")
129+
logger.info("Maintenance tasks executed successfully")
130+
except redis.exceptions.ConnectionError as exc:
131+
logger.error("Redis connection error during maintenance tasks", err=exc)
132+
except django.db.utils.OperationalError as exc:
133+
logger.error("Database connection error during maintenance tasks", err=exc)
130134
connections.close_all()
131-
except Exception as e:
132-
logging.error("Error during maintenance tasks: %s", e)
135+
except Exception as exc:
136+
logger.error("Unexpected error during maintenance tasks", err=exc)
133137
raise
134138
except KeyboardInterrupt:
135-
logging.info("Maintenance task interrupted. Exiting...")
139+
logger.info("Maintenance task interrupted. Exiting...")
136140
return
137141

138142
time.sleep(interval)
@@ -146,7 +150,7 @@ def _maintenance_process(maintenance_interval):
146150
)
147151
process.start()
148152

149-
logging.info("Started maintenance process with PID %s", process.pid)
153+
logger.info("Started maintenance process", pid=process.pid)
150154

151155
return process
152156

@@ -217,6 +221,10 @@ def _wait_opensearch_ready(
217221
) -> None:
218222
"""Wait for OpenSearch to be available before starting"""
219223

224+
# The 'opensearch' library writes logs with the exceptions while
225+
# connecting to the database. Disable them temporarily until
226+
# the service is up. We have to use logging library because structlog
227+
# doesn't allow to disable a logger dynamically.
220228
os_logger = logging.getLogger("opensearch")
221229
os_logger.disabled = True
222230

@@ -245,24 +253,26 @@ def _wait_opensearch_ready(
245253
# Index still not created, but OpenSearch is up
246254
break
247255
except opensearchpy.exceptions.AuthorizationException:
248-
logging.error("OpenSearch Authorization failed. Check your credentials.")
256+
logger.error("OpenSearch Authorization failed. Check your credentials.")
249257
exit(1)
250258
except (
251259
opensearchpy.exceptions.ConnectionError,
252260
opensearchpy.exceptions.TransportError,
253-
) as e:
254-
logging.warning(
255-
f"[{attempt + 1}/{DEFAULT_MAX_RETRIES}] OpenSearch connection not ready: {e}"
261+
) as exc:
262+
logger.warning(
263+
f"[{attempt + 1}/{DEFAULT_MAX_RETRIES}] OpenSearch connection not ready",
264+
err=exc,
256265
)
257266
_sleep_backoff(attempt)
258267

259268
else:
260-
logging.error("Failed to connect to OpenSearch.")
269+
logger.error("Failed to connect to OpenSearch.")
261270
exit(1)
262271

272+
# Enable back 'opensearch' library logs
263273
os_logger.disabled = False
264274

265-
logging.info("OpenSearch is ready.")
275+
logger.info("OpenSearch is ready.")
266276

267277

268278
def _wait_redis_ready():
@@ -273,16 +283,17 @@ def _wait_redis_ready():
273283
redis_conn = django_rq.get_connection()
274284
redis_conn.ping()
275285
break
276-
except redis.exceptions.ConnectionError as e:
277-
logging.warning(
278-
f"[{attempt + 1}/{DEFAULT_MAX_RETRIES}] Redis connection not ready: {e}"
286+
except redis.exceptions.ConnectionError as exc:
287+
logger.warning(
288+
f"[{attempt + 1}/{DEFAULT_MAX_RETRIES}] Redis connection not ready",
289+
err=exc,
279290
)
280291
_sleep_backoff(attempt)
281292
else:
282-
logging.error("Failed to connect to Redis server")
293+
logger.error("Failed to connect to Redis server")
283294
exit(1)
284295

285-
logging.info("Redis is ready")
296+
logger.info("Redis is ready")
286297

287298

288299
def _wait_database_ready():
@@ -296,17 +307,18 @@ def _wait_database_ready():
296307
pass # Just test the connection
297308
break
298309

299-
except OperationalError as e:
300-
logging.warning(
301-
f"[{attempt + 1}/{DEFAULT_MAX_RETRIES}] Database connection not ready: {e.__cause__}"
310+
except OperationalError as exc:
311+
logger.warning(
312+
f"[{attempt + 1}/{DEFAULT_MAX_RETRIES}] Database connection not ready",
313+
err=exc.__cause__,
302314
)
303315
_sleep_backoff(attempt)
304316
else:
305317
error_msg = "Failed to connect to the database after all retries"
306-
logging.error(error_msg)
318+
logger.error(error_msg)
307319
raise ConnectionError(error_msg)
308320

309-
logging.info("Database is ready.")
321+
logger.info("Database is ready.")
310322

311323
# Close all database connections to avoid timed out connections
312324
connections.close_all()

src/grimoirelab/core/scheduler/jobs.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
import typing
2323

2424
import rq.job
25+
import structlog
2526

2627
if typing.TYPE_CHECKING:
2728
from typing import Any
2829
from logging import LogRecord
2930
from rq.types import FunctionReferenceType
3031

3132

32-
logger = logging.getLogger(__name__)
33+
logger = structlog.get_logger(__name__)
3334

3435

3536
class GrimoireLabJob(rq.job.Job):
@@ -129,9 +130,9 @@ def _execute(self) -> Any:
129130
try:
130131
self._add_log_handler()
131132
return super()._execute()
132-
except Exception:
133-
logger.exception(f"Error running job {self.id}.")
134-
raise
133+
except Exception as ex:
134+
logger.error("job exception", job_id=self.id, exc_info=ex)
135+
raise ex
135136
finally:
136137
self._remove_log_handler()
137138

0 commit comments

Comments
 (0)