Usage Tracking
The dcc_backend_common.usage_tracking module provides a service for tracking and logging usage events compatible with OpenSearch functionality, with built-in user pseudonymization for privacy compliance.
Overview
The module provides:
UsageTrackingService: Main service class for tracking application usage events- User pseudonymization: One-way HMAC-based hashing of user IDs for privacy
- Structured logging: Events logged in a format compatible with OpenSearch
- Flexible event data: Support for custom key-value pairs in event logs
- Always emitted: Events go through the pinned
usagelogger, soLOG_LEVELnever silences them
Where the Events Go
UsageTrackingService writes to the usage logger (see Logger — Usage Events), not to your module logger. That logger is pinned to INFO in init_logger(), so business events survive even LOG_LEVEL=WARNING.
Fields are flat, snake_case, top-level keys, so OpenSearch can aggregate on them without nested paths. Filter on logger: "usage" and event: "app_event" to isolate them.
Example production output:
{
"timestamp": "2025-07-14T09:12:03.481920Z",
"logger": "usage",
"level": "info",
"event": "app_event",
"action": "document_processor.extract_text",
"pseudonym_id": "9f2c1b...",
"document_type": "pdf",
"page_count": 10
}Installation
The usage tracking module is part of the dcc-backend-common package:
uv add dcc-backend-commonQuick Start
from dcc_backend_common.usage_tracking import UsageTrackingService
# Initialize the service with an HMAC secret
usage_tracking = UsageTrackingService(hmac_secret="your-secret-key")
# Log a usage event
usage_tracking.log_event(
module="my_module",
func="process_data",
user_id="user123",
data_size=1024,
processing_time=0.5
)API Reference
UsageTrackingService
The main service class for tracking usage events.
Initialization
UsageTrackingService(hmac_secret: str)| Parameter | Type | Description |
|---|---|---|
hmac_secret | str | Secret key used for HMAC-based pseudonymization of user IDs |
Raises ValueError if hmac_secret is empty or the literal string "none" — an unset secret would make every pseudonym trivially reproducible, so the service refuses to start instead.
Example:
from dcc_backend_common.usage_tracking import UsageTrackingService
usage_tracking = UsageTrackingService(hmac_secret="your-hmac-secret-key")get_pseudonymized_user_id
Generates a consistent, one-way pseudonym for a given user ID using HMAC-SHA256.
get_pseudonymized_user_id(user_id: str | None) -> str| Parameter | Type | Description |
|---|---|---|
user_id | str | None | The user ID to pseudonymize. If None, returns pseudonym for "unknown" |
Returns: str - A hexadecimal HMAC-SHA256 hash of the user ID
Example:
pseudonym = usage_tracking.get_pseudonymized_user_id("user123")
# Returns: "a1b2c3d4e5f6..." (consistent hash)
# Unknown users
pseudonym = usage_tracking.get_pseudonymized_user_id(None)
# Returns: pseudonym for "unknown"Features:
- One-way: Cannot be reversed to obtain the original user ID
- Consistent: Same user ID always produces the same pseudonym
- Privacy-compliant: Suitable for GDPR and privacy regulations
log_event
Logs a usage event with structured data compatible with OpenSearch.
log_event(
module: str,
func: str,
user_id: str | None,
**kwargs: str | int | float | bool | None
) -> None| Parameter | Type | Description |
|---|---|---|
module | str | The module name where the event occurred |
func | str | The function name where the event occurred |
user_id | str | None | The user ID (will be pseudonymized automatically) |
**kwargs | str | int | float | bool | None | Additional event data as flat top-level key-value pairs |
Emits an app_event record with:
| Field | Value |
|---|---|
event | "app_event" |
action | "{module}.{func}" |
pseudonym_id | HMAC-SHA256 of the user ID |
| (your kwargs) | Merged in as top-level keys |
Scalars only
Keep **kwargs to str, int, float, bool or None. Nested dicts and lists defeat OpenSearch aggregation, which is the point of the flat shape. Never pass raw user content or unhashed user identifiers.
Example:
usage_tracking.log_event(
module="document_processor",
func="extract_text",
user_id="user123",
document_type="pdf",
page_count=10,
processing_time_ms=250.5,
success=True
)Configuration
HMAC Secret
The HMAC secret should be:
- Secure: Use a strong, randomly generated secret
- Consistent: Same secret across all application instances for consistent pseudonymization
- Stored securely: Use environment variables or secret management systems
The built-in AppConfig already exposes it as hmac_secret (from HMAC_SECRET), so wire the service from config rather than reading the environment again:
config = AppConfig.from_env()
usage_tracking = UsageTrackingService(hmac_secret=config.hmac_secret)Rotating the secret
Changing the secret changes every pseudonym, breaking continuity of per-user analysis across the rotation point.
Related Documentation
- Logger — the pinned
usagelogger these events use - Configuration — where
hmac_secretcomes from - LLM Agent — the other producer on the
usagelogger
Source Code
The full implementation is available on GitHub: dcc_backend_common/usage_tracking/usage_tracking.py
