tessl install tessl/golang-cloud-google-com--go--logging@1.13.0Cloud Logging client library for Go that enables writing log entries to Google Cloud Logging service with buffered asynchronous and synchronous logging capabilities.
This document describes how to integrate Cloud Logging with distributed tracing using OpenTelemetry, W3C Traceparent headers, and X-Cloud-Trace-Context headers.
type Entry struct {
// ...
Trace string
SpanID string
TraceSampled bool
// ...
}Log entries can include trace context information to correlate logs with distributed traces:
Fields:
Trace string - Resource name of the trace associated with the log entry, if any. If it contains a relative resource name, the name is assumed to be relative to //tracing.googleapis.com.
SpanID string - ID of the span within the trace associated with the log entry. The ID is a 16-character hexadecimal encoding of an 8-byte array.
TraceSampled bool - If set, indicates that this request was sampled.
The logging library automatically extracts trace context from HTTP requests when an HTTPRequest is included in the log entry. It supports three methods in the following priority order:
Traceparent headerX-Cloud-Trace-Context headerfunc handler(w http.ResponseWriter, r *http.Request) {
// Trace context is automatically extracted from r
logger.Log(logging.Entry{
Payload: "processing request",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// The Entry will automatically have Trace, SpanID, and TraceSampled
// populated from the request's trace headers
}When using OpenTelemetry for distributed tracing, the logging library automatically extracts trace information from the request context:
import (
"context"
"net/http"
"cloud.google.com/go/logging"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
func handlerWithOTel(w http.ResponseWriter, r *http.Request) {
// OpenTelemetry tracer
tracer := otel.Tracer("my-service")
// Start a span
ctx, span := tracer.Start(r.Context(), "handle-request")
defer span.End()
// Create request with span context
r = r.WithContext(ctx)
// Log entry - trace context extracted automatically from OpenTelemetry
logger.Log(logging.Entry{
Payload: "handling request in span",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// The log entry will have:
// - Trace: OpenTelemetry trace ID
// - SpanID: OpenTelemetry span ID
// - TraceSampled: Whether the trace is sampled
}The logging library supports the W3C Trace Context standard via the Traceparent header:
Traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
| | | | |
| | | | +-- Flags (sampled)
| | | +---- Span ID
| | +--------------------- Trace ID
| +------------------------------------------------------ Version
+--------------------------------------------------------- Formatfunc handlerWithW3CTraceContext(w http.ResponseWriter, r *http.Request) {
// The Traceparent header is automatically parsed
// Example: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
logger.Log(logging.Entry{
Payload: "processing traced request",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Trace field will be: "0af7651916cd43dd8448eb211c80319c"
// SpanID field will be: "b7ad6b7169203331"
// TraceSampled will be based on the flags
}For compatibility with Google Cloud services, the library also supports the legacy X-Cloud-Trace-Context header:
X-Cloud-Trace-Context: TRACE_ID[/SPAN_ID][;o=TRACE_TRUE]
Example: 105445aa7843bc8bf206b12000100000/1;o=1func handlerWithCloudTraceContext(w http.ResponseWriter, r *http.Request) {
// The X-Cloud-Trace-Context header is automatically parsed
// Example: "105445aa7843bc8bf206b12000100000/1;o=1"
logger.Log(logging.Entry{
Payload: "processing cloud trace request",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Trace field will be: "105445aa7843bc8bf206b12000100000"
// SpanID field will be: "0000000000000001" (converted to 16-char hex)
// TraceSampled will be: true (from o=1)
}You can also manually set trace context without using HTTPRequest:
func manualTraceContext() {
traceID := "0af7651916cd43dd8448eb211c80319c"
spanID := "b7ad6b7169203331"
logger.Log(logging.Entry{
Payload: "manual trace context",
Severity: logging.Info,
Trace: traceID,
SpanID: spanID,
TraceSampled: true,
})
}When logging to Cloud Trace, the Trace field should be in one of these formats:
// Full resource name
trace := "projects/my-project/traces/0af7651916cd43dd8448eb211c80319c"
// Relative resource name (automatically expanded)
trace := "0af7651916cd43dd8448eb211c80319c"
logger.Log(logging.Entry{
Payload: "traced operation",
Severity: logging.Info,
Trace: trace,
SpanID: "b7ad6b7169203331",
})Use trace context to correlate logs with distributed traces in Cloud Trace:
func distributedOperation(w http.ResponseWriter, r *http.Request) {
// Log 1: Request received
logger.Log(logging.Entry{
Payload: "request received",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Call downstream service
callDownstreamService(r)
// Log 2: Request completed
logger.Log(logging.Entry{
Payload: "request completed",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// All logs will be correlated in Cloud Trace because they
// share the same trace context from the HTTP request
}When making downstream HTTP requests, propagate the trace context:
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func callDownstreamService(r *http.Request) error {
// Create downstream request with propagated trace context
client := &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
req, _ := http.NewRequestWithContext(r.Context(), "GET", "http://downstream", nil)
// Log before calling downstream
logger.Log(logging.Entry{
Payload: "calling downstream service",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
resp, err := client.Do(req)
if err != nil {
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"error": err.Error(),
},
Severity: logging.Error,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
return err
}
defer resp.Body.Close()
return nil
}The TraceSampled field indicates whether the trace should be recorded:
func sampledLogging(w http.ResponseWriter, r *http.Request) {
entry := logging.Entry{
Payload: "potentially sampled log",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
}
// After automatic trace extraction, check if sampled
// (In practice, the library does this automatically)
// You can also manually control sampling
entry.TraceSampled = shouldSample() // Custom sampling logic
logger.Log(entry)
}
func shouldSample() bool {
// Custom sampling logic (e.g., 10% sampling)
return rand.Float64() < 0.1
}package main
import (
"context"
"log"
"net/http"
"cloud.google.com/go/logging"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/propagation"
)
var logger *logging.Logger
func main() {
ctx := context.Background()
// Initialize Cloud Logging
client, err := logging.NewClient(ctx, "my-project")
if err != nil {
log.Fatalf("failed to create logging client: %v", err)
}
defer client.Close()
logger = client.Logger("trace-log")
// Initialize OpenTelemetry
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
log.Fatalf("failed to create exporter: %v", err)
}
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
// Start HTTP server
http.HandleFunc("/", tracedHandler)
http.ListenAndServe(":8080", nil)
}
func tracedHandler(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("my-service")
// Start span
ctx, span := tracer.Start(r.Context(), "handle-request")
defer span.End()
// Update request context
r = r.WithContext(ctx)
// Log with trace context
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"event": "request_started",
"path": r.URL.Path,
},
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Simulate work
performWork(ctx, r)
// Log completion
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"event": "request_completed",
"path": r.URL.Path,
},
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: http.StatusOK,
},
})
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func performWork(ctx context.Context, r *http.Request) {
tracer := otel.Tracer("my-service")
// Create child span
_, span := tracer.Start(ctx, "perform-work")
defer span.End()
// Log within the span
logger.Log(logging.Entry{
Payload: "performing work",
Severity: logging.Debug,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
}