logger/logger.go (176 lines of code) (raw):

// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package logger logs messages as appropriate. package logger import ( "context" "fmt" "io" "os" "regexp" "time" "cloud.google.com/go/compute/metadata" "cloud.google.com/go/logging" "google.golang.org/api/option" ) var ( // DeferredFatalFuncs is a slice of functions that will be called prior to os.Exit in Fatal. DeferredFatalFuncs []func() cloudLoggingClient *logging.Client cloudLogger *logging.Logger debugEnabled bool loggerName string formatFunction func(LogEntry) string writers []io.Writer ) const ( // The following are MIG labels. migNameLabel = `compute.googleapis.com/instance_group_manager/name` migZoneLabel = `compute.googleapis.com/instance_group_manager/zone` migRegionLabel = `compute.googleapis.com/instance_group_manager/region` ) // LogOpts represents options for logging. type LogOpts struct { Debug bool ProjectName string LoggerName string DisableLocalLogging bool DisableCloudLogging bool // FormatFunction will produce the string representation of each log event. FormatFunction func(LogEntry) string // Additional writers that will be used during logging. Writers []io.Writer UserAgent string // MIG is the Managed Instance Group, used for labeling logs. MIG string } // SetDebugLogging enables or disables debug level logging. func SetDebugLogging(enabled bool) { debugEnabled = enabled } // parseMIGLabels parses the given MIG string into a map of labels for // Cloud Logging. func parseMIGLabels(mig string) map[string]string { labels := make(map[string]string) if mig == "" { return labels } migRe := regexp.MustCompile(`^projects/[^/]+/(zones|regions)/([^/]+)/instanceGroupManagers/([^/]+)$`) matches := migRe.FindStringSubmatch(mig) if matches == nil { return labels } var locationLabel string switch matches[1] { case "zones": locationLabel = migZoneLabel case "regions": locationLabel = migRegionLabel } labels[migNameLabel] = matches[3] labels[locationLabel] = matches[2] return labels } // Init instantiates the logger. func Init(ctx context.Context, opts LogOpts) error { if opts.LoggerName == "" { return fmt.Errorf("logger name must be set") } loggerName = opts.LoggerName debugEnabled = opts.Debug formatFunction = opts.FormatFunction writers = opts.Writers if !opts.DisableLocalLogging { if err := localSetup(loggerName); err != nil { return fmt.Errorf("logger Init localSetup error: %v", err) } } if !opts.DisableCloudLogging && opts.ProjectName != "" { var err error cOpts := []option.ClientOption{} if opts.UserAgent != "" { cOpts = append(cOpts, option.WithUserAgent(opts.UserAgent)) } cloudLoggingClient, err = logging.NewClient(ctx, opts.ProjectName, cOpts...) if err != nil { Errorf("Continuing without cloud logging due to error in initialization: %v", err.Error()) // Log but don't return this error, as it doesn't prevent continuing. return nil } // Override default error handler. Must be a func and not nil. cloudLoggingClient.OnError = func(e error) { return } // The logger automatically detects and associates with a GCE // resource. However instance_name is not included in this // resource, so add an instance_name label to all log Entries. name, err := metadata.InstanceName() labels := make(map[string]string) if err == nil { labels["instance_name"] = name } // Add MIG labels if provided. migLabels := parseMIGLabels(opts.MIG) for k, v := range migLabels { labels[k] = v } // Initialize the logger. cloudLogger = cloudLoggingClient.Logger(loggerName, logging.CommonLabels(labels)) go func() { for { time.Sleep(5 * time.Second) cloudLogger.Flush() } }() } return nil } // Close closes the logger. func Close() { if cloudLoggingClient != nil { // Attempt to connect to Cloud Logging. timeoutContext, cancelFunc := context.WithTimeout(context.Background(), time.Second*3) defer cancelFunc() if err := cloudLoggingClient.Ping(timeoutContext); err != nil { Warningf("Cannot connect to cloud logging, skipping flush: %v", err) } else { cloudLogger.Flush() cloudLoggingClient.Close() } } localClose() } // Log writes an entry to all outputs. func Log(e LogEntry) { if e.Severity == Debug && !debugEnabled { return } if e.CallDepth == 0 { e.CallDepth = 2 } e.LocalTimestamp = now() e.Source = caller(e.CallDepth) local(e) for _, w := range writers { w.Write(e.bytes()) } var cloudSev logging.Severity if cloudLogger != nil { var payload interface{} if e.StructuredPayload != nil { payload = e.StructuredPayload } else { payload = e } switch e.Severity { case Debug: cloudSev = logging.Debug case Info: cloudSev = logging.Info case Warning: cloudSev = logging.Warning case Error: cloudSev = logging.Error case Critical: cloudSev = logging.Critical default: cloudSev = logging.Default } cloudLogger.Log(logging.Entry{Severity: cloudSev, SourceLocation: e.Source, Payload: payload, Labels: e.Labels}) } } // Debugf logs debug information. func Debugf(format string, v ...interface{}) { Log(LogEntry{Message: fmt.Sprintf(format, v...), Severity: Debug}) } // Infof logs general information. func Infof(format string, v ...interface{}) { Log(LogEntry{Message: fmt.Sprintf(format, v...), Severity: Info}) } // Warningf logs warning information. func Warningf(format string, v ...interface{}) { Log(LogEntry{Message: fmt.Sprintf(format, v...), Severity: Warning}) } // Errorf logs error information. func Errorf(format string, v ...interface{}) { Log(LogEntry{Message: fmt.Sprintf(format, v...), Severity: Error}) } // Fatalf logs critical error information and exits. func Fatalf(format string, v ...interface{}) { Log(LogEntry{Message: fmt.Sprintf(format, v...), Severity: Critical}) for _, f := range DeferredFatalFuncs { f() } Close() os.Exit(1) }