package logging import ( "io" "os" "time" cloudlogging "cloud.google.com/go/logging" logpb "cloud.google.com/go/logging/apiv2/loggingpb" "github.com/go-logr/logr" "go.uber.org/zap/zapcore" kzap "sigs.k8s.io/controller-runtime/pkg/log/zap" ) // Severity maps to the levels understood by the Google Cloud Logging api // ref: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity type Severity int const ( DEFAULT = "DEFAULT" // The log entry has no assigned severity level. DEBUG = "DEBUG" // Debug or trace information. INFO = "INFO" // Routine information, such as ongoing status or performance. NOTICE = "NOTICE" // Normal but significant events, such as start up, shut down, or a configuration change. WARNING = "WARNING" // Warning events might cause problems. ERROR = "ERROR" // Error events are likely to cause problems. CRITICAL = "CRITICAL" // Critical events cause more severe problems or outages. ALERT = "ALERT" // A person must take an action immediately. EMERGENCY = "EMERGENCY" // One or more systems are unusable. ) type Operation struct { ID string `json:"id"` Producer string `json:"producer"` First bool `json:"first"` Last bool `json:"last"` } type SourceLocation struct { File string `json:"file"` Line string `json:"line"` Function string `json:"function"` } type HTTPRequest struct { File string `json:"file"` Line string `json:"line"` Function string `json:"function"` } type LogEntry struct { Severity string `json:"severity"` Message string `json:"message"` HTTPRequest HTTPRequest `json:"httpRequest"` Operation Operation `json:"logging.googleapis.com/operation"` SourceLocation SourceLocation `json:"logging.googleapis.com/sourceLocation"` } type LogEntry2 struct { Severity cloudlogging.Severity `json:"severity"` Message string `json:"message"` HTTPRequest cloudlogging.HTTPRequest `json:"httpRequest"` Operation logpb.LogEntryOperation `json:"logging.googleapis.com/operation"` SourceLocation logpb.LogEntrySourceLocation `json:"logging.googleapis.com/sourceLocation"` } const ( OperationKey = "logging.googleapis.com/operation" MessageKey = "message" SeverityKey = "severity" ) // OperationFields provides a zerolog style interface for the Operation field func OperationFields(opID string) map[string]interface{} { return map[string]interface{}{ "logging.googleapis.com/operation": map[string]interface{}{ "id": opID, }, } } // OperationFields provides a 'keysAndValues ...interface{}' compatible interface for the Operation field, // for use in fmt and logr.Logger style logging. e.g., logr.Logger.WithValues(OperationKeysAndValues(id)...) func OperationKeysAndValues(opID string) []interface{} { return []interface{}{ OperationKey, map[string]interface{}{ "id": opID, }, } } // HTTPRequestFields provides a zerolog style interface for the HTTPRequest field func HTTPRequestFields(method, url string, status, bytes int, elapsed time.Duration) map[string]interface{} { return map[string]interface{}{ "httpRequest": map[string]interface{}{ "requestMethod": method, "requestUrl": url, "status": status, "bytes": bytes, "elapsed": elapsed.Milliseconds(), }, } } // zapEncoderOpts provides options to configure a zap.Logger with the correct // JSON structure func zapEncoderOpts(dest io.Writer) kzap.Opts { return func(o *kzap.Options) { *o = kzap.Options{ EncoderConfigOptions: []kzap.EncoderConfigOption{ func(ec *zapcore.EncoderConfig) { ec.MessageKey = MessageKey }, func(ec *zapcore.EncoderConfig) { ec.LevelKey = SeverityKey }, }, DestWriter: dest, } } } // EdgeLogger TODO: logr logger suitable to use in controllers // TODO: provides structured log schema adherence type EdgeLogger struct { logr.Logger } // private options struct, can only be changed by consumers via the public // Option function interface type options struct { dest io.Writer } // Option is a functional interface used to define this package's public contract // with consumers type Option func(*options) // To allows setting the logging destination writer, defaults to `os.Stdout` if // not provided func To(dest io.Writer) Option { return func(o *options) { o.dest = dest } } func New(opts ...Option) logr.Logger { return NewLogger(opts...).Logger } func NewLogger(opts ...Option) *EdgeLogger { o := &options{dest: os.Stdout} for _, opt := range opts { opt(o) } zapOpts := zapEncoderOpts(o.dest) return &EdgeLogger{ Logger: kzap.New(zapOpts), } } // WithValues is a helper that wraps the WithValues method of the embedded logr.Logger, // allowing callers to preserve the EdgeLogger type when calling WithValues, which would // return a logr.Logger instead func (e EdgeLogger) WithValues(keysAndValues ...interface{}) *EdgeLogger { logrl := e.Logger.WithValues(keysAndValues...) return &EdgeLogger{logrl} }