1// Copyright 2016 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// API/gRPC features intentionally missing from this client: 16// - You cannot have the server pick the time of the entry. This client 17// always sends a time. 18// - There is no way to provide a protocol buffer payload. 19// - No support for the "partial success" feature when writing log entries. 20 21// TODO(jba): test whether forward-slash characters in the log ID must be URL-encoded. 22// These features are missing now, but will likely be added: 23// - There is no way to specify CallOptions. 24 25package logging 26 27import ( 28 "bytes" 29 "context" 30 "encoding/json" 31 "errors" 32 "fmt" 33 "log" 34 "net/http" 35 "strconv" 36 "strings" 37 "sync" 38 "time" 39 "unicode/utf8" 40 41 "cloud.google.com/go/compute/metadata" 42 "cloud.google.com/go/internal/version" 43 vkit "cloud.google.com/go/logging/apiv2" 44 "cloud.google.com/go/logging/internal" 45 "github.com/golang/protobuf/proto" 46 "github.com/golang/protobuf/ptypes" 47 structpb "github.com/golang/protobuf/ptypes/struct" 48 tspb "github.com/golang/protobuf/ptypes/timestamp" 49 "google.golang.org/api/option" 50 "google.golang.org/api/support/bundler" 51 mrpb "google.golang.org/genproto/googleapis/api/monitoredres" 52 logtypepb "google.golang.org/genproto/googleapis/logging/type" 53 logpb "google.golang.org/genproto/googleapis/logging/v2" 54) 55 56const ( 57 // ReadScope is the scope for reading from the logging service. 58 ReadScope = "https://www.googleapis.com/auth/logging.read" 59 60 // WriteScope is the scope for writing to the logging service. 61 WriteScope = "https://www.googleapis.com/auth/logging.write" 62 63 // AdminScope is the scope for administrative actions on the logging service. 64 AdminScope = "https://www.googleapis.com/auth/logging.admin" 65) 66 67const ( 68 // defaultErrorCapacity is the capacity of the channel used to deliver 69 // errors to the OnError function. 70 defaultErrorCapacity = 10 71 72 // DefaultDelayThreshold is the default value for the DelayThreshold LoggerOption. 73 DefaultDelayThreshold = time.Second 74 75 // DefaultEntryCountThreshold is the default value for the EntryCountThreshold LoggerOption. 76 DefaultEntryCountThreshold = 1000 77 78 // DefaultEntryByteThreshold is the default value for the EntryByteThreshold LoggerOption. 79 DefaultEntryByteThreshold = 1 << 20 // 1MiB 80 81 // DefaultBufferedByteLimit is the default value for the BufferedByteLimit LoggerOption. 82 DefaultBufferedByteLimit = 1 << 30 // 1GiB 83 84 // defaultWriteTimeout is the timeout for the underlying write API calls. As 85 // write API calls are not idempotent, they are not retried on timeout. This 86 // timeout is to allow clients to degrade gracefully if underlying logging 87 // service is temporarily impaired for some reason. 88 defaultWriteTimeout = 10 * time.Minute 89) 90 91// For testing: 92var now = time.Now 93 94// ErrOverflow signals that the number of buffered entries for a Logger 95// exceeds its BufferLimit. 96var ErrOverflow = bundler.ErrOverflow 97 98// ErrOversizedEntry signals that an entry's size exceeds the maximum number of 99// bytes that will be sent in a single call to the logging service. 100var ErrOversizedEntry = bundler.ErrOversizedItem 101 102// Client is a Logging client. A Client is associated with a single Cloud project. 103type Client struct { 104 client *vkit.Client // client for the logging service 105 parent string // e.g. "projects/proj-id" 106 errc chan error // should be buffered to minimize dropped errors 107 donec chan struct{} // closed on Client.Close to close Logger bundlers 108 loggers sync.WaitGroup // so we can wait for loggers to close 109 closed bool 110 111 mu sync.Mutex 112 nErrs int // number of errors we saw 113 lastErr error // last error we saw 114 115 // OnError is called when an error occurs in a call to Log or Flush. The 116 // error may be due to an invalid Entry, an overflow because BufferLimit 117 // was reached (in which case the error will be ErrOverflow) or an error 118 // communicating with the logging service. OnError is called with errors 119 // from all Loggers. It is never called concurrently. OnError is expected 120 // to return quickly; if errors occur while OnError is running, some may 121 // not be reported. The default behavior is to call log.Printf. 122 // 123 // This field should be set only once, before any method of Client is called. 124 OnError func(err error) 125} 126 127// NewClient returns a new logging client associated with the provided parent. 128// A parent can take any of the following forms: 129// projects/PROJECT_ID 130// folders/FOLDER_ID 131// billingAccounts/ACCOUNT_ID 132// organizations/ORG_ID 133// for backwards compatibility, a string with no '/' is also allowed and is interpreted 134// as a project ID. 135// 136// By default NewClient uses WriteScope. To use a different scope, call 137// NewClient using a WithScopes option (see https://godoc.org/google.golang.org/api/option#WithScopes). 138func NewClient(ctx context.Context, parent string, opts ...option.ClientOption) (*Client, error) { 139 if !strings.ContainsRune(parent, '/') { 140 parent = "projects/" + parent 141 } 142 opts = append([]option.ClientOption{ 143 option.WithEndpoint(internal.ProdAddr), 144 option.WithScopes(WriteScope), 145 }, opts...) 146 c, err := vkit.NewClient(ctx, opts...) 147 if err != nil { 148 return nil, err 149 } 150 c.SetGoogleClientInfo("gccl", version.Repo) 151 client := &Client{ 152 client: c, 153 parent: parent, 154 errc: make(chan error, defaultErrorCapacity), // create a small buffer for errors 155 donec: make(chan struct{}), 156 OnError: func(e error) { log.Printf("logging client: %v", e) }, 157 } 158 // Call the user's function synchronously, to make life easier for them. 159 go func() { 160 for err := range client.errc { 161 // This reference to OnError is memory-safe if the user sets OnError before 162 // calling any client methods. The reference happens before the first read from 163 // client.errc, which happens before the first write to client.errc, which 164 // happens before any call, which happens before the user sets OnError. 165 if fn := client.OnError; fn != nil { 166 fn(err) 167 } else { 168 log.Printf("logging (parent %q): %v", parent, err) 169 } 170 } 171 }() 172 return client, nil 173} 174 175var unixZeroTimestamp *tspb.Timestamp 176 177func init() { 178 var err error 179 unixZeroTimestamp, err = ptypes.TimestampProto(time.Unix(0, 0)) 180 if err != nil { 181 panic(err) 182 } 183} 184 185// Ping reports whether the client's connection to the logging service and the 186// authentication configuration are valid. To accomplish this, Ping writes a 187// log entry "ping" to a log named "ping". 188func (c *Client) Ping(ctx context.Context) error { 189 ent := &logpb.LogEntry{ 190 Payload: &logpb.LogEntry_TextPayload{TextPayload: "ping"}, 191 Timestamp: unixZeroTimestamp, // Identical timestamps and insert IDs are both 192 InsertId: "ping", // necessary for the service to dedup these entries. 193 } 194 _, err := c.client.WriteLogEntries(ctx, &logpb.WriteLogEntriesRequest{ 195 LogName: internal.LogPath(c.parent, "ping"), 196 Resource: monitoredResource(c.parent), 197 Entries: []*logpb.LogEntry{ent}, 198 }) 199 return err 200} 201 202// error puts the error on the client's error channel 203// without blocking, and records summary error info. 204func (c *Client) error(err error) { 205 select { 206 case c.errc <- err: 207 default: 208 } 209 c.mu.Lock() 210 c.lastErr = err 211 c.nErrs++ 212 c.mu.Unlock() 213} 214 215func (c *Client) extractErrorInfo() error { 216 var err error 217 c.mu.Lock() 218 if c.lastErr != nil { 219 err = fmt.Errorf("saw %d errors; last: %v", c.nErrs, c.lastErr) 220 c.nErrs = 0 221 c.lastErr = nil 222 } 223 c.mu.Unlock() 224 return err 225} 226 227// A Logger is used to write log messages to a single log. It can be configured 228// with a log ID, common monitored resource, and a set of common labels. 229type Logger struct { 230 client *Client 231 logName string // "projects/{projectID}/logs/{logID}" 232 stdLoggers map[Severity]*log.Logger 233 bundler *bundler.Bundler 234 235 // Options 236 commonResource *mrpb.MonitoredResource 237 commonLabels map[string]string 238 ctxFunc func() (context.Context, func()) 239} 240 241// A LoggerOption is a configuration option for a Logger. 242type LoggerOption interface { 243 set(*Logger) 244} 245 246// CommonResource sets the monitored resource associated with all log entries 247// written from a Logger. If not provided, the resource is automatically 248// detected based on the running environment. This value can be overridden 249// per-entry by setting an Entry's Resource field. 250func CommonResource(r *mrpb.MonitoredResource) LoggerOption { return commonResource{r} } 251 252type commonResource struct{ *mrpb.MonitoredResource } 253 254func (r commonResource) set(l *Logger) { l.commonResource = r.MonitoredResource } 255 256var detectedResource struct { 257 pb *mrpb.MonitoredResource 258 once sync.Once 259} 260 261func detectResource() *mrpb.MonitoredResource { 262 detectedResource.once.Do(func() { 263 if !metadata.OnGCE() { 264 return 265 } 266 projectID, err := metadata.ProjectID() 267 if err != nil { 268 return 269 } 270 id, err := metadata.InstanceID() 271 if err != nil { 272 return 273 } 274 zone, err := metadata.Zone() 275 if err != nil { 276 return 277 } 278 name, err := metadata.InstanceName() 279 if err != nil { 280 return 281 } 282 detectedResource.pb = &mrpb.MonitoredResource{ 283 Type: "gce_instance", 284 Labels: map[string]string{ 285 "project_id": projectID, 286 "instance_id": id, 287 "instance_name": name, 288 "zone": zone, 289 }, 290 } 291 }) 292 return detectedResource.pb 293} 294 295var resourceInfo = map[string]struct{ rtype, label string }{ 296 "organizations": {"organization", "organization_id"}, 297 "folders": {"folder", "folder_id"}, 298 "projects": {"project", "project_id"}, 299 "billingAccounts": {"billing_account", "account_id"}, 300} 301 302func monitoredResource(parent string) *mrpb.MonitoredResource { 303 parts := strings.SplitN(parent, "/", 2) 304 if len(parts) != 2 { 305 return globalResource(parent) 306 } 307 info, ok := resourceInfo[parts[0]] 308 if !ok { 309 return globalResource(parts[1]) 310 } 311 return &mrpb.MonitoredResource{ 312 Type: info.rtype, 313 Labels: map[string]string{info.label: parts[1]}, 314 } 315} 316 317func globalResource(projectID string) *mrpb.MonitoredResource { 318 return &mrpb.MonitoredResource{ 319 Type: "global", 320 Labels: map[string]string{ 321 "project_id": projectID, 322 }, 323 } 324} 325 326// CommonLabels are labels that apply to all log entries written from a Logger, 327// so that you don't have to repeat them in each log entry's Labels field. If 328// any of the log entries contains a (key, value) with the same key that is in 329// CommonLabels, then the entry's (key, value) overrides the one in 330// CommonLabels. 331func CommonLabels(m map[string]string) LoggerOption { return commonLabels(m) } 332 333type commonLabels map[string]string 334 335func (c commonLabels) set(l *Logger) { l.commonLabels = c } 336 337// ConcurrentWriteLimit determines how many goroutines will send log entries to the 338// underlying service. The default is 1. Set ConcurrentWriteLimit to a higher value to 339// increase throughput. 340func ConcurrentWriteLimit(n int) LoggerOption { return concurrentWriteLimit(n) } 341 342type concurrentWriteLimit int 343 344func (c concurrentWriteLimit) set(l *Logger) { l.bundler.HandlerLimit = int(c) } 345 346// DelayThreshold is the maximum amount of time that an entry should remain 347// buffered in memory before a call to the logging service is triggered. Larger 348// values of DelayThreshold will generally result in fewer calls to the logging 349// service, while increasing the risk that log entries will be lost if the 350// process crashes. 351// The default is DefaultDelayThreshold. 352func DelayThreshold(d time.Duration) LoggerOption { return delayThreshold(d) } 353 354type delayThreshold time.Duration 355 356func (d delayThreshold) set(l *Logger) { l.bundler.DelayThreshold = time.Duration(d) } 357 358// EntryCountThreshold is the maximum number of entries that will be buffered 359// in memory before a call to the logging service is triggered. Larger values 360// will generally result in fewer calls to the logging service, while 361// increasing both memory consumption and the risk that log entries will be 362// lost if the process crashes. 363// The default is DefaultEntryCountThreshold. 364func EntryCountThreshold(n int) LoggerOption { return entryCountThreshold(n) } 365 366type entryCountThreshold int 367 368func (e entryCountThreshold) set(l *Logger) { l.bundler.BundleCountThreshold = int(e) } 369 370// EntryByteThreshold is the maximum number of bytes of entries that will be 371// buffered in memory before a call to the logging service is triggered. See 372// EntryCountThreshold for a discussion of the tradeoffs involved in setting 373// this option. 374// The default is DefaultEntryByteThreshold. 375func EntryByteThreshold(n int) LoggerOption { return entryByteThreshold(n) } 376 377type entryByteThreshold int 378 379func (e entryByteThreshold) set(l *Logger) { l.bundler.BundleByteThreshold = int(e) } 380 381// EntryByteLimit is the maximum number of bytes of entries that will be sent 382// in a single call to the logging service. ErrOversizedEntry is returned if an 383// entry exceeds EntryByteLimit. This option limits the size of a single RPC 384// payload, to account for network or service issues with large RPCs. If 385// EntryByteLimit is smaller than EntryByteThreshold, the latter has no effect. 386// The default is zero, meaning there is no limit. 387func EntryByteLimit(n int) LoggerOption { return entryByteLimit(n) } 388 389type entryByteLimit int 390 391func (e entryByteLimit) set(l *Logger) { l.bundler.BundleByteLimit = int(e) } 392 393// BufferedByteLimit is the maximum number of bytes that the Logger will keep 394// in memory before returning ErrOverflow. This option limits the total memory 395// consumption of the Logger (but note that each Logger has its own, separate 396// limit). It is possible to reach BufferedByteLimit even if it is larger than 397// EntryByteThreshold or EntryByteLimit, because calls triggered by the latter 398// two options may be enqueued (and hence occupying memory) while new log 399// entries are being added. 400// The default is DefaultBufferedByteLimit. 401func BufferedByteLimit(n int) LoggerOption { return bufferedByteLimit(n) } 402 403type bufferedByteLimit int 404 405func (b bufferedByteLimit) set(l *Logger) { l.bundler.BufferedByteLimit = int(b) } 406 407// ContextFunc is a function that will be called to obtain a context.Context for the 408// WriteLogEntries RPC executed in the background for calls to Logger.Log. The 409// default is a function that always returns context.Background. The second return 410// value of the function is a function to call after the RPC completes. 411// 412// The function is not used for calls to Logger.LogSync, since the caller can pass 413// in the context directly. 414// 415// This option is EXPERIMENTAL. It may be changed or removed. 416func ContextFunc(f func() (ctx context.Context, afterCall func())) LoggerOption { 417 return contextFunc(f) 418} 419 420type contextFunc func() (ctx context.Context, afterCall func()) 421 422func (c contextFunc) set(l *Logger) { l.ctxFunc = c } 423 424// Logger returns a Logger that will write entries with the given log ID, such as 425// "syslog". A log ID must be less than 512 characters long and can only 426// include the following characters: upper and lower case alphanumeric 427// characters: [A-Za-z0-9]; and punctuation characters: forward-slash, 428// underscore, hyphen, and period. 429func (c *Client) Logger(logID string, opts ...LoggerOption) *Logger { 430 r := detectResource() 431 if r == nil { 432 r = monitoredResource(c.parent) 433 } 434 l := &Logger{ 435 client: c, 436 logName: internal.LogPath(c.parent, logID), 437 commonResource: r, 438 ctxFunc: func() (context.Context, func()) { return context.Background(), nil }, 439 } 440 l.bundler = bundler.NewBundler(&logpb.LogEntry{}, func(entries interface{}) { 441 l.writeLogEntries(entries.([]*logpb.LogEntry)) 442 }) 443 l.bundler.DelayThreshold = DefaultDelayThreshold 444 l.bundler.BundleCountThreshold = DefaultEntryCountThreshold 445 l.bundler.BundleByteThreshold = DefaultEntryByteThreshold 446 l.bundler.BufferedByteLimit = DefaultBufferedByteLimit 447 for _, opt := range opts { 448 opt.set(l) 449 } 450 l.stdLoggers = map[Severity]*log.Logger{} 451 for s := range severityName { 452 l.stdLoggers[s] = log.New(severityWriter{l, s}, "", 0) 453 } 454 455 c.loggers.Add(1) 456 // Start a goroutine that cleans up the bundler, its channel 457 // and the writer goroutines when the client is closed. 458 go func() { 459 defer c.loggers.Done() 460 <-c.donec 461 l.bundler.Flush() 462 }() 463 return l 464} 465 466type severityWriter struct { 467 l *Logger 468 s Severity 469} 470 471func (w severityWriter) Write(p []byte) (n int, err error) { 472 w.l.Log(Entry{ 473 Severity: w.s, 474 Payload: string(p), 475 }) 476 return len(p), nil 477} 478 479// Close waits for all opened loggers to be flushed and closes the client. 480func (c *Client) Close() error { 481 if c.closed { 482 return nil 483 } 484 close(c.donec) // close Logger bundlers 485 c.loggers.Wait() // wait for all bundlers to flush and close 486 // Now there can be no more errors. 487 close(c.errc) // terminate error goroutine 488 // Prefer errors arising from logging to the error returned from Close. 489 err := c.extractErrorInfo() 490 err2 := c.client.Close() 491 if err == nil { 492 err = err2 493 } 494 c.closed = true 495 return err 496} 497 498// Severity is the severity of the event described in a log entry. These 499// guideline severity levels are ordered, with numerically smaller levels 500// treated as less severe than numerically larger levels. 501type Severity int 502 503const ( 504 // Default means the log entry has no assigned severity level. 505 Default = Severity(logtypepb.LogSeverity_DEFAULT) 506 // Debug means debug or trace information. 507 Debug = Severity(logtypepb.LogSeverity_DEBUG) 508 // Info means routine information, such as ongoing status or performance. 509 Info = Severity(logtypepb.LogSeverity_INFO) 510 // Notice means normal but significant events, such as start up, shut down, or configuration. 511 Notice = Severity(logtypepb.LogSeverity_NOTICE) 512 // Warning means events that might cause problems. 513 Warning = Severity(logtypepb.LogSeverity_WARNING) 514 // Error means events that are likely to cause problems. 515 Error = Severity(logtypepb.LogSeverity_ERROR) 516 // Critical means events that cause more severe problems or brief outages. 517 Critical = Severity(logtypepb.LogSeverity_CRITICAL) 518 // Alert means a person must take an action immediately. 519 Alert = Severity(logtypepb.LogSeverity_ALERT) 520 // Emergency means one or more systems are unusable. 521 Emergency = Severity(logtypepb.LogSeverity_EMERGENCY) 522) 523 524var severityName = map[Severity]string{ 525 Default: "Default", 526 Debug: "Debug", 527 Info: "Info", 528 Notice: "Notice", 529 Warning: "Warning", 530 Error: "Error", 531 Critical: "Critical", 532 Alert: "Alert", 533 Emergency: "Emergency", 534} 535 536// String converts a severity level to a string. 537func (v Severity) String() string { 538 // same as proto.EnumName 539 s, ok := severityName[v] 540 if ok { 541 return s 542 } 543 return strconv.Itoa(int(v)) 544} 545 546// ParseSeverity returns the Severity whose name equals s, ignoring case. It 547// returns Default if no Severity matches. 548func ParseSeverity(s string) Severity { 549 sl := strings.ToLower(s) 550 for sev, name := range severityName { 551 if strings.ToLower(name) == sl { 552 return sev 553 } 554 } 555 return Default 556} 557 558// Entry is a log entry. 559// See https://cloud.google.com/logging/docs/view/logs_index for more about entries. 560type Entry struct { 561 // Timestamp is the time of the entry. If zero, the current time is used. 562 Timestamp time.Time 563 564 // Severity is the entry's severity level. 565 // The zero value is Default. 566 Severity Severity 567 568 // Payload must be either a string, or something that marshals via the 569 // encoding/json package to a JSON object (and not any other type of JSON value). 570 Payload interface{} 571 572 // Labels optionally specifies key/value labels for the log entry. 573 // The Logger.Log method takes ownership of this map. See Logger.CommonLabels 574 // for more about labels. 575 Labels map[string]string 576 577 // InsertID is a unique ID for the log entry. If you provide this field, 578 // the logging service considers other log entries in the same log with the 579 // same ID as duplicates which can be removed. If omitted, the logging 580 // service will generate a unique ID for this log entry. Note that because 581 // this client retries RPCs automatically, it is possible (though unlikely) 582 // that an Entry without an InsertID will be written more than once. 583 InsertID string 584 585 // HTTPRequest optionally specifies metadata about the HTTP request 586 // associated with this log entry, if applicable. It is optional. 587 HTTPRequest *HTTPRequest 588 589 // Operation optionally provides information about an operation associated 590 // with the log entry, if applicable. 591 Operation *logpb.LogEntryOperation 592 593 // LogName is the full log name, in the form 594 // "projects/{ProjectID}/logs/{LogID}". It is set by the client when 595 // reading entries. It is an error to set it when writing entries. 596 LogName string 597 598 // Resource is the monitored resource associated with the entry. 599 Resource *mrpb.MonitoredResource 600 601 // Trace is the resource name of the trace associated with the log entry, 602 // if any. If it contains a relative resource name, the name is assumed to 603 // be relative to //tracing.googleapis.com. 604 Trace string 605 606 // ID of the span within the trace associated with the log entry. 607 // The ID is a 16-character hexadecimal encoding of an 8-byte array. 608 SpanID string 609 610 // Optional. Source code location information associated with the log entry, 611 // if any. 612 SourceLocation *logpb.LogEntrySourceLocation 613} 614 615// HTTPRequest contains an http.Request as well as additional 616// information about the request and its response. 617type HTTPRequest struct { 618 // Request is the http.Request passed to the handler. 619 Request *http.Request 620 621 // RequestSize is the size of the HTTP request message in bytes, including 622 // the request headers and the request body. 623 RequestSize int64 624 625 // Status is the response code indicating the status of the response. 626 // Examples: 200, 404. 627 Status int 628 629 // ResponseSize is the size of the HTTP response message sent back to the client, in bytes, 630 // including the response headers and the response body. 631 ResponseSize int64 632 633 // Latency is the request processing latency on the server, from the time the request was 634 // received until the response was sent. 635 Latency time.Duration 636 637 // LocalIP is the IP address (IPv4 or IPv6) of the origin server that the request 638 // was sent to. 639 LocalIP string 640 641 // RemoteIP is the IP address (IPv4 or IPv6) of the client that issued the 642 // HTTP request. Examples: "192.168.1.1", "FE80::0202:B3FF:FE1E:8329". 643 RemoteIP string 644 645 // CacheHit reports whether an entity was served from cache (with or without 646 // validation). 647 CacheHit bool 648 649 // CacheValidatedWithOriginServer reports whether the response was 650 // validated with the origin server before being served from cache. This 651 // field is only meaningful if CacheHit is true. 652 CacheValidatedWithOriginServer bool 653} 654 655func fromHTTPRequest(r *HTTPRequest) *logtypepb.HttpRequest { 656 if r == nil { 657 return nil 658 } 659 if r.Request == nil { 660 panic("HTTPRequest must have a non-nil Request") 661 } 662 u := *r.Request.URL 663 u.Fragment = "" 664 pb := &logtypepb.HttpRequest{ 665 RequestMethod: r.Request.Method, 666 RequestUrl: fixUTF8(u.String()), 667 RequestSize: r.RequestSize, 668 Status: int32(r.Status), 669 ResponseSize: r.ResponseSize, 670 UserAgent: r.Request.UserAgent(), 671 ServerIp: r.LocalIP, 672 RemoteIp: r.RemoteIP, // TODO(jba): attempt to parse http.Request.RemoteAddr? 673 Referer: r.Request.Referer(), 674 CacheHit: r.CacheHit, 675 CacheValidatedWithOriginServer: r.CacheValidatedWithOriginServer, 676 } 677 if r.Latency != 0 { 678 pb.Latency = ptypes.DurationProto(r.Latency) 679 } 680 return pb 681} 682 683// fixUTF8 is a helper that fixes an invalid UTF-8 string by replacing 684// invalid UTF-8 runes with the Unicode replacement character (U+FFFD). 685// See Issue https://github.com/googleapis/google-cloud-go/issues/1383. 686func fixUTF8(s string) string { 687 if utf8.ValidString(s) { 688 return s 689 } 690 691 // Otherwise time to build the sequence. 692 buf := new(bytes.Buffer) 693 buf.Grow(len(s)) 694 for _, r := range s { 695 if utf8.ValidRune(r) { 696 buf.WriteRune(r) 697 } else { 698 buf.WriteRune('\uFFFD') 699 } 700 } 701 return buf.String() 702} 703 704// toProtoStruct converts v, which must marshal into a JSON object, 705// into a Google Struct proto. 706func toProtoStruct(v interface{}) (*structpb.Struct, error) { 707 // Fast path: if v is already a *structpb.Struct, nothing to do. 708 if s, ok := v.(*structpb.Struct); ok { 709 return s, nil 710 } 711 // v is a Go value that supports JSON marshalling. We want a Struct 712 // protobuf. Some day we may have a more direct way to get there, but right 713 // now the only way is to marshal the Go value to JSON, unmarshal into a 714 // map, and then build the Struct proto from the map. 715 var jb []byte 716 var err error 717 if raw, ok := v.(json.RawMessage); ok { // needed for Go 1.7 and below 718 jb = []byte(raw) 719 } else { 720 jb, err = json.Marshal(v) 721 if err != nil { 722 return nil, fmt.Errorf("logging: json.Marshal: %v", err) 723 } 724 } 725 var m map[string]interface{} 726 err = json.Unmarshal(jb, &m) 727 if err != nil { 728 return nil, fmt.Errorf("logging: json.Unmarshal: %v", err) 729 } 730 return jsonMapToProtoStruct(m), nil 731} 732 733func jsonMapToProtoStruct(m map[string]interface{}) *structpb.Struct { 734 fields := map[string]*structpb.Value{} 735 for k, v := range m { 736 fields[k] = jsonValueToStructValue(v) 737 } 738 return &structpb.Struct{Fields: fields} 739} 740 741func jsonValueToStructValue(v interface{}) *structpb.Value { 742 switch x := v.(type) { 743 case bool: 744 return &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: x}} 745 case float64: 746 return &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: x}} 747 case string: 748 return &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: x}} 749 case nil: 750 return &structpb.Value{Kind: &structpb.Value_NullValue{}} 751 case map[string]interface{}: 752 return &structpb.Value{Kind: &structpb.Value_StructValue{StructValue: jsonMapToProtoStruct(x)}} 753 case []interface{}: 754 var vals []*structpb.Value 755 for _, e := range x { 756 vals = append(vals, jsonValueToStructValue(e)) 757 } 758 return &structpb.Value{Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: vals}}} 759 default: 760 panic(fmt.Sprintf("bad type %T for JSON value", v)) 761 } 762} 763 764// LogSync logs the Entry synchronously without any buffering. Because LogSync is slow 765// and will block, it is intended primarily for debugging or critical errors. 766// Prefer Log for most uses. 767// TODO(jba): come up with a better name (LogNow?) or eliminate. 768func (l *Logger) LogSync(ctx context.Context, e Entry) error { 769 ent, err := l.toLogEntry(e) 770 if err != nil { 771 return err 772 } 773 _, err = l.client.client.WriteLogEntries(ctx, &logpb.WriteLogEntriesRequest{ 774 LogName: l.logName, 775 Resource: l.commonResource, 776 Labels: l.commonLabels, 777 Entries: []*logpb.LogEntry{ent}, 778 }) 779 return err 780} 781 782// Log buffers the Entry for output to the logging service. It never blocks. 783func (l *Logger) Log(e Entry) { 784 ent, err := l.toLogEntry(e) 785 if err != nil { 786 l.client.error(err) 787 return 788 } 789 if err := l.bundler.Add(ent, proto.Size(ent)); err != nil { 790 l.client.error(err) 791 } 792} 793 794// Flush blocks until all currently buffered log entries are sent. 795// 796// If any errors occurred since the last call to Flush from any Logger, or the 797// creation of the client if this is the first call, then Flush returns a non-nil 798// error with summary information about the errors. This information is unlikely to 799// be actionable. For more accurate error reporting, set Client.OnError. 800func (l *Logger) Flush() error { 801 l.bundler.Flush() 802 return l.client.extractErrorInfo() 803} 804 805func (l *Logger) writeLogEntries(entries []*logpb.LogEntry) { 806 req := &logpb.WriteLogEntriesRequest{ 807 LogName: l.logName, 808 Resource: l.commonResource, 809 Labels: l.commonLabels, 810 Entries: entries, 811 } 812 ctx, afterCall := l.ctxFunc() 813 ctx, cancel := context.WithTimeout(ctx, defaultWriteTimeout) 814 defer cancel() 815 _, err := l.client.client.WriteLogEntries(ctx, req) 816 if err != nil { 817 l.client.error(err) 818 } 819 if afterCall != nil { 820 afterCall() 821 } 822} 823 824// StandardLogger returns a *log.Logger for the provided severity. 825// 826// This method is cheap. A single log.Logger is pre-allocated for each 827// severity level in each Logger. Callers may mutate the returned log.Logger 828// (for example by calling SetFlags or SetPrefix). 829func (l *Logger) StandardLogger(s Severity) *log.Logger { return l.stdLoggers[s] } 830 831func (l *Logger) toLogEntry(e Entry) (*logpb.LogEntry, error) { 832 if e.LogName != "" { 833 return nil, errors.New("logging: Entry.LogName should be not be set when writing") 834 } 835 t := e.Timestamp 836 if t.IsZero() { 837 t = now() 838 } 839 ts, err := ptypes.TimestampProto(t) 840 if err != nil { 841 return nil, err 842 } 843 if e.Trace == "" && e.HTTPRequest != nil && e.HTTPRequest.Request != nil { 844 traceHeader := e.HTTPRequest.Request.Header.Get("X-Cloud-Trace-Context") 845 if traceHeader != "" { 846 // Set to a relative resource name, as described at 847 // https://cloud.google.com/appengine/docs/flexible/go/writing-application-logs. 848 e.Trace = fmt.Sprintf("%s/traces/%s", l.client.parent, traceHeader) 849 } 850 } 851 ent := &logpb.LogEntry{ 852 Timestamp: ts, 853 Severity: logtypepb.LogSeverity(e.Severity), 854 InsertId: e.InsertID, 855 HttpRequest: fromHTTPRequest(e.HTTPRequest), 856 Operation: e.Operation, 857 Labels: e.Labels, 858 Trace: e.Trace, 859 SpanId: e.SpanID, 860 Resource: e.Resource, 861 SourceLocation: e.SourceLocation, 862 } 863 switch p := e.Payload.(type) { 864 case string: 865 ent.Payload = &logpb.LogEntry_TextPayload{TextPayload: p} 866 default: 867 s, err := toProtoStruct(p) 868 if err != nil { 869 return nil, err 870 } 871 ent.Payload = &logpb.LogEntry_JsonPayload{JsonPayload: s} 872 } 873 return ent, nil 874} 875