From 533ecbb19a2833e667ad139a6604fd40dfb43cdc Mon Sep 17 00:00:00 2001 From: Francis Tang <96877163+tang-fh@users.noreply.github.com> Date: Thu, 26 Jan 2023 17:42:39 +0000 Subject: [PATCH] feat(logging): Add (*Logger). StandardLoggerFromTemplate() method. (#7261) --- logging/examples_test.go | 17 ++++++ logging/logging.go | 37 ++++++++---- logging/logging_test.go | 95 ++++++++++++++++++++++++++++++ logging/logging_unexported_test.go | 6 ++ 4 files changed, 144 insertions(+), 11 deletions(-) diff --git a/logging/examples_test.go b/logging/examples_test.go index 61fdb3921cbd..cc3caec7114c 100644 --- a/logging/examples_test.go +++ b/logging/examples_test.go @@ -203,6 +203,23 @@ func ExampleLogger_StandardLogger() { slg.Println("an informative message") } +func ExampleLogger_StandardLoggerFromTemplate() { + ctx := context.Background() + client, err := logging.NewClient(ctx, "my-project") + if err != nil { + // TODO: Handle error. + } + lg := client.Logger("my-log") + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + slg := lg.StandardLoggerFromTemplate(&logging.Entry{ + Severity: logging.Info, + HTTPRequest: &logging.HTTPRequest{Request: r}, + }) + slg.Println("Before hello world") + fmt.Fprintf(w, "Hello world!\n") + }) +} + func ExampleParseSeverity() { sev := logging.ParseSeverity("ALERT") fmt.Println(sev) diff --git a/logging/logging.go b/logging/logging.go index 4c478c98f7b3..02b2231c3c87 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -96,7 +96,8 @@ var ( ErrRedirectProtoPayloadNotSupported = errors.New("printEntryToStdout: cannot find valid payload") // For testing: - now = time.Now + now = time.Now + toLogEntryInternal = toLogEntryInternalImpl // ErrOverflow signals that the number of buffered entries for a Logger // exceeds its BufferLimit. @@ -287,7 +288,8 @@ func (c *Client) Logger(logID string, opts ...LoggerOption) *Logger { } l.stdLoggers = map[Severity]*log.Logger{} for s := range severityName { - l.stdLoggers[s] = log.New(severityWriter{l, s}, "", 0) + e := Entry{Severity: s} + l.stdLoggers[s] = log.New(templateEntryWriter{l, &e}, "", 0) } c.loggers.Add(1) @@ -301,16 +303,15 @@ func (c *Client) Logger(logID string, opts ...LoggerOption) *Logger { return l } -type severityWriter struct { - l *Logger - s Severity +type templateEntryWriter struct { + l *Logger + template *Entry } -func (w severityWriter) Write(p []byte) (n int, err error) { - w.l.Log(Entry{ - Severity: w.s, - Payload: string(p), - }) +func (w templateEntryWriter) Write(p []byte) (n int, err error) { + e := *w.template + e.Payload = string(p) + w.l.Log(e) return len(p), nil } @@ -721,6 +722,20 @@ func (l *Logger) writeLogEntries(entries []*logpb.LogEntry) { // (for example by calling SetFlags or SetPrefix). func (l *Logger) StandardLogger(s Severity) *log.Logger { return l.stdLoggers[s] } +// StandardLoggerFromTemplate returns a Go Standard Logging API *log.Logger. +// +// The returned logger emits logs using logging.(*Logger).Log() with an entry +// constructed from the provided template Entry struct. +// +// The caller is responsible for ensuring that the template Entry struct +// does not change during the the lifetime of the returned *log.Logger. +// +// Prefer (*Logger).StandardLogger() which is more efficient if the template +// only sets Severity. +func (l *Logger) StandardLoggerFromTemplate(template *Entry) *log.Logger { + return log.New(templateEntryWriter{l, template}, "", 0) +} + func populateTraceInfo(e *Entry, req *http.Request) bool { if req == nil { if e.HTTPRequest != nil && e.HTTPRequest.Request != nil { @@ -834,7 +849,7 @@ func (l *Logger) ToLogEntry(e Entry, parent string) (*logpb.LogEntry, error) { return toLogEntryInternal(e, l, parent, 1) } -func toLogEntryInternal(e Entry, l *Logger, parent string, skipLevels int) (*logpb.LogEntry, error) { +func toLogEntryInternalImpl(e Entry, l *Logger, parent string, skipLevels int) (*logpb.LogEntry, error) { if e.LogName != "" { return nil, errors.New("logging: Entry.LogName should be not be set when writing") } diff --git a/logging/logging_test.go b/logging/logging_test.go index b48f3aa7ab61..ad38096390ab 100644 --- a/logging/logging_test.go +++ b/logging/logging_test.go @@ -28,6 +28,7 @@ import ( "net/http" "net/url" "os" + "reflect" "runtime" "strings" "sync" @@ -717,6 +718,100 @@ func TestStandardLogger(t *testing.T) { } } +func TestStandardLoggerFromTemplate(t *testing.T) { + tests := []struct { + name string + template logging.Entry + message string + want logging.Entry + }{ + { + name: "severity only", + template: logging.Entry{ + Severity: logging.Error, + }, + message: "log message", + want: logging.Entry{ + Severity: logging.Error, + Payload: "log message\n", + }, + }, + { + name: "severity and trace", + template: logging.Entry{ + Severity: logging.Info, + Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", + }, + message: "log message", + want: logging.Entry{ + Severity: logging.Info, + Payload: "log message\n", + Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", + }, + }, + { + name: "severity and http request", + template: logging.Entry{ + Severity: logging.Info, + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + Method: "GET", + Host: "example.com", + }, + Status: 200, + }, + }, + message: "log message", + want: logging.Entry{ + Severity: logging.Info, + Payload: "log message\n", + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + Method: "GET", + Host: "example.com", + }, + Status: 200, + }, + }, + }, + { + name: "payload in template is ignored", + template: logging.Entry{ + Severity: logging.Info, + Payload: "this should not be set in the template", + Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", + }, + message: "log message", + want: logging.Entry{ + Severity: logging.Info, + Payload: "log message\n", + Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", + }, + }, + } + lg := client.Logger(testLogID) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mock := func(got logging.Entry, l *logging.Logger, parent string, skipLevels int) (*logpb.LogEntry, error) { + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Emitted Entry incorrect. Expected %v got %v", tc.want, got) + } + // Return value is not interesting + return &logpb.LogEntry{}, nil + } + + f := logging.SetToLogEntryInternal(mock) + defer func() { logging.SetToLogEntryInternal(f) }() + + slg := lg.StandardLoggerFromTemplate(&tc.template) + slg.Print(tc.message) + if err := lg.Flush(); err != nil { + t.Fatal(err) + } + }) + } +} + func TestSeverity(t *testing.T) { if got, want := logging.Info.String(), "Info"; got != want { t.Errorf("got %q, want %q", got, want) diff --git a/logging/logging_unexported_test.go b/logging/logging_unexported_test.go index eb0b91c85cd4..bce4b2c5c78c 100644 --- a/logging/logging_unexported_test.go +++ b/logging/logging_unexported_test.go @@ -24,6 +24,7 @@ import ( "time" "cloud.google.com/go/internal/testutil" + logpb "cloud.google.com/go/logging/apiv2/loggingpb" "github.com/golang/protobuf/proto" durpb "github.com/golang/protobuf/ptypes/duration" structpb "github.com/golang/protobuf/ptypes/struct" @@ -355,3 +356,8 @@ func SetNow(f func() time.Time) func() time.Time { now, f = f, now return f } + +func SetToLogEntryInternal(f func(Entry, *Logger, string, int) (*logpb.LogEntry, error)) func(Entry, *Logger, string, int) (*logpb.LogEntry, error) { + toLogEntryInternal, f = f, toLogEntryInternal + return f +}