-
Notifications
You must be signed in to change notification settings - Fork 72
Home
DRF API Logger is a Django REST Framework observability package for capturing API request and response data, storing it safely, and using it to investigate production behavior.
Use this wiki as the operational guide. Keep the README short for installation and quick start; keep deeper production, performance, compliance, and troubleshooting notes here.
DRF API Logger can capture:
- API URL and path
- HTTP method
- Request headers
- Request body
- Response body
- Status code
- Client IP address
- Execution time
- Request timestamp
- Optional tracing ID
- Optional profiling data such as SQL time and query count
It supports two output modes:
- Database logging through
APILogsModel - Signal-based logging through
API_LOGGER_SIGNAL
Both modes can be enabled together when needed.
Install the package:
pip install drf-api-loggerAdd the app:
INSTALLED_APPS = [
# ...
"drf_api_logger",
]Add the middleware:
MIDDLEWARE = [
# ...
"drf_api_logger.middleware.api_logger_middleware.APILoggerMiddleware",
]Enable database logging:
DRF_API_LOGGER_DATABASE = TrueRun migrations:
python manage.py migrateAfter this, API requests are logged and can be viewed in Django Admin.
For production, start with this conservative configuration:
DRF_API_LOGGER_DATABASE = True
DRF_API_LOGGER_SIGNAL = False
DRF_LOGGER_QUEUE_MAX_SIZE = 100
DRF_LOGGER_INTERVAL = 5
DRF_API_LOGGER_MAX_REQUEST_BODY_SIZE = 32768
DRF_API_LOGGER_MAX_RESPONSE_BODY_SIZE = 65536
DRF_API_LOGGER_EXCLUDE_KEYS = [
"password",
"token",
"access",
"refresh",
"secret",
"api_key",
"client_secret",
]
DRF_API_LOGGER_SKIP_URL_NAME = ["health-check", "metrics"]
DRF_API_LOGGER_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"]
DRF_API_LOGGER_ENABLE_PROFILING = FalseFor high-traffic systems, keep profiling disabled by default and enable it temporarily or with sampling:
DRF_API_LOGGER_ENABLE_PROFILING = True
DRF_API_LOGGER_PROFILING_SAMPLE_RATE = 0.05| Setting | Default | Purpose |
|---|---|---|
DRF_API_LOGGER_DATABASE |
False |
Enables database-backed log storage. |
DRF_API_LOGGER_SIGNAL |
False |
Emits log records to custom signal listeners. |
DRF_LOGGER_QUEUE_MAX_SIZE |
50 |
Bulk insert batch size and queue wake-up threshold. Must be greater than 0. |
DRF_LOGGER_INTERVAL |
10 |
Background flush interval in seconds. Must be greater than 0. |
DRF_API_LOGGER_DEFAULT_DATABASE |
"default" |
Database alias used for storing log rows. |
DRF_API_LOGGER_SKIP_URL_NAME |
[] |
Skips logging for matching Django URL names. |
DRF_API_LOGGER_SKIP_NAMESPACE |
[] |
Skips logging for matching URL namespaces. |
DRF_API_LOGGER_METHODS |
[] |
Logs only listed HTTP methods. Empty means all methods. |
DRF_API_LOGGER_STATUS_CODES |
[] |
Logs only listed response codes. Empty means all status codes. |
DRF_API_LOGGER_EXCLUDE_KEYS |
built-in sensitive keys plus custom keys | Adds extra keys to mask recursively. |
DRF_API_LOGGER_MAX_REQUEST_BODY_SIZE |
32768 |
Maximum request body bytes to store. Use -1 for no limit. |
DRF_API_LOGGER_MAX_RESPONSE_BODY_SIZE |
65536 |
Maximum response body bytes to store. Use -1 for no limit. |
DRF_API_LOGGER_ENABLE_PROFILING |
False |
Enables per-request profiling data. |
DRF_API_LOGGER_PROFILING_SQL_TRACKING |
True |
Captures SQL query count and SQL time when profiling is enabled. |
DRF_API_LOGGER_PROFILING_SAMPLE_RATE |
1.0 |
Fraction of logged requests to profile. Clamped between 0.0 and 1.0. |
DRF_API_LOGGER_CONTENT_TYPES |
default set plus custom values | Adds loggable response/request content types. |
DRF_API_LOGGER_TIMEDELTA |
0 |
Admin display offset in minutes. Storage is unchanged. |
DRF_API_LOGGER_PATH_TYPE |
"ABSOLUTE" |
Controls stored URL format. Options: ABSOLUTE, FULL_PATH, RAW_URI. |
DRF_API_LOGGER_ENABLE_TRACING |
False |
Adds a tracing ID to each request. |
DRF_API_LOGGER_TRACING_FUNC |
unset | Dotted path to a custom trace ID generator. |
DRF_API_LOGGER_TRACING_ID_HEADER_NAME |
unset | Header name to read an existing trace ID from. |
DRF_API_LOGGER_CUSTOM_HANDLER |
unset | Dotted path to a function that transforms or drops log records before queueing. |
Database logging is the normal production path:
DRF_API_LOGGER_DATABASE = TrueRequest threads do not write logs directly to the database. They enqueue log records. A background daemon thread flushes records with bulk_create.
This keeps database writes out of the request path, but the application still pays for:
- Reading request body
- Reading response body when content type is loggable
- JSON decode/serialization for supported payloads
- Recursive sensitive-data masking
- Queue insertion
The database worker tracks queue and insert statistics through LOGGER_THREAD.get_status().
Example diagnostic use:
from drf_api_logger import apps as logger_apps
def api_logger_status():
if not logger_apps.LOGGER_THREAD:
return {"enabled": False}
return logger_apps.LOGGER_THREAD.get_status()Watch these fields in production:
queue_backloginserted_countdropped_countfailed_insert_countbatch_sizeinterval
Signal logging is useful when logs should go somewhere other than the package database table:
DRF_API_LOGGER_SIGNAL = TrueExample:
from drf_api_logger import API_LOGGER_SIGNAL
def send_to_observability_platform(**data):
# Send data to your logging or analytics system.
pass
API_LOGGER_SIGNAL.listen += send_to_observability_platformImportant production note: signal listeners run synchronously in the request/response flow. Keep signal listeners fast. Do not do slow network calls directly inside a listener unless you dispatch them to a queue.
Use selective logging to reduce cost, noise, and risk.
Skip internal endpoints:
DRF_API_LOGGER_SKIP_URL_NAME = ["health-check", "metrics"]
DRF_API_LOGGER_SKIP_NAMESPACE = ["admin", "internal"]Log only selected methods:
DRF_API_LOGGER_METHODS = ["POST", "PUT", "PATCH", "DELETE"]Log only selected status codes:
DRF_API_LOGGER_STATUS_CODES = [400, 401, 403, 404, 429, 500]Admin requests are automatically skipped. Static and media requests are also skipped.
By default, the middleware stores JSON and selected safe content types. Large payloads are replaced with a truncation marker instead of being stored.
Defaults:
DRF_API_LOGGER_MAX_REQUEST_BODY_SIZE = 32768
DRF_API_LOGGER_MAX_RESPONSE_BODY_SIZE = 65536Special response handling:
- Streaming responses are stored as
** Streaming ** - GZIP responses are stored as
** GZIP Archive ** - Binary responses are stored as
** Binary File ** - Calendar responses are stored as
** Calendar **
Add custom content types only when there is a clear reason:
DRF_API_LOGGER_CONTENT_TYPES = [
"application/json",
"application/vnd.api+json",
"application/xml",
"text/csv",
]Avoid unlimited payload capture in production unless the API is low-volume and the data is safe:
DRF_API_LOGGER_MAX_REQUEST_BODY_SIZE = -1
DRF_API_LOGGER_MAX_RESPONSE_BODY_SIZE = -1Sensitive fields are replaced with:
***FILTERED***
Built-in sensitive keys include:
passwordtokenaccessrefreshauthorizationproxy_authorizationcookieset_cookiex_api_keyapi_keysecretclient_secretprivate_keysessionidcsrfmiddlewaretoken
Matching is case-insensitive and treats hyphens and underscores equivalently.
Add project-specific sensitive keys:
DRF_API_LOGGER_EXCLUDE_KEYS = [
"ssn",
"credit_card",
"patient_id",
"customer_secret",
]Masking applies recursively to dictionaries and lists. Query parameters in stored API URLs are also masked when the parameter name matches a sensitive key.
Use a custom handler to transform, enrich, or drop records before database queueing:
DRF_API_LOGGER_CUSTOM_HANDLER = "myapp.logging.clean_api_log"Example:
def clean_api_log(data):
if data["api"].endswith("/health/"):
return None
data["headers"].pop("AUTHORIZATION", None)
return dataReturn None to intentionally drop a log entry.
This is the best place for project-specific compliance filtering.
Profiling adds timing breakdowns to logged requests:
DRF_API_LOGGER_ENABLE_PROFILING = True
DRF_API_LOGGER_PROFILING_SQL_TRACKING = True
DRF_API_LOGGER_PROFILING_SAMPLE_RATE = 1.0Captured profiling areas:
- Middleware work before the view
- View and serialization time
- Middleware work after the view
- SQL total time
- SQL query count
Use profiling to identify:
- N+1 query patterns
- Few but slow SQL queries
- Business logic or external API slowness
- Middleware overhead
Production guidance:
- Keep profiling disabled by default on very high traffic systems.
- Use
DRF_API_LOGGER_PROFILING_SAMPLE_RATEto profile a small fraction of requests. - Disable SQL tracking if the query instrumentation overhead is not acceptable:
DRF_API_LOGGER_PROFILING_SQL_TRACKING = FalseEnable tracing:
DRF_API_LOGGER_ENABLE_TRACING = TrueUse an existing inbound trace header:
DRF_API_LOGGER_TRACING_ID_HEADER_NAME = "X-Trace-ID"Or provide a custom generator:
DRF_API_LOGGER_TRACING_FUNC = "myapp.tracing.generate_trace_id"The middleware also attaches the trace ID to the request:
def view(request):
trace_id = getattr(request, "tracing_id", None)With database logging enabled, Django Admin can be used to:
- Search logs by API, body, response, and headers
- Filter by method, status code, date, and performance
- Inspect request and response data
- Review slow APIs
- Review profiling details when enabled
- Export or query log data through normal Django model access
Restrict admin access carefully. API logs may still contain personal or business-sensitive data even when masking is enabled.
The current design keeps database writes out of the request path, but production systems should still watch these areas.
Large request or response bodies increase CPU and memory pressure because the middleware may read, decode, serialize, and mask payloads.
Recommendation:
- Keep payload limits enabled.
- Avoid
-1limits in production. - Skip endpoints that return large files or large reports.
The background queue is intentionally unbounded so request threads do not block on logging. If the database is slow or unavailable, memory can grow as logs accumulate.
Recommendation:
- Alert on
queue_backlog. - Alert on
failed_insert_count. - Use a dedicated log database if needed.
- Keep retention cleanup in place.
API logs can grow quickly.
Recommendation:
- Add retention policy.
- Archive or delete old logs.
- Index high-use query fields.
- Keep log storage separate from transactional tables on high-volume systems.
Useful indexes:
CREATE INDEX idx_api_logs_added_on ON drf_api_logs(added_on);
CREATE INDEX idx_api_logs_api_method ON drf_api_logs(api, method);
CREATE INDEX idx_api_logs_status_code ON drf_api_logs(status_code);Signal listeners run synchronously.
Recommendation:
- Keep listeners lightweight.
- Push external calls to Celery, RQ, Kafka, Redis, or another queue.
- Avoid blocking network calls directly in listeners.
SQL profiling uses Django query collection. It is valuable for diagnosis but should be sampled on high-traffic systems.
Recommendation:
- Use profiling during investigations.
- Use sampling in production.
- Disable SQL tracking when the query-count detail is not needed.
DRF API Logger helps with compliance readiness, but it does not make an application compliant by itself.
Before production use in regulated environments:
- Identify whether request/response bodies contain PII, PHI, PCI, secrets, or customer confidential data.
- Add domain-specific sensitive fields to
DRF_API_LOGGER_EXCLUDE_KEYS. - Keep payload limits enabled.
- Use
DRF_API_LOGGER_CUSTOM_HANDLERto remove fields that should never be logged. - Restrict Django Admin access to log records.
- Store logs in an encrypted database or encrypted volume.
- Define retention and deletion rules.
- Document who can access logs and why.
- Avoid logging full card numbers, government IDs, medical records, or long-lived credentials.
- Confirm log storage region/data residency requirements.
- Include API logs in incident-response and breach-review processes.
Recommended compliance-oriented configuration:
DRF_API_LOGGER_DATABASE = True
DRF_API_LOGGER_SIGNAL = False
DRF_API_LOGGER_MAX_REQUEST_BODY_SIZE = 8192
DRF_API_LOGGER_MAX_RESPONSE_BODY_SIZE = 8192
DRF_API_LOGGER_EXCLUDE_KEYS = [
"password",
"token",
"authorization",
"cookie",
"api_key",
"secret",
"ssn",
"credit_card",
]
DRF_API_LOGGER_CUSTOM_HANDLER = "myapp.logging.compliance_filter"Check:
-
DRF_API_LOGGER_DATABASE = TrueorDRF_API_LOGGER_SIGNAL = True - Middleware is present in
MIDDLEWARE -
drf_api_loggeris present inINSTALLED_APPS - Migrations were run
- The request is not an admin/static/media request
- The URL name or namespace is not skipped
- The status code and method match configured filters
Run:
python manage.py migrateIf using a dedicated database, confirm the database router and DRF_API_LOGGER_DEFAULT_DATABASE point to the right alias.
Likely causes:
- Response content type is not in
DRF_API_LOGGER_CONTENT_TYPES - Response is streaming
- Response is binary/gzip/calendar
- Response body exceeded
DRF_API_LOGGER_MAX_RESPONSE_BODY_SIZE
Likely causes:
- Request body is not decodable as UTF-8
- Request content type is unsupported
- Request body exceeded
DRF_API_LOGGER_MAX_REQUEST_BODY_SIZE - Request body was unavailable from Django request internals
Likely cause: database logging queue is accumulating because inserts are failing or slow.
Check:
logger_apps.LOGGER_THREAD.get_status()Watch queue_backlog and failed_insert_count.
Check:
- Payload size limits
- Large response bodies
- Signal listeners doing slow work
- Profiling enabled at
1.0 - SQL tracking enabled under high traffic
- Custom handler doing expensive processing
For a new production rollout:
- Enable database logging in staging.
- Add sensitive fields to
DRF_API_LOGGER_EXCLUDE_KEYS. - Keep payload limits conservative.
- Skip health, metrics, and high-volume internal endpoints.
- Run a load test with realistic payloads.
- Watch API latency,
queue_backlog, insert failures, and database write volume. - Enable profiling with sampling only if needed.
- Add retention cleanup before going live.
- Review admin permissions for log access.
- Document the final logging policy.
- Repository: https://github.com/vishalanandl177/DRF-API-Logger
- Package: https://pypi.org/project/drf-api-logger/
- Django REST Framework: https://www.django-rest-framework.org/