diff --git a/logging/logging.go b/logging/logging.go index b845561cf24f..1528a69d4e5b 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -803,7 +803,7 @@ func jsonValueToStructValue(v interface{}) *structpb.Value { // and will block, it is intended primarily for debugging or critical errors. // Prefer Log for most uses. func (l *Logger) LogSync(ctx context.Context, e Entry) error { - ent, err := l.toLogEntry(e) + ent, err := l.ToLogEntry(e) if err != nil { return err } @@ -818,7 +818,7 @@ func (l *Logger) LogSync(ctx context.Context, e Entry) error { // Log buffers the Entry for output to the logging service. It never blocks. func (l *Logger) Log(e Entry) { - ent, err := l.toLogEntry(e) + ent, err := l.ToLogEntry(e) if err != nil { l.client.error(err) return @@ -894,7 +894,8 @@ func deconstructXCloudTraceContext(s string) (traceID, spanID string, traceSampl return } -func (l *Logger) toLogEntry(e Entry) (*logpb.LogEntry, error) { +// ToLogEntry takes an Entry structure and converts it to the LogEntry proto. +func (l *Logger) ToLogEntry(e Entry) (*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 f4386bedddd0..dfa6937aaac8 100644 --- a/logging/logging_test.go +++ b/logging/logging_test.go @@ -23,6 +23,8 @@ import ( "fmt" "log" "math/rand" + "net/http" + "net/url" "os" "strings" "sync" @@ -36,9 +38,12 @@ import ( "cloud.google.com/go/logging" ltesting "cloud.google.com/go/logging/internal/testing" "cloud.google.com/go/logging/logadmin" + "github.com/golang/protobuf/proto" + structpb "github.com/golang/protobuf/ptypes/struct" gax "github.com/googleapis/gax-go/v2" "golang.org/x/oauth2" "google.golang.org/api/iterator" + logpb "google.golang.org/api/logging/v2" "google.golang.org/api/option" mrpb "google.golang.org/genproto/googleapis/api/monitoredres" "google.golang.org/grpc" @@ -625,3 +630,202 @@ func TestSeverityUnmarshal(t *testing.T) { t.Fatalf("Severity: got %v, want %v", entry.Severity, logging.Error) } } + +func TestToLogEntryPayload(t *testing.T) { + lg := client.Logger(testLogID) + for _, test := range []struct { + in interface{} + wantText string + wantStruct *structpb.Struct + }{ + { + in: "string", + wantText: "string", + }, + { + in: map[string]interface{}{"a": 1, "b": true}, + wantStruct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, + "b": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, + }, + }, + }, + { + in: json.RawMessage([]byte(`{"a": 1, "b": true}`)), + wantStruct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, + "b": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, + }, + }, + }, + } { + e, err := lg.ToLogEntry(logging.Entry{Payload: test.in}) + if err != nil { + t.Fatalf("%+v: %v", test.in, err) + } + if test.wantStruct != nil { + got := e.GetJsonPayload() + if !proto.Equal(got, test.wantStruct) { + t.Errorf("%+v: got %s, want %s", test.in, got, test.wantStruct) + } + } else { + got := e.GetTextPayload() + if got != test.wantText { + t.Errorf("%+v: got %s, want %s", test.in, got, test.wantText) + } + } + } +} + +func TestToLogEntryTrace(t *testing.T) { + c, a := newClients(ctx, "P") + defer c.Close() + defer a.Close() + logger := c.Logger("") + + // Verify that we get the trace from the HTTP request if it isn't + // provided by the caller. + u := &url.URL{Scheme: "http"} + + tests := []struct { + name string + in logging.Entry + want logpb.LogEntry + }{ + {"BlankLogEntry", logging.Entry{}, logpb.LogEntry{}}, + {"Already set Trace", logging.Entry{Trace: "t1"}, logpb.LogEntry{Trace: "t1"}}, + { + "No X-Trace-Context header", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{URL: u, Header: http.Header{"foo": {"bar"}}}, + }, + }, + logpb.LogEntry{}, + }, + { + "X-Trace-Context header with all fields", + logging.Entry{ + TraceSampled: false, + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/000000000000004a;o=1"}}, + }, + }, + }, + logpb.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true}, + }, + { + "X-Trace-Context header with all fields; TraceSampled explicitly set", + logging.Entry{ + TraceSampled: true, + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/000000000000004a;o=0"}}, + }, + }, + }, + logpb.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true}, + }, + { + "X-Trace-Context header with all fields; TraceSampled from Header", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/000000000000004a;o=1"}}, + }, + }, + }, + logpb.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true}, + }, + { + "X-Trace-Context header with blank trace", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"/0;o=1"}}, + }, + }, + }, + logpb.LogEntry{TraceSampled: true}, + }, + { + "X-Trace-Context header with blank span", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/;o=0"}}, + }, + }, + }, + logpb.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000"}, + }, + { + "X-Trace-Context header with missing traceSampled aka ?o=*", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/0"}}, + }, + }, + }, + logpb.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000"}, + }, + { + "X-Trace-Context header with all blank fields", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {""}}, + }, + }, + }, + logpb.LogEntry{}, + }, + { + "Invalid X-Trace-Context header but already set TraceID", + logging.Entry{ + HTTPRequest: &logging.HTTPRequest{ + Request: &http.Request{ + URL: u, + Header: http.Header{"X-Cloud-Trace-Context": {"t3"}}, + }, + }, + Trace: "t4", + }, + logpb.LogEntry{Trace: "t4"}, + }, + { + "Already set TraceID and SpanID", + logging.Entry{Trace: "t1", SpanID: "007"}, + logpb.LogEntry{Trace: "t1", SpanId: "007"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + e, err := logger.ToLogEntry(test.in) + if err != nil { + t.Fatalf("Unexpected error:: %+v: %v", test.in, err) + } + if got := e.Trace; got != test.want.Trace { + t.Errorf("TraceId: %+v: got %q, want %q", test.in, got, test.want.Trace) + } + if got := e.SpanId; got != test.want.SpanId { + t.Errorf("SpanId: %+v: got %q, want %q", test.in, got, test.want.SpanId) + } + if got := e.TraceSampled; got != test.want.TraceSampled { + t.Errorf("TraceSampled: %+v: got %t, want %t", test.in, got, test.want.TraceSampled) + } + }) + } +} diff --git a/logging/logging_unexported_test.go b/logging/logging_unexported_test.go index a6413b8bc682..d67646ac18b7 100644 --- a/logging/logging_unexported_test.go +++ b/logging/logging_unexported_test.go @@ -17,7 +17,6 @@ package logging import ( - "encoding/json" "net/http" "net/url" "testing" @@ -27,7 +26,6 @@ import ( "github.com/golang/protobuf/proto" durpb "github.com/golang/protobuf/ptypes/duration" structpb "github.com/golang/protobuf/ptypes/struct" - "google.golang.org/api/logging/v2" "google.golang.org/api/support/bundler" mrpb "google.golang.org/genproto/googleapis/api/monitoredres" logtypepb "google.golang.org/genproto/googleapis/logging/type" @@ -180,201 +178,6 @@ func TestToProtoStruct(t *testing.T) { } } -func TestToLogEntryPayload(t *testing.T) { - var logger Logger - for _, test := range []struct { - in interface{} - wantText string - wantStruct *structpb.Struct - }{ - { - in: "string", - wantText: "string", - }, - { - in: map[string]interface{}{"a": 1, "b": true}, - wantStruct: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, - "b": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, - }, - }, - }, - { - in: json.RawMessage([]byte(`{"a": 1, "b": true}`)), - wantStruct: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, - "b": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, - }, - }, - }, - } { - e, err := logger.toLogEntry(Entry{Payload: test.in}) - if err != nil { - t.Fatalf("%+v: %v", test.in, err) - } - if test.wantStruct != nil { - got := e.GetJsonPayload() - if !proto.Equal(got, test.wantStruct) { - t.Errorf("%+v: got %s, want %s", test.in, got, test.wantStruct) - } - } else { - got := e.GetTextPayload() - if got != test.wantText { - t.Errorf("%+v: got %s, want %s", test.in, got, test.wantText) - } - } - } -} - -func TestToLogEntryTrace(t *testing.T) { - logger := &Logger{client: &Client{parent: "projects/P"}} - // Verify that we get the trace from the HTTP request if it isn't - // provided by the caller. - u := &url.URL{Scheme: "http"} - - tests := []struct { - name string - in Entry - want logging.LogEntry - }{ - {"BlankLogEntry", Entry{}, logging.LogEntry{}}, - {"Already set Trace", Entry{Trace: "t1"}, logging.LogEntry{Trace: "t1"}}, - { - "No X-Trace-Context header", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{URL: u, Header: http.Header{"foo": {"bar"}}}, - }, - }, - logging.LogEntry{}, - }, - { - "X-Trace-Context header with all fields", - Entry{ - TraceSampled: false, - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/000000000000004a;o=1"}}, - }, - }, - }, - logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true}, - }, - { - "X-Trace-Context header with all fields; TraceSampled explicitly set", - Entry{ - TraceSampled: true, - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/000000000000004a;o=0"}}, - }, - }, - }, - logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true}, - }, - { - "X-Trace-Context header with all fields; TraceSampled from Header", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/000000000000004a;o=1"}}, - }, - }, - }, - logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true}, - }, - { - "X-Trace-Context header with blank trace", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"/0;o=1"}}, - }, - }, - }, - logging.LogEntry{TraceSampled: true}, - }, - { - "X-Trace-Context header with blank span", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/;o=0"}}, - }, - }, - }, - logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000"}, - }, - { - "X-Trace-Context header with missing traceSampled aka ?o=*", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/0"}}, - }, - }, - }, - logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000"}, - }, - { - "X-Trace-Context header with all blank fields", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {""}}, - }, - }, - }, - logging.LogEntry{}, - }, - { - "Invalid X-Trace-Context header but already set TraceID", - Entry{ - HTTPRequest: &HTTPRequest{ - Request: &http.Request{ - URL: u, - Header: http.Header{"X-Cloud-Trace-Context": {"t3"}}, - }, - }, - Trace: "t4", - }, - logging.LogEntry{Trace: "t4"}, - }, - { - "Already set TraceID and SpanID", - Entry{Trace: "t1", SpanID: "007"}, - logging.LogEntry{Trace: "t1", SpanId: "007"}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - e, err := logger.toLogEntry(test.in) - if err != nil { - t.Fatalf("Unexpected error:: %+v: %v", test.in, err) - } - if got := e.Trace; got != test.want.Trace { - t.Errorf("TraceId: %+v: got %q, want %q", test.in, got, test.want.Trace) - } - if got := e.SpanId; got != test.want.SpanId { - t.Errorf("SpanId: %+v: got %q, want %q", test.in, got, test.want.SpanId) - } - if got := e.TraceSampled; got != test.want.TraceSampled { - t.Errorf("TraceSampled: %+v: got %t, want %t", test.in, got, test.want.TraceSampled) - } - }) - } -} - func TestFromHTTPRequest(t *testing.T) { // The test URL has invalid UTF-8 runes. const testURL = "http://example.com/path?q=1&name=\xfe\xff"