-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Problem
In multi-tenant mode, workers process messages from multiple tenants concurrently. When debugging issues in production (e.g., a report failing for a specific tenant), operators need to filter logs by tenant_id. Currently, the tenant ID is available in context (tmcore.GetTenantIDFromContext(ctx)) but is not automatically included in log output.
This means every log statement that needs tenant context requires manual effort:
logger.Log(ctx, log.LevelInfo, "processing report",
log.String("tenant_id", tenantID), // dev must remember this every time
log.String("report_id", reportID),
)Real-world impact
During the clotilde-dev stabilization (March 2026), we needed to trace which tenant was affected by specific errors in the reporter-worker. The logs showed:
ERROR client/client.go:413 tenant manager returned error {"status": 401, "body": "..."}
INFO logcompat/logger.go:43 sync complete: 1 known, 1 active, 0 discovered, 0 removed
DEBUG client/client.go:341 tenant config cache hit {"tenant_id": "org_01KJZQAR17MPVQNQEHC228VQKK", ...}
Some log lines had tenant_id (the client cache hit), most did not. In a production environment with dozens of tenants, correlating errors to specific tenants requires grep-chaining trace IDs — slow and error-prone.
What we want
Every log line emitted within a tenant context should automatically include tenant_id without requiring the developer to pass it manually.
Proposed Solutions
Solution A: Logger Decorator (Recommended)
Create a TenantAwareLogger that wraps the base logger and extracts tenant_id from context on every Log() call:
// commons/tenant-manager/log/tenant_logger.go
type TenantAwareLogger struct {
base log.Logger
}
func NewTenantAwareLogger(base log.Logger) *TenantAwareLogger {
return &TenantAwareLogger{base: base}
}
func (l *TenantAwareLogger) Log(ctx context.Context, level log.Level, msg string, fields ...log.Field) {
if tenantID := tmcore.GetTenantIDFromContext(ctx); tenantID != "" {
fields = append(fields, log.String("tenant_id", tenantID))
}
l.base.Log(ctx, level, msg, fields...)
}Pros:
- Zero change required in existing service code
- If applied in
logcompat.New(), all consumer/manager logs gettenant_idfor free - Simple implementation, easy to test
- No risk of breaking existing behavior
Cons:
- Adds a field even where it might not be needed (acceptable — tenant_id in a multi-tenant worker log is always useful)
Solution B: Context Field Extractors (More Extensible)
Register field extractor hooks that run on every log call:
// commons/log/context_fields.go
type ContextFieldExtractor func(ctx context.Context) []Field
var extractors []ContextFieldExtractor
func RegisterContextFieldExtractor(fn ContextFieldExtractor) {
extractors = append(extractors, fn)
}The tenant-manager package would register its extractor at init:
func init() {
log.RegisterContextFieldExtractor(func(ctx context.Context) []log.Field {
if id := tmcore.GetTenantIDFromContext(ctx); id != "" {
return []log.Field{log.String("tenant_id", id)}
}
return nil
})
}Pros:
- Extensible: any package can register extractors (request_id, trace_id, user_id)
- Decoupled: log package doesn't need to know about tenant-manager
- One mechanism for all contextual fields
Cons:
- Higher implementation effort
- Global mutable state (registry) requires care in tests
- May be over-engineering if only tenant_id is needed now
Recommendation
Start with Solution A — it solves the immediate problem with minimal effort and zero risk. If demand grows for other context fields (request_id, user_id), evolve to Solution B later.
The ideal injection point is logcompat.New() in commons/tenant-manager/logcompat/ — this is already used by the consumer, MongoDB manager, and RabbitMQ manager. Wrapping the logger there gives automatic coverage to all multi-tenant components.
Related
- feat: TLS support for multi-tenant connection managers (MongoDB, RabbitMQ, Redis, PostgreSQL) #358 — TLS support for multi-tenant connection managers
- feat: add AllowInsecureHTTP option to MultiTenantConsumer config #357 — AllowInsecureHTTP for consumer
- clotilde-dev reporter-worker debugging (March 2026)
Requested by @brunobls