Skip to content

Commit ad89c28

Browse files
committed
Add logging to v2 APIs
We have added middleware logging for request/response to API calls via v2 APIs. The logging information includes the IP address, user (if authenticated), request details, and response details. Enabling or disabling this logging can be configured through setting.ENABLE_API_LOGGING.
1 parent 3c2527a commit ad89c28

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

promgen/middleware.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
caching system to set a key and then triggering the actual event from middleware
1818
"""
1919

20+
import json
2021
import logging
22+
import uuid
2123
from http import HTTPStatus
2224
from threading import local
2325

@@ -36,6 +38,12 @@
3638

3739

3840
class PromgenMiddleware:
41+
ACCESS_LOG_FIELD_LIMITS = {
42+
"body": 8192,
43+
"description": 512,
44+
"clause": 8192,
45+
}
46+
3947
def __init__(self, get_response):
4048
self.get_response = get_response
4149

@@ -53,8 +61,46 @@ def __call__(self, request):
5361
if request.user.is_authenticated:
5462
_user.value = request.user
5563

64+
# Log all requests to our v2 API endpoints
65+
if settings.ENABLE_API_LOGGING and request.path.startswith("/rest/v2/"):
66+
try:
67+
# Generate a trace ID for each request
68+
trace_id = str(uuid.uuid4())
69+
request.trace_id = trace_id
70+
# Log the IP address of the request
71+
ip_address = request.META.get("REMOTE_ADDR")
72+
logger.info(f"[Trace ID: {trace_id}] IP Address: {ip_address}")
73+
# Log the user if authenticated
74+
if request.user.is_authenticated:
75+
logger.info(f"[Trace ID: {trace_id}] User: {request.user.username}")
76+
# Log the request details
77+
logger.info(
78+
f"[Trace ID: {request.trace_id}] Request: {request.method} {request.get_full_path()} - body size: {len(request.body) if request.body else 0} bytes"
79+
)
80+
if request.body and request.headers["Content-Type"] == "application/json":
81+
# Only log first 512 characters of the request body to avoid flooding the logs
82+
logger.info(
83+
f"[Trace ID: {request.trace_id}] Request body: {self.truncate_json_fields(json.loads(request.body), self.ACCESS_LOG_FIELD_LIMITS)}"
84+
)
85+
except Exception as e:
86+
logger.exception(
87+
f"[Trace ID: {request.trace_id}] An error occurred when parsing request: {str(e)}"
88+
)
89+
5690
response = self.get_response(request)
5791

92+
# Log all responses to our v2 API endpoints
93+
if settings.ENABLE_API_LOGGING and request.path.startswith("/rest/v2/"):
94+
try:
95+
# Log the response details
96+
logger.info(
97+
f"[Trace ID: {request.trace_id}] Response status: {response.status_code} - content size: {len(response.content)} bytes"
98+
)
99+
except Exception as e:
100+
logger.exception(
101+
f"[Trace ID: {request.trace_id}] An error occurred when logging response: {str(e)}"
102+
)
103+
58104
triggers = {
59105
"Config": trigger_write_config.send,
60106
"Rules": trigger_write_rules.send,
@@ -67,6 +113,36 @@ def __call__(self, request):
67113
messages.warning(request, "Error queueing %s " % msg)
68114
return response
69115

116+
@staticmethod
117+
def truncate_json_fields(data, field_limits, default_limits=128):
118+
"""
119+
Recursively truncate fields in a JSON object based on a given field-length mapping.
120+
121+
Args:
122+
data (dict): The JSON object to process.
123+
field_limits (dict): A mapping of field names to their maximum lengths.
124+
default_limits (int): Default maximum length for fields not specified in field_limits.
125+
126+
Returns:
127+
dict: A new JSON object with truncated fields.
128+
"""
129+
truncated_data = {}
130+
for field, value in data.items():
131+
if isinstance(value, dict):
132+
# Recursively process nested dictionaries
133+
truncated_data[field] = PromgenMiddleware.truncate_json_fields(value, field_limits)
134+
elif field in field_limits and isinstance(value, str):
135+
truncated_data[field] = value[: field_limits[field]] + (
136+
"..." if len(value) > field_limits[field] else ""
137+
)
138+
elif isinstance(value, str):
139+
truncated_data[field] = value[:default_limits] + (
140+
"..." if len(value) > default_limits else ""
141+
)
142+
else:
143+
truncated_data[field] = value
144+
return truncated_data
145+
70146

71147
def get_current_user():
72148
return getattr(_user, "value", None)

promgen/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,5 @@
237237
"DESCRIPTION": "Non-GET APIs require authentication. "
238238
"Please login Promgen and access the Profile page to get a Token.",
239239
}
240+
241+
ENABLE_API_LOGGING = env.bool("ENABLE_API_LOGGING", default=False)

0 commit comments

Comments
 (0)