| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package cloudlog | |
| 6 | |
| 7 import ( | |
| 8 "crypto/rand" | |
| 9 "encoding/hex" | |
| 10 "fmt" | |
| 11 "sync/atomic" | |
| 12 | |
| 13 "github.com/luci/luci-go/common/clock" | |
| 14 "github.com/luci/luci-go/common/cloudlogging" | |
| 15 "github.com/luci/luci-go/common/logging" | |
| 16 "golang.org/x/net/context" | |
| 17 ) | |
| 18 | |
| 19 // generatedInsertIDBaseSize is the size, in bytes, of the InsertIDBase value | |
| 20 // generated by GenerateInsertIDBase. | |
| 21 const generatedInsertIDBaseSize = 32 | |
| 22 | |
| 23 // Config is a configuration structure for Cloud Logging. | |
| 24 type Config struct { | |
| 25 // InsertIDBase is the base unique value for log insert IDs. Setting thi
s will | |
| 26 // cause an InsertID to be generated for each log entry to uniquely iden
tify | |
| 27 // it to the cloud logging system, avoiding duplicates. | |
| 28 // | |
| 29 // A random InsertIDBase value can be generated by calling | |
| 30 // GenerateInsertIDBase(). | |
| 31 // | |
| 32 // If this is empty, no InsertID will be generated for log messages, whi
ch | |
| 33 // should generally be fine. | |
| 34 InsertIDBase string | |
| 35 | |
| 36 // FieldConverter, if not nil, is called to convert a logging Field to a | |
| 37 // string. | |
| 38 FieldConverter func(interface{}) string | |
| 39 } | |
| 40 | |
| 41 // GenerateInsertIDBase generates and assigns a random InsertIDBase value. | |
| 42 func (c *Config) GenerateInsertIDBase() error { | |
| 43 buf := make([]byte, generatedInsertIDBaseSize) | |
| 44 count, err := rand.Read(buf) | |
| 45 if err != nil { | |
| 46 return err | |
| 47 } | |
| 48 if count != len(buf) { | |
| 49 panic("cloudlog: generated count is smaller than buffer") | |
| 50 } | |
| 51 c.InsertIDBase = hex.EncodeToString(buf) | |
| 52 return nil | |
| 53 } | |
| 54 | |
| 55 // Use installs a cloud logging Logger into the supplied Context. | |
| 56 func Use(ctx context.Context, config Config, client cloudlogging.Client) context
.Context { | |
| 57 counter := new(atomicCounter) | |
| 58 | |
| 59 return logging.SetFactory(ctx, func(factoryCtx context.Context) logging.
Logger { | |
| 60 return &boundCloudLogger{ | |
| 61 Config: &config, | |
| 62 ctx: factoryCtx, | |
| 63 client: client, | |
| 64 index: counter.next(), | |
| 65 counter: new(atomicCounter), | |
| 66 } | |
| 67 }) | |
| 68 } | |
| 69 | |
| 70 // boundCloudLogger is a logging.Logger implementation binding the current | |
| 71 // Context to the Cloud Logging singleton. | |
| 72 type boundCloudLogger struct { | |
| 73 *Config | |
| 74 | |
| 75 ctx context.Context | |
| 76 client cloudlogging.Client | |
| 77 index int64 | |
| 78 // Allocating atomicCounter ensures proper 8-byte alignment. | |
| 79 counter *atomicCounter | |
| 80 } | |
| 81 | |
| 82 var _ logging.Logger = (*boundCloudLogger)(nil) | |
| 83 | |
| 84 func (l *boundCloudLogger) Debugf(fmt string, args ...interface{}) { | |
| 85 l.LogCall(logging.Debug, 0, fmt, args) | |
| 86 } | |
| 87 func (l *boundCloudLogger) Infof(fmt string, args ...interface{}) { | |
| 88 l.LogCall(logging.Info, 0, fmt, args) | |
| 89 } | |
| 90 func (l *boundCloudLogger) Warningf(fmt string, args ...interface{}) { | |
| 91 l.LogCall(logging.Warning, 0, fmt, args) | |
| 92 } | |
| 93 func (l *boundCloudLogger) Errorf(fmt string, args ...interface{}) { | |
| 94 l.LogCall(logging.Error, 0, fmt, args) | |
| 95 } | |
| 96 | |
| 97 func (l *boundCloudLogger) LogCall(level logging.Level, calldepth int, f string,
args []interface{}) { | |
| 98 if len(f) == 0 { | |
| 99 return | |
| 100 } | |
| 101 | |
| 102 text := fmt.Sprintf(f, args...) | |
| 103 fields := logging.GetFields(l.ctx) | |
| 104 if len(fields) > 0 { | |
| 105 text = text + " " + fields.String() | |
| 106 } | |
| 107 | |
| 108 // Add logging fields to labels. | |
| 109 entry := cloudlogging.Entry{ | |
| 110 Timestamp: clock.Now(l.ctx), | |
| 111 Severity: l.getSeverity(level), | |
| 112 Labels: make(map[string]string, len(fields)), | |
| 113 TextPayload: text, | |
| 114 } | |
| 115 | |
| 116 // Populate Labels. | |
| 117 for k, v := range fields { | |
| 118 val := "" | |
| 119 if l.FieldConverter != nil { | |
| 120 val = l.FieldConverter(v) | |
| 121 } else { | |
| 122 val = fmt.Sprintf("%v", v) | |
| 123 } | |
| 124 entry.Labels[k] = val | |
| 125 } | |
| 126 | |
| 127 // Generate an InsertID, if we're configured with a base. | |
| 128 if l.InsertIDBase != "" { | |
| 129 entry.InsertID = l.generateInsertID() | |
| 130 } | |
| 131 | |
| 132 l.client.PushEntries([]*cloudlogging.Entry{&entry}) | |
| 133 } | |
| 134 | |
| 135 func (*boundCloudLogger) getSeverity(l logging.Level) cloudlogging.Severity { | |
| 136 switch l { | |
| 137 case logging.Debug: | |
| 138 return cloudlogging.Debug | |
| 139 case logging.Info: | |
| 140 return cloudlogging.Info | |
| 141 case logging.Warning: | |
| 142 return cloudlogging.Warning | |
| 143 case logging.Error: | |
| 144 return cloudlogging.Error | |
| 145 | |
| 146 default: | |
| 147 return cloudlogging.Default | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 func (l *boundCloudLogger) generateInsertID() string { | |
| 152 return fmt.Sprintf("%s.%d.%d", l.InsertIDBase, l.index, l.counter.next()
) | |
| 153 } | |
| 154 | |
| 155 // atomicCounter must be allocated separately. If embedding, use a pointer. | |
| 156 // Stores current counter value. | |
| 157 type atomicCounter int64 | |
| 158 | |
| 159 func (c *atomicCounter) next() int64 { | |
| 160 nextPlusOne := atomic.AddInt64((*int64)(c), 1) | |
| 161 return nextPlusOne - 1 | |
| 162 } | |
| OLD | NEW |