Cloud Logging - ericfitz/tmi GitHub Wiki
Cloud Logging
TMI's logger (internal/slogging) has a provider-neutral extension point for shipping logs to a cloud service. No cloud log provider is currently implemented (the former OCI writer was removed). The server starts with no CloudWriter, so cloud logging is off; logs go to the console and rotating files as described in Monitoring-and-Health.
AWS deployments
AWS deployments do not use CloudLogWriter. The terraform/modules/logging/aws module (used by aws-public) creates a CloudWatch log group (/tmi/<name_prefix>, configurable retention) and a Fluent Bit DaemonSet with an IRSA role that collects pod logs and ships them to CloudWatch. The module outputs the name as log_group_name; aws-public re-exports it as cloudwatch_log_group. See Terraform-Deployment.
The CloudLogWriter interface
Defined in internal/slogging/cloud_writer.go:
type CloudLogWriter interface {
io.Writer
WriteLog(ctx context.Context, entry LogEntry) error // structured entry (preferred)
Flush(ctx context.Context) error
Close() error
Name() string
IsHealthy(ctx context.Context) bool
}
type LogEntry struct {
Timestamp time.Time
Level slog.Level
Message string
Attrs map[string]any
Source string // file:line if available
}
Implementations own their buffering, batching, and retry. NoopCloudWriter is a built-in no-op implementation for tests or when disabled.
Logger integration
LoggerConfig (in logger.go) has these optional cloud fields:
| Field | Meaning |
|---|---|
CloudWriter |
The provider; nil disables cloud logging |
CloudLogLevel |
Minimum level sent to the cloud (defaults to Level) |
CloudLogBufferSize |
Async buffer size (default 1000) |
CloudErrorThreshold |
Write-failure count that triggers a watchdog warning (default 5; config key logging.cloud_error_threshold, env TMI_LOG_CLOUD_ERROR_THRESHOLD) |
When a CloudWriter is set, the logger wraps its handler in a CloudLogHandler. Each record is written to the local handler first, then converted to a LogEntry and queued on a buffered channel drained by a background goroutine that calls WriteLog. If the buffer is full the entry is written synchronously. Write errors are recorded (LastError(), ErrorCount(), Logger.CloudLogErrors()) and never fail local logging.
Graceful shutdown
Logger.Close() stops the watchdog, then closes the CloudLogHandler, which drains the buffer, calls Flush (10-second timeout), and then Close on the writer.
Watchdog and health
When a writer is configured, a watchdog polls it periodically and logs (via the local logger):
- health transitions:
cloud log sink healthy/cloud log sink unhealthy(fromIsHealthy, 5-second timeout, panics treated as unhealthy); cloud log sink failing writesonce the failure count reachesCloudErrorThreshold, andcloud log sink writes recoveredafterwards. A threshold of 0 disables the failure alarm but keeps health transitions.
Adding a provider
- Implement
CloudLogWriterininternal/slogging(or a new package), including internal batching, retry, and a cheapIsHealthy. - In
cmd/server/main.go, where the logger is initialized (the code currently leavesCloudWriter/CloudLogLevelunset), construct your writer and setCloudWriter(and optionallyCloudLogLevel) on the logger config. - Add any provider configuration to
internal/configand document it in Configuration-Reference.