Skip to content

Commit

Permalink
feat(logging): OpenTelemetry trace/span ID integration for Go logging…
Browse files Browse the repository at this point in the history
… library (#10030)

* feat(logging): OpenTelemetry trace/span ID integration for Go logging library

* Added system/integration tests

* added go.sum

---------

Co-authored-by: Cindy Peng <[email protected]>
  • Loading branch information
gkevinzheng and cindy-peng authored May 16, 2024
1 parent 215e0c8 commit c6711b8
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 2 deletions.
4 changes: 2 additions & 2 deletions logging/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ require (
github.com/google/go-cmp v0.6.0
github.com/googleapis/gax-go/v2 v2.12.4
go.opencensus.io v0.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
golang.org/x/oauth2 v0.20.0
google.golang.org/api v0.180.0
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda
Expand All @@ -35,8 +37,6 @@ require (
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import (
"time"
"unicode/utf8"

"go.opentelemetry.io/otel/trace"

vkit "cloud.google.com/go/logging/apiv2"
logpb "cloud.google.com/go/logging/apiv2/loggingpb"
"cloud.google.com/go/logging/internal"
Expand Down Expand Up @@ -813,6 +815,13 @@ func populateTraceInfo(e *Entry, req *http.Request) bool {
return false
}
}
otelSpanContext := trace.SpanContextFromContext(req.Context())
if otelSpanContext.IsValid() {
e.Trace = otelSpanContext.TraceID().String()
e.SpanID = otelSpanContext.SpanID().String()
e.TraceSampled = otelSpanContext.IsSampled()
return true
}
header := req.Header.Get("Traceparent")
if header != "" {
// do not use traceSampled flag defined by traceparent because
Expand Down
129 changes: 129 additions & 0 deletions logging/logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
gax "github.com/googleapis/gax-go/v2"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
"golang.org/x/oauth2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
Expand Down Expand Up @@ -609,6 +612,132 @@ func TestToLogEntry(t *testing.T) {
}
}

func TestToLogEntryOTelIntegration(t *testing.T) {
// Some slight modifications need to be done for testing ToLogEntry
// for the OpenTelemetry integration, so they are in a separate function.
u := &url.URL{Scheme: "http"}
tests := []struct {
name string
in logging.Entry
want *logpb.LogEntry // if want is nil, pull wants from spanContext
}{
{
name: "Using OpenTelemetry with a valid span",
in: logging.Entry{
HTTPRequest: &logging.HTTPRequest{
Request: &http.Request{
URL: u,
},
},
},
},
{
name: "Using OpenTelemetry only with a valid span + valid traceparent headers (precedence test)",
in: logging.Entry{
HTTPRequest: &logging.HTTPRequest{
Request: &http.Request{
URL: u,
Header: http.Header{
"Traceparent": {"00-105445aa7843bc8bf206b12000100012-000000000000004a-01"},
},
},
},
},
},
{
name: "Using OpenTelemetry only with a valid span + valid XCTC headers (precedence test)",
in: logging.Entry{
HTTPRequest: &logging.HTTPRequest{
Request: &http.Request{
URL: u,
Header: http.Header{
"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120000000/0000000000000bbb;o=1"},
},
},
},
},
},
{
name: "Using OpenTelemetry with a valid span + trace info set in Entry object",
in: logging.Entry{
HTTPRequest: &logging.HTTPRequest{
Request: &http.Request{
URL: u,
},
},
Trace: "abc",
SpanID: "def",
TraceSampled: false,
},
want: &logpb.LogEntry{
Trace: "abc",
SpanId: "def",
TraceSampled: false,
},
},
{
name: "Using OpenTelemetry without a request",
in: logging.Entry{},
want: &logpb.LogEntry{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var span trace.Span
ctx := context.Background()

// Set up an OTel SDK tracer if integration test, mock noop tracer if not.
if integrationTest {
tracerProvider := sdktrace.NewTracerProvider()
defer tracerProvider.Shutdown(ctx)

ctx, span = tracerProvider.Tracer("integration-test-tracer").Start(ctx, "test span")
defer span.End()
} else {
otelTraceID, _ := trace.TraceIDFromHex(strings.Repeat("a", 32))
otelSpanID, _ := trace.SpanIDFromHex(strings.Repeat("f", 16))
otelTraceFlags := trace.FlagsSampled // tracesampled = true
mockSpanContext := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: otelTraceID,
SpanID: otelSpanID,
TraceFlags: otelTraceFlags,
})
ctx = trace.ContextWithSpanContext(ctx, mockSpanContext)
ctx, span = noop.NewTracerProvider().Tracer("test tracer").Start(ctx, "test span")
defer span.End()
}

if test.in.HTTPRequest != nil && test.in.HTTPRequest.Request != nil {
test.in.HTTPRequest.Request = test.in.HTTPRequest.Request.WithContext(ctx)
}
spanContext := trace.SpanContextFromContext(ctx)

// if want is nil, pull wants from spanContext
if test.want == nil {
test.want = &logpb.LogEntry{
Trace: "projects/P/traces/" + spanContext.TraceID().String(),
SpanId: spanContext.SpanID().String(),
TraceSampled: spanContext.TraceFlags().IsSampled(),
}
}

e, err := logging.ToLogEntry(test.in, "projects/P")
if err != nil {
t.Fatalf("Unexpected error: %+v: %v", test.in, err)
}
if got := e.Trace; got != test.want.Trace {
t.Errorf("TraceId: %+v: SpanContext: %+v: got %q, want %q", test.in, spanContext, got, test.want.Trace)
}
if got := e.SpanId; got != test.want.SpanId {
t.Errorf("SpanId: %+v: SpanContext: %+v: got %q, want %q", test.in, spanContext, got, test.want.SpanId)
}
if got := e.TraceSampled; got != test.want.TraceSampled {
t.Errorf("TraceSampled: %+v: SpanContext: %+v: got %t, want %t", test.in, spanContext, got, test.want.TraceSampled)
}
})
}
}

// compareEntries compares most fields list of Entries against expected. compareEntries does not compare:
// - HTTPRequest
// - Operation
Expand Down

0 comments on commit c6711b8

Please sign in to comment.