123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- // Copyright 2016 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // API/gRPC features intentionally missing from this client:
- // - You cannot have the server pick the time of the entry. This client
- // always sends a time.
- // - There is no way to provide a protocol buffer payload.
- // - No support for the "partial success" feature when writing log entries.
- // TODO(jba): test whether forward-slash characters in the log ID must be URL-encoded.
- // These features are missing now, but will likely be added:
- // - There is no way to specify CallOptions.
- package logging
- import (
- "encoding/json"
- "errors"
- "fmt"
- "log"
- "math"
- "net/http"
- "strconv"
- "strings"
- "sync"
- "time"
- vkit "cloud.google.com/go/logging/apiv2"
- "cloud.google.com/go/logging/internal"
- "github.com/golang/protobuf/proto"
- "github.com/golang/protobuf/ptypes"
- structpb "github.com/golang/protobuf/ptypes/struct"
- tspb "github.com/golang/protobuf/ptypes/timestamp"
- "golang.org/x/net/context"
- "google.golang.org/api/option"
- "google.golang.org/api/support/bundler"
- mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
- logtypepb "google.golang.org/genproto/googleapis/logging/type"
- logpb "google.golang.org/genproto/googleapis/logging/v2"
- )
- const (
- // Scope for reading from the logging service.
- ReadScope = "https://www.googleapis.com/auth/logging.read"
- // Scope for writing to the logging service.
- WriteScope = "https://www.googleapis.com/auth/logging.write"
- // Scope for administrative actions on the logging service.
- AdminScope = "https://www.googleapis.com/auth/logging.admin"
- )
- const (
- // defaultErrorCapacity is the capacity of the channel used to deliver
- // errors to the OnError function.
- defaultErrorCapacity = 10
- // DefaultDelayThreshold is the default value for the DelayThreshold LoggerOption.
- DefaultDelayThreshold = time.Second
- // DefaultEntryCountThreshold is the default value for the EntryCountThreshold LoggerOption.
- DefaultEntryCountThreshold = 1000
- // DefaultEntryByteThreshold is the default value for the EntryByteThreshold LoggerOption.
- DefaultEntryByteThreshold = 1 << 20 // 1MiB
- // DefaultBufferedByteLimit is the default value for the BufferedByteLimit LoggerOption.
- DefaultBufferedByteLimit = 1 << 30 // 1GiB
- )
- // For testing:
- var now = time.Now
- // ErrOverflow signals that the number of buffered entries for a Logger
- // exceeds its BufferLimit.
- var ErrOverflow = errors.New("logging: log entry overflowed buffer limits")
- // Client is a Logging client. A Client is associated with a single Cloud project.
- type Client struct {
- client *vkit.Client // client for the logging service
- projectID string
- errc chan error // should be buffered to minimize dropped errors
- donec chan struct{} // closed on Client.Close to close Logger bundlers
- loggers sync.WaitGroup // so we can wait for loggers to close
- closed bool
- // OnError is called when an error occurs in a call to Log or Flush. The
- // error may be due to an invalid Entry, an overflow because BufferLimit
- // was reached (in which case the error will be ErrOverflow) or an error
- // communicating with the logging service. OnError is called with errors
- // from all Loggers. It is never called concurrently. OnError is expected
- // to return quickly; if errors occur while OnError is running, some may
- // not be reported. The default behavior is to call log.Printf.
- //
- // This field should be set only once, before any method of Client is called.
- OnError func(err error)
- }
- // NewClient returns a new logging client associated with the provided project ID.
- //
- // By default NewClient uses WriteScope. To use a different scope, call
- // NewClient using a WithScopes option (see https://godoc.org/google.golang.org/api/option#WithScopes).
- func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
- // Check for '/' in project ID to reserve the ability to support various owning resources,
- // in the form "{Collection}/{Name}", for instance "organizations/my-org".
- if strings.ContainsRune(projectID, '/') {
- return nil, errors.New("logging: project ID contains '/'")
- }
- opts = append([]option.ClientOption{
- option.WithEndpoint(internal.ProdAddr),
- option.WithScopes(WriteScope),
- }, opts...)
- c, err := vkit.NewClient(ctx, opts...)
- if err != nil {
- return nil, err
- }
- c.SetGoogleClientInfo("logging", internal.Version)
- client := &Client{
- client: c,
- projectID: projectID,
- errc: make(chan error, defaultErrorCapacity), // create a small buffer for errors
- donec: make(chan struct{}),
- OnError: func(e error) { log.Printf("logging client: %v", e) },
- }
- // Call the user's function synchronously, to make life easier for them.
- go func() {
- for err := range client.errc {
- // This reference to OnError is memory-safe if the user sets OnError before
- // calling any client methods. The reference happens before the first read from
- // client.errc, which happens before the first write to client.errc, which
- // happens before any call, which happens before the user sets OnError.
- if fn := client.OnError; fn != nil {
- fn(err)
- } else {
- log.Printf("logging (project ID %q): %v", projectID, err)
- }
- }
- }()
- return client, nil
- }
- // parent returns the string used in many RPCs to denote the parent resource of the log.
- func (c *Client) parent() string {
- return "projects/" + c.projectID
- }
- var unixZeroTimestamp *tspb.Timestamp
- func init() {
- var err error
- unixZeroTimestamp, err = ptypes.TimestampProto(time.Unix(0, 0))
- if err != nil {
- panic(err)
- }
- }
- // Ping reports whether the client's connection to the logging service and the
- // authentication configuration are valid. To accomplish this, Ping writes a
- // log entry "ping" to a log named "ping".
- func (c *Client) Ping(ctx context.Context) error {
- ent := &logpb.LogEntry{
- Payload: &logpb.LogEntry_TextPayload{"ping"},
- Timestamp: unixZeroTimestamp, // Identical timestamps and insert IDs are both
- InsertId: "ping", // necessary for the service to dedup these entries.
- }
- _, err := c.client.WriteLogEntries(ctx, &logpb.WriteLogEntriesRequest{
- LogName: internal.LogPath(c.parent(), "ping"),
- Resource: &mrpb.MonitoredResource{Type: "global"},
- Entries: []*logpb.LogEntry{ent},
- })
- return err
- }
- // A Logger is used to write log messages to a single log. It can be configured
- // with a log ID, common monitored resource, and a set of common labels.
- type Logger struct {
- client *Client
- logName string // "projects/{projectID}/logs/{logID}"
- stdLoggers map[Severity]*log.Logger
- bundler *bundler.Bundler
- // Options
- commonResource *mrpb.MonitoredResource
- commonLabels map[string]string
- }
- // A LoggerOption is a configuration option for a Logger.
- type LoggerOption interface {
- set(*Logger)
- }
- // CommonResource sets the monitored resource associated with all log entries
- // written from a Logger. If not provided, a resource of type "global" is used.
- // This value can be overridden by setting an Entry's Resource field.
- func CommonResource(r *mrpb.MonitoredResource) LoggerOption { return commonResource{r} }
- type commonResource struct{ *mrpb.MonitoredResource }
- func (r commonResource) set(l *Logger) { l.commonResource = r.MonitoredResource }
- // CommonLabels are labels that apply to all log entries written from a Logger,
- // so that you don't have to repeat them in each log entry's Labels field. If
- // any of the log entries contains a (key, value) with the same key that is in
- // CommonLabels, then the entry's (key, value) overrides the one in
- // CommonLabels.
- func CommonLabels(m map[string]string) LoggerOption { return commonLabels(m) }
- type commonLabels map[string]string
- func (c commonLabels) set(l *Logger) { l.commonLabels = c }
- // DelayThreshold is the maximum amount of time that an entry should remain
- // buffered in memory before a call to the logging service is triggered. Larger
- // values of DelayThreshold will generally result in fewer calls to the logging
- // service, while increasing the risk that log entries will be lost if the
- // process crashes.
- // The default is DefaultDelayThreshold.
- func DelayThreshold(d time.Duration) LoggerOption { return delayThreshold(d) }
- type delayThreshold time.Duration
- func (d delayThreshold) set(l *Logger) { l.bundler.DelayThreshold = time.Duration(d) }
- // EntryCountThreshold is the maximum number of entries that will be buffered
- // in memory before a call to the logging service is triggered. Larger values
- // will generally result in fewer calls to the logging service, while
- // increasing both memory consumption and the risk that log entries will be
- // lost if the process crashes.
- // The default is DefaultEntryCountThreshold.
- func EntryCountThreshold(n int) LoggerOption { return entryCountThreshold(n) }
- type entryCountThreshold int
- func (e entryCountThreshold) set(l *Logger) { l.bundler.BundleCountThreshold = int(e) }
- // EntryByteThreshold is the maximum number of bytes of entries that will be
- // buffered in memory before a call to the logging service is triggered. See
- // EntryCountThreshold for a discussion of the tradeoffs involved in setting
- // this option.
- // The default is DefaultEntryByteThreshold.
- func EntryByteThreshold(n int) LoggerOption { return entryByteThreshold(n) }
- type entryByteThreshold int
- func (e entryByteThreshold) set(l *Logger) { l.bundler.BundleByteThreshold = int(e) }
- // EntryByteLimit is the maximum number of bytes of entries that will be sent
- // in a single call to the logging service. This option limits the size of a
- // single RPC payload, to account for network or service issues with large
- // RPCs. If EntryByteLimit is smaller than EntryByteThreshold, the latter has
- // no effect.
- // The default is zero, meaning there is no limit.
- func EntryByteLimit(n int) LoggerOption { return entryByteLimit(n) }
- type entryByteLimit int
- func (e entryByteLimit) set(l *Logger) { l.bundler.BundleByteLimit = int(e) }
- // BufferedByteLimit is the maximum number of bytes that the Logger will keep
- // in memory before returning ErrOverflow. This option limits the total memory
- // consumption of the Logger (but note that each Logger has its own, separate
- // limit). It is possible to reach BufferedByteLimit even if it is larger than
- // EntryByteThreshold or EntryByteLimit, because calls triggered by the latter
- // two options may be enqueued (and hence occupying memory) while new log
- // entries are being added.
- // The default is DefaultBufferedByteLimit.
- func BufferedByteLimit(n int) LoggerOption { return bufferedByteLimit(n) }
- type bufferedByteLimit int
- func (b bufferedByteLimit) set(l *Logger) { l.bundler.BufferedByteLimit = int(b) }
- // Logger returns a Logger that will write entries with the given log ID, such as
- // "syslog". A log ID must be less than 512 characters long and can only
- // include the following characters: upper and lower case alphanumeric
- // characters: [A-Za-z0-9]; and punctuation characters: forward-slash,
- // underscore, hyphen, and period.
- func (c *Client) Logger(logID string, opts ...LoggerOption) *Logger {
- l := &Logger{
- client: c,
- logName: internal.LogPath(c.parent(), logID),
- commonResource: &mrpb.MonitoredResource{Type: "global"},
- }
- // TODO(jba): determine the right context for the bundle handler.
- ctx := context.TODO()
- l.bundler = bundler.NewBundler(&logpb.LogEntry{}, func(entries interface{}) {
- l.writeLogEntries(ctx, entries.([]*logpb.LogEntry))
- })
- l.bundler.DelayThreshold = DefaultDelayThreshold
- l.bundler.BundleCountThreshold = DefaultEntryCountThreshold
- l.bundler.BundleByteThreshold = DefaultEntryByteThreshold
- l.bundler.BufferedByteLimit = DefaultBufferedByteLimit
- for _, opt := range opts {
- opt.set(l)
- }
- l.stdLoggers = map[Severity]*log.Logger{}
- for s := range severityName {
- l.stdLoggers[s] = log.New(severityWriter{l, s}, "", 0)
- }
- c.loggers.Add(1)
- go func() {
- defer c.loggers.Done()
- <-c.donec
- l.bundler.Close()
- }()
- return l
- }
- type severityWriter struct {
- l *Logger
- s Severity
- }
- func (w severityWriter) Write(p []byte) (n int, err error) {
- w.l.Log(Entry{
- Severity: w.s,
- Payload: string(p),
- })
- return len(p), nil
- }
- // Close closes the client.
- func (c *Client) Close() error {
- if c.closed {
- return nil
- }
- close(c.donec) // close Logger bundlers
- c.loggers.Wait() // wait for all bundlers to flush and close
- // Now there can be no more errors.
- close(c.errc) // terminate error goroutine
- // Return only the first error. Since all clients share an underlying connection,
- // Closes after the first always report a "connection is closing" error.
- err := c.client.Close()
- c.closed = true
- return err
- }
- // Severity is the severity of the event described in a log entry. These
- // guideline severity levels are ordered, with numerically smaller levels
- // treated as less severe than numerically larger levels.
- type Severity int
- const (
- // Default means the log entry has no assigned severity level.
- Default = Severity(logtypepb.LogSeverity_DEFAULT)
- // Debug means debug or trace information.
- Debug = Severity(logtypepb.LogSeverity_DEBUG)
- // Info means routine information, such as ongoing status or performance.
- Info = Severity(logtypepb.LogSeverity_INFO)
- // Notice means normal but significant events, such as start up, shut down, or configuration.
- Notice = Severity(logtypepb.LogSeverity_NOTICE)
- // Warning means events that might cause problems.
- Warning = Severity(logtypepb.LogSeverity_WARNING)
- // Error means events that are likely to cause problems.
- Error = Severity(logtypepb.LogSeverity_ERROR)
- // Critical means events that cause more severe problems or brief outages.
- Critical = Severity(logtypepb.LogSeverity_CRITICAL)
- // Alert means a person must take an action immediately.
- Alert = Severity(logtypepb.LogSeverity_ALERT)
- // Emergency means one or more systems are unusable.
- Emergency = Severity(logtypepb.LogSeverity_EMERGENCY)
- )
- var severityName = map[Severity]string{
- Default: "Default",
- Debug: "Debug",
- Info: "Info",
- Notice: "Notice",
- Warning: "Warning",
- Error: "Error",
- Critical: "Critical",
- Alert: "Alert",
- Emergency: "Emergency",
- }
- // String converts a severity level to a string.
- func (v Severity) String() string {
- // same as proto.EnumName
- s, ok := severityName[v]
- if ok {
- return s
- }
- return strconv.Itoa(int(v))
- }
- // ParseSeverity returns the Severity whose name equals s, ignoring case. It
- // returns Default if no Severity matches.
- func ParseSeverity(s string) Severity {
- sl := strings.ToLower(s)
- for sev, name := range severityName {
- if strings.ToLower(name) == sl {
- return sev
- }
- }
- return Default
- }
- // Entry is a log entry.
- // See https://cloud.google.com/logging/docs/view/logs_index for more about entries.
- type Entry struct {
- // Timestamp is the time of the entry. If zero, the current time is used.
- Timestamp time.Time
- // Severity is the entry's severity level.
- // The zero value is Default.
- Severity Severity
- // Payload must be either a string or something that
- // marshals via the encoding/json package to a JSON object
- // (and not any other type of JSON value).
- Payload interface{}
- // Labels optionally specifies key/value labels for the log entry.
- // The Logger.Log method takes ownership of this map. See Logger.CommonLabels
- // for more about labels.
- Labels map[string]string
- // InsertID is a unique ID for the log entry. If you provide this field,
- // the logging service considers other log entries in the same log with the
- // same ID as duplicates which can be removed. If omitted, the logging
- // service will generate a unique ID for this log entry. Note that because
- // this client retries RPCs automatically, it is possible (though unlikely)
- // that an Entry without an InsertID will be written more than once.
- InsertID string
- // HTTPRequest optionally specifies metadata about the HTTP request
- // associated with this log entry, if applicable. It is optional.
- HTTPRequest *HTTPRequest
- // Operation optionally provides information about an operation associated
- // with the log entry, if applicable.
- Operation *logpb.LogEntryOperation
- // LogName is the full log name, in the form
- // "projects/{ProjectID}/logs/{LogID}". It is set by the client when
- // reading entries. It is an error to set it when writing entries.
- LogName string
- // Resource is the monitored resource associated with the entry. It is set
- // by the client when reading entries. It is an error to set it when
- // writing entries.
- Resource *mrpb.MonitoredResource
- }
- // HTTPRequest contains an http.Request as well as additional
- // information about the request and its response.
- type HTTPRequest struct {
- // Request is the http.Request passed to the handler.
- Request *http.Request
- // RequestSize is the size of the HTTP request message in bytes, including
- // the request headers and the request body.
- RequestSize int64
- // Status is the response code indicating the status of the response.
- // Examples: 200, 404.
- Status int
- // ResponseSize is the size of the HTTP response message sent back to the client, in bytes,
- // including the response headers and the response body.
- ResponseSize int64
- // Latency is the request processing latency on the server, from the time the request was
- // received until the response was sent.
- Latency time.Duration
- // RemoteIP is the IP address (IPv4 or IPv6) of the client that issued the
- // HTTP request. Examples: "192.168.1.1", "FE80::0202:B3FF:FE1E:8329".
- RemoteIP string
- // CacheHit reports whether an entity was served from cache (with or without
- // validation).
- CacheHit bool
- // CacheValidatedWithOriginServer reports whether the response was
- // validated with the origin server before being served from cache. This
- // field is only meaningful if CacheHit is true.
- CacheValidatedWithOriginServer bool
- }
- func fromHTTPRequest(r *HTTPRequest) *logtypepb.HttpRequest {
- if r == nil {
- return nil
- }
- if r.Request == nil {
- panic("HTTPRequest must have a non-nil Request")
- }
- u := *r.Request.URL
- u.Fragment = ""
- return &logtypepb.HttpRequest{
- RequestMethod: r.Request.Method,
- RequestUrl: u.String(),
- RequestSize: r.RequestSize,
- Status: int32(r.Status),
- ResponseSize: r.ResponseSize,
- Latency: ptypes.DurationProto(r.Latency),
- UserAgent: r.Request.UserAgent(),
- RemoteIp: r.RemoteIP, // TODO(jba): attempt to parse http.Request.RemoteAddr?
- Referer: r.Request.Referer(),
- CacheHit: r.CacheHit,
- CacheValidatedWithOriginServer: r.CacheValidatedWithOriginServer,
- }
- }
- // toProtoStruct converts v, which must marshal into a JSON object,
- // into a Google Struct proto.
- func toProtoStruct(v interface{}) (*structpb.Struct, error) {
- // Fast path: if v is already a *structpb.Struct, nothing to do.
- if s, ok := v.(*structpb.Struct); ok {
- return s, nil
- }
- // v is a Go struct that supports JSON marshalling. We want a Struct
- // protobuf. Some day we may have a more direct way to get there, but right
- // now the only way is to marshal the Go struct to JSON, unmarshal into a
- // map, and then build the Struct proto from the map.
- jb, err := json.Marshal(v)
- if err != nil {
- return nil, fmt.Errorf("logging: json.Marshal: %v", err)
- }
- var m map[string]interface{}
- err = json.Unmarshal(jb, &m)
- if err != nil {
- return nil, fmt.Errorf("logging: json.Unmarshal: %v", err)
- }
- return jsonMapToProtoStruct(m), nil
- }
- func jsonMapToProtoStruct(m map[string]interface{}) *structpb.Struct {
- fields := map[string]*structpb.Value{}
- for k, v := range m {
- fields[k] = jsonValueToStructValue(v)
- }
- return &structpb.Struct{Fields: fields}
- }
- func jsonValueToStructValue(v interface{}) *structpb.Value {
- switch x := v.(type) {
- case bool:
- return &structpb.Value{Kind: &structpb.Value_BoolValue{x}}
- case float64:
- return &structpb.Value{Kind: &structpb.Value_NumberValue{x}}
- case string:
- return &structpb.Value{Kind: &structpb.Value_StringValue{x}}
- case nil:
- return &structpb.Value{Kind: &structpb.Value_NullValue{}}
- case map[string]interface{}:
- return &structpb.Value{Kind: &structpb.Value_StructValue{jsonMapToProtoStruct(x)}}
- case []interface{}:
- var vals []*structpb.Value
- for _, e := range x {
- vals = append(vals, jsonValueToStructValue(e))
- }
- return &structpb.Value{Kind: &structpb.Value_ListValue{&structpb.ListValue{vals}}}
- default:
- panic(fmt.Sprintf("bad type %T for JSON value", v))
- }
- }
- // LogSync logs the Entry synchronously without any buffering. Because LogSync is slow
- // and will block, it is intended primarily for debugging or critical errors.
- // Prefer Log for most uses.
- // TODO(jba): come up with a better name (LogNow?) or eliminate.
- func (l *Logger) LogSync(ctx context.Context, e Entry) error {
- ent, err := toLogEntry(e)
- if err != nil {
- return err
- }
- _, err = l.client.client.WriteLogEntries(ctx, &logpb.WriteLogEntriesRequest{
- LogName: l.logName,
- Resource: l.commonResource,
- Labels: l.commonLabels,
- Entries: []*logpb.LogEntry{ent},
- })
- return err
- }
- // Log buffers the Entry for output to the logging service. It never blocks.
- func (l *Logger) Log(e Entry) {
- ent, err := toLogEntry(e)
- if err != nil {
- l.error(err)
- return
- }
- if err := l.bundler.Add(ent, proto.Size(ent)); err != nil {
- l.error(err)
- }
- }
- // Flush blocks until all currently buffered log entries are sent.
- func (l *Logger) Flush() {
- l.bundler.Flush()
- }
- func (l *Logger) writeLogEntries(ctx context.Context, entries []*logpb.LogEntry) {
- req := &logpb.WriteLogEntriesRequest{
- LogName: l.logName,
- Resource: l.commonResource,
- Labels: l.commonLabels,
- Entries: entries,
- }
- _, err := l.client.client.WriteLogEntries(ctx, req)
- if err != nil {
- l.error(err)
- }
- }
- // error puts the error on the client's error channel
- // without blocking.
- func (l *Logger) error(err error) {
- select {
- case l.client.errc <- err:
- default:
- }
- }
- // StandardLogger returns a *log.Logger for the provided severity.
- //
- // This method is cheap. A single log.Logger is pre-allocated for each
- // severity level in each Logger. Callers may mutate the returned log.Logger
- // (for example by calling SetFlags or SetPrefix).
- func (l *Logger) StandardLogger(s Severity) *log.Logger { return l.stdLoggers[s] }
- func trunc32(i int) int32 {
- if i > math.MaxInt32 {
- i = math.MaxInt32
- }
- return int32(i)
- }
- func toLogEntry(e Entry) (*logpb.LogEntry, error) {
- if e.LogName != "" {
- return nil, errors.New("logging: Entry.LogName should be not be set when writing")
- }
- t := e.Timestamp
- if t.IsZero() {
- t = now()
- }
- ts, err := ptypes.TimestampProto(t)
- if err != nil {
- return nil, err
- }
- ent := &logpb.LogEntry{
- Timestamp: ts,
- Severity: logtypepb.LogSeverity(e.Severity),
- InsertId: e.InsertID,
- HttpRequest: fromHTTPRequest(e.HTTPRequest),
- Operation: e.Operation,
- Labels: e.Labels,
- }
- switch p := e.Payload.(type) {
- case string:
- ent.Payload = &logpb.LogEntry_TextPayload{p}
- default:
- s, err := toProtoStruct(p)
- if err != nil {
- return nil, err
- }
- ent.Payload = &logpb.LogEntry_JsonPayload{s}
- }
- return ent, nil
- }
|