Mirrowel commited on
Commit
1d838ea
·
1 Parent(s): aa25dd9

refactor(logging): separate detailed failure logs from main stream

Browse files

This refactoring separates comprehensive failure event logging into a dedicated file, preventing them from cluttering the main application logs.

- Establish a dedicated `failure_logger` for detailed, structured JSON logs written to `failures.log`. This logger does not propagate messages to the root logger.
- Modify `log_failure` to send complete failure context (including timestamp, error details, and request data) to the new `failure_logger`.
- Continue logging a concise summary of failures to the `rotator_library`'s main logger for high-level monitoring.
- Update the JSON formatter to directly handle pre-structured dictionary messages, simplifying log record creation.

Files changed (1) hide show
  1. src/rotator_library/failure_logger.py +30 -22
src/rotator_library/failure_logger.py CHANGED
@@ -2,57 +2,58 @@ import logging
2
  import json
3
  from logging.handlers import RotatingFileHandler
4
  import os
 
5
 
6
  def setup_failure_logger():
7
- """Sets up a dedicated JSON logger for failed API calls."""
8
  log_dir = "logs"
9
  if not os.path.exists(log_dir):
10
  os.makedirs(log_dir)
11
 
12
- # Use the same named logger as the rest of the library
13
- logger = logging.getLogger('rotator_library')
14
- logger.setLevel(logging.INFO) # Set to INFO to capture all levels
15
-
16
- # Prevent logs from propagating to the root logger
17
  logger.propagate = False
18
 
19
- # Use a rotating file handler to keep log files from growing too large
20
  handler = RotatingFileHandler(
21
  os.path.join(log_dir, 'failures.log'),
22
  maxBytes=5*1024*1024, # 5 MB
23
  backupCount=2
24
  )
25
 
26
- # Custom JSON formatter
27
  class JsonFormatter(logging.Formatter):
28
  def format(self, record):
29
- log_record = {
30
- "timestamp": self.formatTime(record, self.datefmt),
31
- "level": record.levelname,
32
- "message": record.getMessage()
33
- }
34
- return json.dumps(log_record)
35
 
36
  handler.setFormatter(JsonFormatter())
37
 
38
- # Add handler only if it hasn't been added before, and is not a NullHandler
39
- if not any(isinstance(h, RotatingFileHandler) for h in logger.handlers):
40
  logger.addHandler(handler)
41
 
42
  return logger
43
 
44
- # Initialize the logger for failures
45
  failure_logger = setup_failure_logger()
46
 
 
 
 
47
  def log_failure(api_key: str, model: str, attempt: int, error: Exception, request_data: dict):
48
- """Logs a structured message for a failed API call."""
49
-
50
- # Try to get the raw response from the exception if it exists
 
51
  raw_response = None
52
  if hasattr(error, 'response') and hasattr(error.response, 'text'):
53
  raw_response = error.response.text
54
 
55
- log_data = {
 
56
  "api_key_ending": api_key[-4:],
57
  "model": model,
58
  "attempt_number": attempt,
@@ -61,4 +62,11 @@ def log_failure(api_key: str, model: str, attempt: int, error: Exception, reques
61
  "raw_response": raw_response,
62
  "request_data": request_data,
63
  }
64
- failure_logger.error(log_data)
 
 
 
 
 
 
 
 
2
  import json
3
  from logging.handlers import RotatingFileHandler
4
  import os
5
+ from datetime import datetime
6
 
7
  def setup_failure_logger():
8
+ """Sets up a dedicated JSON logger for writing detailed failure logs to a file."""
9
  log_dir = "logs"
10
  if not os.path.exists(log_dir):
11
  os.makedirs(log_dir)
12
 
13
+ # Create a logger specifically for failures.
14
+ # This logger will NOT propagate to the root logger.
15
+ logger = logging.getLogger('failure_logger')
16
+ logger.setLevel(logging.INFO)
 
17
  logger.propagate = False
18
 
19
+ # Use a rotating file handler
20
  handler = RotatingFileHandler(
21
  os.path.join(log_dir, 'failures.log'),
22
  maxBytes=5*1024*1024, # 5 MB
23
  backupCount=2
24
  )
25
 
26
+ # Custom JSON formatter for structured logs
27
  class JsonFormatter(logging.Formatter):
28
  def format(self, record):
29
+ # The message is already a dict, so we just format it as a JSON string
30
+ return json.dumps(record.msg)
 
 
 
 
31
 
32
  handler.setFormatter(JsonFormatter())
33
 
34
+ # Add handler only if it hasn't been added before
35
+ if not logger.handlers:
36
  logger.addHandler(handler)
37
 
38
  return logger
39
 
40
+ # Initialize the dedicated logger for detailed failure logs
41
  failure_logger = setup_failure_logger()
42
 
43
+ # Get the main library logger for concise, propagated messages
44
+ main_lib_logger = logging.getLogger('rotator_library')
45
+
46
  def log_failure(api_key: str, model: str, attempt: int, error: Exception, request_data: dict):
47
+ """
48
+ Logs a detailed failure message to a file and a concise summary to the main logger.
49
+ """
50
+ # 1. Log the full, detailed error to the dedicated failures.log file
51
  raw_response = None
52
  if hasattr(error, 'response') and hasattr(error.response, 'text'):
53
  raw_response = error.response.text
54
 
55
+ detailed_log_data = {
56
+ "timestamp": datetime.utcnow().isoformat(),
57
  "api_key_ending": api_key[-4:],
58
  "model": model,
59
  "attempt_number": attempt,
 
62
  "raw_response": raw_response,
63
  "request_data": request_data,
64
  }
65
+ failure_logger.error(detailed_log_data)
66
+
67
+ # 2. Log a concise summary to the main library logger, which will propagate
68
+ summary_message = (
69
+ f"API call failed for model {model} with key ...{api_key[-4:]}. "
70
+ f"Error: {type(error).__name__}. See failures.log for details."
71
+ )
72
+ main_lib_logger.error(summary_message)