blob: d3f3e9b2a60d3a8e4facb3860f8184107824d6e0 [file] [log] [blame]
package cloudlogging
import (
"context"
"fmt"
"os"
"strings"
"time"
"cloud.google.com/go/logging"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog/sklogimpl"
"golang.org/x/oauth2"
"google.golang.org/api/option"
)
// cloudLogger is a sklogimpl.Logger which uses the non-deprecated
// cloud.google.com/go/logging package.
type cloudLogger struct {
logger *logging.Logger
}
// New returns a sklogimpl.Logger instance. Writes the log URL to stdout.
func New(ctx context.Context, projectID, logID string, ts oauth2.TokenSource, labels map[string]string) (*cloudLogger, error) {
logsClient, err := logging.NewClient(ctx, projectID, option.WithTokenSource(ts))
if err != nil {
return nil, err
}
logger := logsClient.Logger(logID, logging.CommonLabels(labels))
fmt.Printf("Connected Cloud Logging; logs can be found here:\n\thttps://console.cloud.google.com/logs/viewer?project=%s&advancedFilter=logName%%3D%%22projects%%2F%s%%2Flogs%%2F%s%%22\n", projectID, projectID, logID)
return &cloudLogger{
logger: logger,
}, nil
}
// Return the logging.Logger.
func (cl *cloudLogger) Logger() *logging.Logger {
return cl.logger
}
func convertSeverity(severity sklogimpl.Severity) logging.Severity {
switch severity {
case sklogimpl.Debug:
return logging.Debug
case sklogimpl.Info:
return logging.Info
case sklogimpl.Warning:
return logging.Warning
case sklogimpl.Error:
return logging.Error
case sklogimpl.Fatal:
return logging.Alert
default:
return logging.Default
}
}
// Log implements sklogimpl.Logger.
func (cl *cloudLogger) Log(depth int, severity sklogimpl.Severity, format string, args ...interface{}) {
// The following is copied from previous impl; we should consider replacing this with structured
// logs.
// See doc on sklog.Logger interface.
var stack map[string]string
prettyPayload := strings.Builder{}
if severity == sklogimpl.Fatal {
stackDepth := 2 + depth
stacks := skerr.CallStack(5, stackDepth)
_, _ = prettyPayload.WriteString(stacks[0].String())
_ = prettyPayload.WriteByte(' ')
stack = map[string]string{
"stacktrace_0": stacks[0].String(),
"stacktrace_1": stacks[1].String(),
"stacktrace_2": stacks[2].String(),
"stacktrace_3": stacks[3].String(),
"stacktrace_4": stacks[4].String(),
}
}
if len(format) == 0 {
_, _ = fmt.Fprint(&prettyPayload, args...)
} else {
_, _ = fmt.Fprintf(&prettyPayload, format, args...)
}
cl.logger.Log(logging.Entry{
Payload: prettyPayload.String(),
Timestamp: time.Now(),
Severity: convertSeverity(severity),
Labels: stack,
})
if severity == sklogimpl.Fatal {
cl.Flush()
os.Exit(255)
}
}
// Flush implements sklogimpl.Logger.
func (cl *cloudLogger) Flush() {
if err := cl.logger.Flush(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to flush logging.Logger: %s", err)
}
}
var _ sklogimpl.Logger = (*cloudLogger)(nil)