github.com/grailbio/base@v0.0.11/eventlog/eventlog.go (about)

     1  // Copyright 2020 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package eventlog provides logging of semi-structured events, particularly in
     6  // service of downstream analysis, e.g. when machines are started, when a user
     7  // issues a command, when failures happen.
     8  //
     9  // For example, you can log events to CloudWatch Logs:
    10  //
    11  //  sess := session.NewSession()
    12  //  cw := cloudwatchlogs.New(sess)
    13  //  e := cloudwatch.NewEventer(cw, "myLogGroup", "myLogStream")
    14  //  e.Event("rpcRetry", "org", "GRAIL", "retry", 0, "maxRetry", 10)
    15  //  e.Event("machineStopped", "addr", "192.168.1.1", "duration", 3600.0, "startTime": 1584140534)
    16  //
    17  // These events can now be analyzed and monitored using CloudWatch Logs tooling.
    18  package eventlog
    19  
    20  import (
    21  	"bytes"
    22  
    23  	"github.com/grailbio/base/config"
    24  	"github.com/grailbio/base/log"
    25  )
    26  
    27  func init() {
    28  	config.Register("eventer/nop", func(constr *config.Constructor[Nop]) {
    29  		constr.Doc = "eventer/nop configures a no-op event logger"
    30  		constr.New = func() (Nop, error) {
    31  			return Nop{}, nil
    32  		}
    33  	})
    34  	config.Register("eventer/log-info", func(constr *config.Constructor[Log]) {
    35  		constr.Doc = "eventer/log-info configures an eventer that writes events at log level info"
    36  		constr.New = func() (Log, error) {
    37  			return Log(log.Info), nil
    38  		}
    39  	})
    40  	// We do the most conservative thing by default, making event logging a
    41  	// no-op.
    42  	config.Default("eventer", "eventer/nop")
    43  }
    44  
    45  // Eventer is called to log events.
    46  type Eventer interface {
    47  	// Event logs an event of typ with (key string, value interface{}) fields given in fieldPairs
    48  	// as k0, v0, k1, v1, ...kn, vn. For example:
    49  	//
    50  	//  s.Event("machineStart", "addr", "192.168.1.2", "time", time.Now().Unix())
    51  	//
    52  	// The value will be serialized as JSON.
    53  	//
    54  	// The key "eventType" is reserved. Field keys must be unique. Any violation will result
    55  	// in the event being dropped and logged.
    56  	//
    57  	// Implementations must be safe for concurrent use.
    58  	Event(typ string, fieldPairs ...interface{})
    59  }
    60  
    61  // Nop is a no-op Eventer.
    62  type Nop struct{}
    63  
    64  var _ Eventer = Nop{}
    65  
    66  func (Nop) String() string {
    67  	return "disabled"
    68  }
    69  
    70  // Event implements Eventer.
    71  func (Nop) Event(_ string, _ ...interface{}) {}
    72  
    73  // Log is an Eventer that writes events to the logger. It's intended for debugging/development.
    74  type Log log.Level
    75  
    76  var _ Eventer = Log(log.Debug)
    77  
    78  // Event implements Eventer.
    79  func (l Log) Event(typ string, fieldPairs ...interface{}) {
    80  	f := bytes.NewBufferString("eventlog: %s {")
    81  	for i := range fieldPairs {
    82  		f.WriteString("%v")
    83  		if i%2 == 0 {
    84  			f.WriteString(": ")
    85  		} else if i < len(fieldPairs)-1 {
    86  			f.WriteString(", ")
    87  		}
    88  	}
    89  	f.WriteByte('}')
    90  	log.Level(l).Printf(f.String(), append([]interface{}{typ}, fieldPairs...)...)
    91  }