Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(logging): Add (*Logger). StandardLoggerFromTemplate() method. #7261

Merged
merged 11 commits into from
Jan 26, 2023
17 changes: 17 additions & 0 deletions logging/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 26 additions & 11 deletions logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down
95 changes: 95 additions & 0 deletions logging/logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"net/http"
"net/url"
"os"
"reflect"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions logging/logging_unexported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}