| 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 cloudlogging | |
| 6 | |
| 7 import ( | |
| 8 "errors" | |
| 9 "flag" | |
| 10 "fmt" | |
| 11 "net/http" | |
| 12 "os" | |
| 13 | |
| 14 "cloud.google.com/go/compute/metadata" | |
| 15 cloudlog "google.golang.org/api/logging/v1beta3" | |
| 16 ) | |
| 17 | |
| 18 // DefaultResourceType is used by NewClient if ClientOptions doesn't specify | |
| 19 // ResourceType. | |
| 20 const ( | |
| 21 // GCEService is service name for GCE instances. | |
| 22 GCEService = "compute.googleapis.com" | |
| 23 | |
| 24 // DefaultResourceType is used as the ResourceType value if Options does
n't | |
| 25 // specify a ResourceType. | |
| 26 DefaultResourceType = "machine" | |
| 27 | |
| 28 // WriteScope is the cloud logging OAuth write scope. | |
| 29 WriteScope = cloudlog.LoggingWriteScope | |
| 30 ) | |
| 31 | |
| 32 // CloudLoggingScopes is the set of OAuth scopes that are required to | |
| 33 // publish cloud logs via this package. | |
| 34 var CloudLoggingScopes = []string{ | |
| 35 WriteScope, | |
| 36 } | |
| 37 | |
| 38 // Labels is a set of key/value data that is transmitted alongside a log entry. | |
| 39 type Labels map[string]string | |
| 40 | |
| 41 // ClientOptions is passed to NewClient. | |
| 42 type ClientOptions struct { | |
| 43 // ProjectID is Cloud project to sends logs to. Must be set. | |
| 44 ProjectID string | |
| 45 // LogID identifies what sort of log this is. Must be set. | |
| 46 LogID string | |
| 47 | |
| 48 // Zone is the service name that the log will be registered under. Defau
lt is | |
| 49 // GCEService. | |
| 50 ServiceName string | |
| 51 // Zone is the zone to attribute the log to. This can be empty. | |
| 52 Zone string | |
| 53 // ResourceType identifies a kind of entity that produces this log (e.g. | |
| 54 // 'machine', 'master'). Default is DefaultResourceType. | |
| 55 ResourceType string | |
| 56 // ResourceID identifies exact instance of provided resource type (e.g | |
| 57 // 'vm12-m4', 'master.chromium.fyi'). Default is machine hostname. | |
| 58 ResourceID string | |
| 59 // Region is the region to attribute the log to. This can be empty. | |
| 60 Region string | |
| 61 // Region is the user ID to attribute the log to. This can be empty. | |
| 62 UserID string | |
| 63 | |
| 64 // UserAgent is an optional string appended to User-Agent HTTP header. | |
| 65 UserAgent string | |
| 66 | |
| 67 // CommonLabels is a list of key/value labels that will be included with
each | |
| 68 // log message that is sent. | |
| 69 CommonLabels Labels | |
| 70 | |
| 71 // ErrorWriter, if not nil, will be called when an error is encountered
in | |
| 72 // the Client iself. This is used to report errors because the client ma
y | |
| 73 // send messages asynchronously, so they will not be returned immediatel
y, | |
| 74 // as is the go convention. | |
| 75 ErrorWriter func(string) | |
| 76 } | |
| 77 | |
| 78 // Populate fills the Config with default values derived from the environment. | |
| 79 // Missing values will not be set. | |
| 80 // | |
| 81 // This includes: | |
| 82 // - GCE metadata querying. | |
| 83 func (o *ClientOptions) Populate() { | |
| 84 if metadata.OnGCE() { | |
| 85 get := func(f func() (string, error), val *string) { | |
| 86 if v, err := f(); err == nil { | |
| 87 *val = v | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 o.ServiceName = GCEService | |
| 92 get(metadata.ProjectID, &o.ProjectID) | |
| 93 get(metadata.InstanceName, &o.ResourceType) | |
| 94 get(metadata.InstanceID, &o.ResourceID) | |
| 95 get(metadata.Zone, &o.Zone) | |
| 96 } | |
| 97 if o.ErrorWriter == nil { | |
| 98 o.ErrorWriter = func(s string) { | |
| 99 fmt.Fprintln(os.Stderr, s) | |
| 100 } | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 // Validate evaluates a Config and returns an error if it is invalid or | |
| 105 // incomplete. | |
| 106 func (o *ClientOptions) Validate() error { | |
| 107 if o.ProjectID == "" { | |
| 108 return errors.New("cloudlogging: You must supply a project name"
) | |
| 109 } | |
| 110 if o.LogID == "" { | |
| 111 return errors.New("cloudlogging: You must supply a logs ID") | |
| 112 } | |
| 113 if o.ResourceType == "" { | |
| 114 return errors.New("cloudlogging: You must supply a resource type
") | |
| 115 } | |
| 116 return nil | |
| 117 } | |
| 118 | |
| 119 // AddFlags adds logging flags to the supplied FlagSet. | |
| 120 func (o *ClientOptions) AddFlags(fs *flag.FlagSet) { | |
| 121 fs.StringVar(&o.LogID, "cloud-logging-logs-id", o.LogID, | |
| 122 "For cloud logging, the log stream ID.") | |
| 123 fs.StringVar(&o.ServiceName, "cloud-logging-service", o.ServiceName, | |
| 124 "For cloud logging, the service name.") | |
| 125 fs.StringVar(&o.ProjectID, "cloud-logging-project-name", o.ProjectID, | |
| 126 "For cloud logging, the project name.") | |
| 127 fs.StringVar(&o.ResourceType, "cloud-logging-resource-type", o.ResourceT
ype, | |
| 128 "For cloud logging, the instance name.") | |
| 129 fs.StringVar(&o.ResourceID, "cloud-logging-resource-id", o.ResourceID, | |
| 130 "For cloud logging, the instance ID.") | |
| 131 fs.StringVar(&o.Region, "cloud-logging-region", o.Region, | |
| 132 "For cloud logging, the region.") | |
| 133 fs.StringVar(&o.UserID, "cloud-logging-user", o.UserID, | |
| 134 "For cloud logging, the user ID.") | |
| 135 fs.StringVar(&o.Zone, "cloud-logging-zone", o.Zone, | |
| 136 "For cloud logging, the zone.") | |
| 137 } | |
| 138 | |
| 139 // Client knows how to send entries to Cloud Logging log. | |
| 140 type Client interface { | |
| 141 // PushEntries sends entries to Cloud Logging. No retries. | |
| 142 PushEntries(entries []*Entry) error | |
| 143 } | |
| 144 | |
| 145 // NewClient returns new object that knows how to push log entries to a single | |
| 146 // log in Cloud Logging. | |
| 147 func NewClient(opts ClientOptions, client *http.Client) (Client, error) { | |
| 148 if err := opts.Validate(); err != nil { | |
| 149 return nil, err | |
| 150 } | |
| 151 | |
| 152 if opts.ResourceType == "" { | |
| 153 opts.ResourceType = DefaultResourceType | |
| 154 } | |
| 155 if opts.ResourceID == "" { | |
| 156 var err error | |
| 157 hostname, err := os.Hostname() | |
| 158 if err != nil { | |
| 159 return nil, err | |
| 160 } | |
| 161 opts.ResourceID = hostname | |
| 162 } | |
| 163 if opts.LogID == "" { | |
| 164 return nil, errors.New("cloudlogging: no LogID is provided") | |
| 165 } | |
| 166 | |
| 167 service, err := cloudlog.New(client) | |
| 168 if err != nil { | |
| 169 return nil, err | |
| 170 } | |
| 171 if opts.UserAgent != "" { | |
| 172 service.UserAgent = opts.UserAgent | |
| 173 } | |
| 174 | |
| 175 c := clientImpl{ | |
| 176 ClientOptions: &opts, | |
| 177 service: cloudlog.NewProjectsLogsEntriesService(service), | |
| 178 commonLabels: make(map[string]string, len(opts.CommonLabels)), | |
| 179 } | |
| 180 for k, v := range opts.CommonLabels { | |
| 181 c.commonLabels[k] = v | |
| 182 } | |
| 183 if c.ResourceType != "" { | |
| 184 c.commonLabels["compute.googleapis.com/resource_type"] = c.Resou
rceType | |
| 185 } | |
| 186 if c.ResourceID != "" { | |
| 187 c.commonLabels["compute.googleapis.com/resource_id"] = c.Resourc
eID | |
| 188 } | |
| 189 return &c, nil | |
| 190 } | |
| 191 | |
| 192 type clientImpl struct { | |
| 193 *ClientOptions | |
| 194 | |
| 195 service *cloudlog.ProjectsLogsEntriesService | |
| 196 // These are passed to Cloud Logging API as is. | |
| 197 commonLabels map[string]string | |
| 198 } | |
| 199 | |
| 200 func (c *clientImpl) PushEntries(entries []*Entry) error { | |
| 201 req := cloudlog.WriteLogEntriesRequest{ | |
| 202 CommonLabels: c.commonLabels, | |
| 203 Entries: make([]*cloudlog.LogEntry, 0, len(entries)), | |
| 204 } | |
| 205 for i, e := range entries { | |
| 206 entry, err := e.cloudLogEntry(c.ClientOptions) | |
| 207 if err != nil { | |
| 208 c.writeError("Failed to build log for entry #%d: %v", i,
err) | |
| 209 return err | |
| 210 } | |
| 211 req.Entries = append(req.Entries, entry) | |
| 212 } | |
| 213 | |
| 214 _, err := c.service.Write(c.ProjectID, c.LogID, &req).Do() | |
| 215 if err != nil { | |
| 216 c.writeError(err.Error()) | |
| 217 } | |
| 218 return err | |
| 219 } | |
| 220 | |
| 221 func (c *clientImpl) writeError(f string, args ...interface{}) { | |
| 222 if c.ErrorWriter != nil { | |
| 223 c.ErrorWriter(fmt.Sprintf(f, args...)) | |
| 224 } | |
| 225 } | |
| OLD | NEW |