...

Source file src/edge-infra.dev/pkg/lib/logging/logging.go

Documentation: edge-infra.dev/pkg/lib/logging

     1  package logging
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"time"
     7  
     8  	cloudlogging "cloud.google.com/go/logging"
     9  	logpb "cloud.google.com/go/logging/apiv2/loggingpb"
    10  	"github.com/go-logr/logr"
    11  	"go.uber.org/zap/zapcore"
    12  	kzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
    13  )
    14  
    15  // Severity maps to the levels understood by the Google Cloud Logging api
    16  // ref: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
    17  type Severity int
    18  
    19  const (
    20  	DEFAULT   = "DEFAULT"   // The log entry has no assigned severity level.
    21  	DEBUG     = "DEBUG"     // Debug or trace information.
    22  	INFO      = "INFO"      // Routine information, such as ongoing status or performance.
    23  	NOTICE    = "NOTICE"    // Normal but significant events, such as start up, shut down, or a configuration change.
    24  	WARNING   = "WARNING"   // Warning events might cause problems.
    25  	ERROR     = "ERROR"     // Error events are likely to cause problems.
    26  	CRITICAL  = "CRITICAL"  // Critical events cause more severe problems or outages.
    27  	ALERT     = "ALERT"     // A person must take an action immediately.
    28  	EMERGENCY = "EMERGENCY" // One or more systems are unusable.
    29  )
    30  
    31  type Operation struct {
    32  	ID       string `json:"id"`
    33  	Producer string `json:"producer"`
    34  	First    bool   `json:"first"`
    35  	Last     bool   `json:"last"`
    36  }
    37  
    38  type SourceLocation struct {
    39  	File     string `json:"file"`
    40  	Line     string `json:"line"`
    41  	Function string `json:"function"`
    42  }
    43  
    44  type HTTPRequest struct {
    45  	File     string `json:"file"`
    46  	Line     string `json:"line"`
    47  	Function string `json:"function"`
    48  }
    49  
    50  type LogEntry struct {
    51  	Severity       string         `json:"severity"`
    52  	Message        string         `json:"message"`
    53  	HTTPRequest    HTTPRequest    `json:"httpRequest"`
    54  	Operation      Operation      `json:"logging.googleapis.com/operation"`
    55  	SourceLocation SourceLocation `json:"logging.googleapis.com/sourceLocation"`
    56  }
    57  
    58  type LogEntry2 struct {
    59  	Severity       cloudlogging.Severity        `json:"severity"`
    60  	Message        string                       `json:"message"`
    61  	HTTPRequest    cloudlogging.HTTPRequest     `json:"httpRequest"`
    62  	Operation      logpb.LogEntryOperation      `json:"logging.googleapis.com/operation"`
    63  	SourceLocation logpb.LogEntrySourceLocation `json:"logging.googleapis.com/sourceLocation"`
    64  }
    65  
    66  const (
    67  	OperationKey = "logging.googleapis.com/operation"
    68  	MessageKey   = "message"
    69  	SeverityKey  = "severity"
    70  )
    71  
    72  // OperationFields provides a zerolog style interface for the Operation field
    73  func OperationFields(opID string) map[string]interface{} {
    74  	return map[string]interface{}{
    75  		"logging.googleapis.com/operation": map[string]interface{}{
    76  			"id": opID,
    77  		},
    78  	}
    79  }
    80  
    81  // OperationFields provides a 'keysAndValues ...interface{}' compatible interface for the Operation field,
    82  // for use in fmt and logr.Logger style logging. e.g., logr.Logger.WithValues(OperationKeysAndValues(id)...)
    83  func OperationKeysAndValues(opID string) []interface{} {
    84  	return []interface{}{
    85  		OperationKey,
    86  		map[string]interface{}{
    87  			"id": opID,
    88  		},
    89  	}
    90  }
    91  
    92  // HTTPRequestFields provides a zerolog style interface for the HTTPRequest field
    93  func HTTPRequestFields(method, url string, status, bytes int, elapsed time.Duration) map[string]interface{} {
    94  	return map[string]interface{}{
    95  		"httpRequest": map[string]interface{}{
    96  			"requestMethod": method,
    97  			"requestUrl":    url,
    98  			"status":        status,
    99  			"bytes":         bytes,
   100  			"elapsed":       elapsed.Milliseconds(),
   101  		},
   102  	}
   103  }
   104  
   105  // zapEncoderOpts provides options to configure a zap.Logger with the correct
   106  // JSON structure
   107  func zapEncoderOpts(dest io.Writer) kzap.Opts {
   108  	return func(o *kzap.Options) {
   109  		*o = kzap.Options{
   110  			EncoderConfigOptions: []kzap.EncoderConfigOption{
   111  				func(ec *zapcore.EncoderConfig) {
   112  					ec.MessageKey = MessageKey
   113  				},
   114  				func(ec *zapcore.EncoderConfig) {
   115  					ec.LevelKey = SeverityKey
   116  				},
   117  			},
   118  			DestWriter: dest,
   119  		}
   120  	}
   121  }
   122  
   123  // EdgeLogger TODO: logr logger suitable to use in controllers
   124  // TODO: provides structured log schema adherence
   125  type EdgeLogger struct {
   126  	logr.Logger
   127  }
   128  
   129  // private options struct, can only be changed by consumers via the public
   130  // Option function interface
   131  type options struct {
   132  	dest io.Writer
   133  }
   134  
   135  // Option is a functional interface used to define this package's public contract
   136  // with consumers
   137  type Option func(*options)
   138  
   139  // To allows setting the logging destination writer, defaults to `os.Stdout` if
   140  // not provided
   141  func To(dest io.Writer) Option {
   142  	return func(o *options) {
   143  		o.dest = dest
   144  	}
   145  }
   146  
   147  func New(opts ...Option) logr.Logger {
   148  	return NewLogger(opts...).Logger
   149  }
   150  
   151  func NewLogger(opts ...Option) *EdgeLogger {
   152  	o := &options{dest: os.Stdout}
   153  	for _, opt := range opts {
   154  		opt(o)
   155  	}
   156  
   157  	zapOpts := zapEncoderOpts(o.dest)
   158  	return &EdgeLogger{
   159  		Logger: kzap.New(zapOpts),
   160  	}
   161  }
   162  
   163  // WithValues is a helper that wraps the WithValues method of the embedded logr.Logger,
   164  // allowing callers to preserve the EdgeLogger type when calling WithValues, which would
   165  // return a logr.Logger instead
   166  func (e EdgeLogger) WithValues(keysAndValues ...interface{}) *EdgeLogger {
   167  	logrl := e.Logger.WithValues(keysAndValues...)
   168  	return &EdgeLogger{logrl}
   169  }
   170  

View as plain text