### Minimal Server and Client Setup with Global Providers Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/quick-start.md Demonstrates the simplest way to set up otelconnect for both server and client using OpenTelemetry's default global providers. This is suitable for quick starts and basic applications. ```go package main import ( "context" "fmt" "log" "net/http" "connectrpc.com/connect" "connectrpc.com/otelconnect" pingv1 "example.com/gen/ping/v1" "example.com/gen/ping/v1/pingv1connect" ) func main() { // Create interceptor with default global providers interceptor, err := otelconnect.NewInterceptor() if err != nil { log.Fatal(err) } // Setup server mux := http.NewServeMux() mux.Handle( pingv1connect.NewPingServiceHandler( &Handler{}, connect.WithInterceptors(interceptor), ), ) log.Fatal(http.ListenAndServe("localhost:8080", mux)) } type Handler struct {} func (h *Handler) Ping(ctx context.Context, req *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) { return connect.NewResponse(&pingv1.PingResponse{}), nil } // Client usage func callService() { interceptor, _ := otelconnect.NewInterceptor() client := pingv1connect.NewPingServiceClient( http.DefaultClient, "http://localhost:8080", connect.WithInterceptors(interceptor), ) resp, _ := client.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{})) fmt.Println(resp) } ``` -------------------------------- ### Usage Example: Handler with Interceptor Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/interceptor.md Demonstrates how to integrate the interceptor into a Connect handler setup. The streaming logic within the handler remains standard. ```go // Handler implementation with interceptor: handler := pingv1connect.NewPingServiceHandler( &MyPingServiceHandler{}, connect.WithInterceptors(interceptor), ) // In your handler implementation, streaming is automatic: func (h *MyPingServiceHandler) EchoStream( ctx context.Context, stream *connect.BidiStream[Request, Response], ) error { for { msg, err := stream.Receive() if err != nil { return err } if err := stream.Send(&Response{Body: msg.Body}); err != nil { return err } } } ``` -------------------------------- ### RPCSystem Usage Example Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/types.md Example demonstrating how to use the WithRPCSystem option with the GRPCSystem to apply gRPC semantic conventions. This is useful for compatibility or specific tracing requirements. ```go interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithRPCSystem(otelconnect.GRPCSystem), ) ``` -------------------------------- ### OpenTelemetry Collector OTLP Exporter Setup Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Example of configuring an OpenTelemetry metric provider with an OTLP HTTP exporter to send metrics to a collector. ```go import ( "context" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/sdk/metric" ) exporter, _ := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint("localhost:4318")) provider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exporter))) ``` -------------------------------- ### Setup with OpenTelemetry SDK Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/quick-start.md Configure OpenTelemetry providers explicitly using the SDK before creating the otelconnect interceptor. This provides more control over tracing and metrics configuration. ```go package main import ( "context" "log" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func init() { // Create OTLP trace exporter exporter, err := otlptracehttp.New(context.Background()) if err != nil { log.Fatal(err) } // Create resource describing the service res, err := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("my-service"), semconv.ServiceVersionKey.String("1.0.0"), ), ) if err != nil { log.Fatal(err) } // Create and set trace provider tp := trace.NewTracerProvider( trace.WithBatcher(exporter), trace.WithResource(res), ) otel.SetTracerProvider(tp) // Similar setup for MeterProvider... } func main() { // Now interceptor will use the configured providers interceptor, _ := otelconnect.NewInterceptor() // Use interceptor... } ``` -------------------------------- ### Example Streaming RPC Events Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Provides a concrete example of the sequence of 'message' events and the final status recorded for a 3-message server streaming RPC. ```text Event 1: message [type=RECEIVED, id=1, size=256] Event 2: message [type=SENT, id=1, size=512] Event 3: message [type=SENT, id=2, size=512] Event 4: message [type=SENT, id=3, size=512] Final: [status=Unset] ``` -------------------------------- ### Datadog Integration with OTLP Exporter Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Example of configuring an OTLP HTTP exporter to send metrics to a Datadog agent. ```go import ( "context" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" ) // Configure OTLP exporter pointing to Datadog agent exporter, _ := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint("localhost:4318")) ``` -------------------------------- ### Span Naming Examples Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Provides examples of how procedure names are transformed into span names, typically by removing the leading slash. ```text Procedure: /helloworld.Greeter/SayHello Span name: helloworld.Greeter/SayHello ``` ```text Procedure: /pb.UserService/GetUser Span name: pb.UserService/GetUser ``` -------------------------------- ### OpenTelemetry Collector Span Example (Protobuf) Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Spans are exported via OTLP (OpenTelemetry Protocol) for collection by the OpenTelemetry Collector. This example shows the structure of ResourceSpans, ScopeSpans, and Spans. ```protobuf ResourceSpans { Resource: {service.name="my-service"} ScopeSpans { Spans { Name: "helloworld.Greeter/SayHello" Kind: SPAN_KIND_CLIENT Duration: 42500000 ns Attributes: [...] Events: [...] } } } ``` -------------------------------- ### Minimal Client Interceptor Setup Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/README.md Shows the minimal configuration for adding an otelconnect interceptor to a Connect RPC client. This enables tracing and metrics for outgoing client requests. ```go interceptor, _ := otelconnect.NewInterceptor() client := service.NewServiceClient( http.DefaultClient, "http://localhost:8080", connect.WithInterceptors(interceptor), ) ``` -------------------------------- ### Minimal Server Interceptor Setup Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/README.md Demonstrates the minimal configuration for adding an otelconnect interceptor to a Connect RPC server handler. This ensures basic tracing and metrics are collected for server-side requests. ```go interceptor, _ := otelconnect.NewInterceptor() mux.Handle( service.NewServiceHandler(impl, connect.WithInterceptors(interceptor)), ) ``` -------------------------------- ### Automatic Labeler Creation Example Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/labeler.md Demonstrates a scenario where an interceptor automatically ensures a labeler is present in the context. In this case, LabelerFromContext will always return a usable labeler. ```go func myHandler(ctx context.Context, req *Request) (*Response, error) { labeler, _ := otelconnect.LabelerFromContext(ctx) // labeler is always non-nil and safe to use labeler.Add(attribute.String("user", req.User)) return &Response{}, nil } ``` -------------------------------- ### Example Streaming RPC Metric Values Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Shows example metric values for a server streaming RPC call that received three messages, detailing duration, message sizes, and counts. ```text rpc.client.duration{...} = 100ms rpc.client.request.size{...} = [128] # Single request rpc.client.response.size{...} = [256, 256, 256] # Three responses rpc.client.requests_per_rpc{...} = 1 rpc.client.responses_per_rpc{...} = 3 ``` -------------------------------- ### Example Unary RPC Metric Values Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Provides example metric values for a typical unary RPC call, including duration, request/response size, and request/response counts. ```text rpc.client.duration{rpc.system="connect_rpc", rpc.service="Ping", rpc.method="Ping"} = 42ms rpc.client.request.size{...} = 128 bytes rpc.client.response.size{...} = 256 bytes rpc.client.requests_per_rpc{...} = 1 rpc.client.responses_per_rpc{...} = 1 ``` -------------------------------- ### NewInterceptor Error Handling Example Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Demonstrates how to handle potential errors when creating a new interceptor. Errors typically indicate issues with meter or tracer provider configuration. ```go interceptor, err := otelconnect.NewInterceptor() if err != nil { // Handle error - likely provider configuration issue log.Fatal(err) } ``` -------------------------------- ### Basic Unary Metrics Recording Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Example of a Go handler function that records metrics for a unary RPC. It demonstrates adding custom labels to the metric context. ```go import ( "context" "go.opentelemetry.io/otel/attribute" connect "connectrpc.com/connect" otelconnect "github.com/bufbuild/otelconnect-go" ) // Handler that records metrics func (h *Handler) GetUser(ctx context.Context, req *connect.Request[GetUserRequest]) (*connect.Response[User], error) { labeler, _ := otelconnect.LabelerFromContext(ctx) labeler.Add(attribute.String("user_id", req.Msg.ID)) return connect.NewResponse(&User{ID: req.Msg.ID}), nil } ``` -------------------------------- ### Compose otelconnect-go Interceptor Options Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Create a fully configured interceptor by composing various options. This example shows custom providers, filtering, attribute manipulation, and header configuration. ```go import ( "context" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" "connectrpc.com/connect" "connectrpc.com/otelconnect" ) // Create a fully configured interceptor interceptor, err := otelconnect.NewInterceptor( // Use custom providers otelconnect.WithTracerProvider(tracerProvider), otelconnect.WithMeterProvider(meterProvider), // Filter sensitive services otelconnect.WithFilter(func(ctx context.Context, spec connect.Spec) bool { return !strings.Contains(spec.Procedure, "Internal") }), // Remove high-cardinality attributes otelconnect.WithoutServerPeerAttributes(), // Add specific headers to traces otelconnect.WithTraceRequestHeader("X-Request-ID", "X-Tenant-ID"), // Trust internal service traces otelconnect.WithTrustRemote(), // Use uniform conventions otelconnect.WithRPCSystem(otelconnect.GRPCSystem), ) ``` -------------------------------- ### Production Server Configuration Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/README.md An example of a production-ready interceptor configuration for a Connect RPC server. It includes custom OpenTelemetry providers, disables server peer attributes, enables trace request headers, and trusts remote context. ```go interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(tracerProvider), otelconnect.WithMeterProvider(meterProvider), otelconnect.WithoutServerPeerAttributes(), otelconnect.WithTraceRequestHeader("x-request-id"), otelconnect.WithTrustRemote(), ) ``` -------------------------------- ### Streaming Metrics Recording Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Example of a Go streaming handler function that records metrics. It shows how to add labels based on received requests and track multiple responses. ```go import ( "context" "fmt" "go.opentelemetry.io/otel/attribute" connect "connectrpc.com/connect" otelconnect "github.com/bufbuild/otelconnect-go" ) // Streaming handler func (h *Handler) ListUsers(ctx context.Context, stream *connect.ServerStream[ListUsersRequest]) error { req, _ := stream.Receive() labeler, _ := otelconnect.LabelerFromContext(ctx) labeler.Add(attribute.String("filter", req.Filter)) // Send multiple responses for i := 0; i < 100; i++ { _ = stream.Send(&User{ID: fmt.Sprintf("%d", i)}) } return nil } ``` -------------------------------- ### Datadog Span Example Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Spans are integrated with Datadog APM, showing service name, operation, duration, and relevant tags for tracing. ```text Service: my-service Operation: helloworld.Greeter/SayHello Duration: 42.5ms Tags: rpc.system: connect_rpc rpc.service: helloworld.Greeter rpc.method: SayHello ``` -------------------------------- ### Include Request Headers in Traces Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Configure the interceptor to include specified request headers as trace attributes. This example includes 'X-Request-ID', 'X-User-ID', and 'User-Agent'. ```go interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTraceRequestHeader( "X-Request-ID", "X-User-ID", "User-Agent", ), ) ``` -------------------------------- ### Prometheus Metric Exposure Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/metrics-reference.md Example of how metrics are exposed as Prometheus histograms, showing bucket, sum, and count for client duration. ```prometheus rpc_client_duration_ms_bucket{rpc_system="connect_rpc", rpc_service="Greeter", rpc_method="SayHello", le="5"} 42 rpc_client_duration_ms_bucket{rpc_system="connect_rpc", rpc_service="Greeter", rpc_method="SayHello", le="10"} 89 rpc_client_duration_ms_bucket{rpc_system="connect_rpc", rpc_service="Greeter", rpc_method="SayHello", le="+Inf"} 100 rpc_client_duration_ms_sum{rpc_system="connect_rpc", rpc_service="Greeter", rpc_method="SayHello"} 1523 rpc_client_duration_ms_count{rpc_system="connect_rpc", rpc_service="Greeter", rpc_method="SayHello"} 100 ``` -------------------------------- ### Include Response Headers in Traces Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Configure the interceptor to include specified response headers as trace attributes. This example includes 'X-Response-ID' and 'X-Cache-Status'. ```go interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTraceResponseHeader( "X-Response-ID", "X-Cache-Status", ), ) ``` -------------------------------- ### Filter RPCs by Procedure Name Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Use a filter function to include only specific RPC procedures for tracing. This example traces procedures containing 'AdminService'. ```go filter := func(ctx context.Context, spec connect.Spec) bool { return strings.Contains(spec.Procedure, "AdminService") } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithFilter(filter), ) ``` -------------------------------- ### Attribute Filtering with Custom Function Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Demonstrates how to filter attributes during span creation using a custom function. This example excludes the 'net.peer.port' attribute from spans. ```go filter := func(spec connect.Spec, attr attribute.KeyValue) bool { if attr.Key == "net.peer.port" { return false // Exclude peer port from spans } return true } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithAttributeFilter(filter), ) ``` -------------------------------- ### Filter Attributes to Reduce Cardinality Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Use an attribute filter to manage attribute cardinality. This example keeps standard RPC attributes and selectively includes/excludes peer attributes based on client/server role. ```go filter := func(spec connect.Spec, attr attribute.KeyValue) bool { key := string(attr.Key) // Keep standard RPC attributes if strings.HasPrefix(key, "rpc.") { return true } // Skip peer address/port for lower cardinality if key == "net.peer.name" || key == "net.peer.port" { return !spec.IsClient // Allow on client, skip on server } return true } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithAttributeFilter(filter), ) ``` -------------------------------- ### Unary RPC Span Lifecycle Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Illustrates the sequence of operations for creating and managing a trace span during a unary RPC call. Includes span start, context injection, RPC execution, event recording, attribute setting, and span end. ```text [Start] │ ├─ Start span ├─ Inject trace context (client only) ├─ Call RPC ├─ Record request size event (optional) ├─ Record response size event (optional) ├─ Set status and attributes │ [End] └─ End span ``` -------------------------------- ### AttributeFilter Usage Example Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/types.md Example demonstrating how to use the AttributeFilter to exclude sensitive attributes from spans and metrics. This filter can be passed to the WithAttributeFilter option. ```go filter := func(spec connect.Spec, attr attribute.KeyValue) bool { // Exclude sensitive attributes if attr.Key == "user_password" { return false } return true } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithAttributeFilter(filter), ) ``` -------------------------------- ### Set up OpenTelemetry Interceptor for Connect Server Source: https://github.com/connectrpc/otelconnect-go/blob/main/README.md Use otelconnect.NewInterceptor to add tracing and metrics to Connect handlers. Configure with WithTracerProvider and WithMeterProvider to avoid global OpenTelemetry providers. ```go package main import ( "context" "fmt" "log" "net/http" "connectrpc.com/connect" "connectrpc.com/otelconnect" // Generated from your protobuf schema by protoc-gen-go and // protoc-gen-connect-go. pingv1 "connectrpc.com/otelconnect/internal/gen/observability/ping/v1" "connectrpc.com/otelconnect/internal/gen/observability/ping/v1/pingv1connect" ) func main() { mux := http.NewServeMux() otelInterceptor, err := otelconnect.NewInterceptor() if err != nil { log.Fatal(err) } // otelconnect.NewInterceptor provides an interceptor that adds tracing and // metrics to both clients and handlers. By default, it uses OpenTelemetry's // global TracerProvider and MeterProvider, which you can configure by // following the OpenTelemetry documentation. If you'd prefer to avoid // globals, use otelconnect.WithTracerProvider and // otelconnect.WithMeterProvider. mux.Handle(pingv1connect.NewPingServiceHandler( &pingv1connect.UnimplementedPingServiceHandler{}, connect.WithInterceptors(otelInterceptor), )) http.ListenAndServe("localhost:8080", mux) } func makeRequest() { otelInterceptor, err := otelconnect.NewInterceptor() if err != nil { log.Fatal(err) } client := pingv1connect.NewPingServiceClient( http.DefaultClient, "http://localhost:8080", connect.WithInterceptors(otelInterceptor), ) resp, err := client.Ping( context.Background(), connect.NewRequest(&pingv1.PingRequest{}), ) if err != nil { log.Fatal(err) } fmt.Println(resp) } ``` -------------------------------- ### Jaeger Span Example Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Example of how spans are automatically exported to Jaeger with standard formatting. This format includes trace and span IDs, span name, duration, and attributes. ```jaeger Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736 Span ID: 00f067aa0ba902b7 Parent ID: 00f067aa0ba902b7 Span Name: helloworld.Greeter/SayHello Duration: 42.5ms Status: OK Attributes: rpc.system: connect_rpc rpc.service: helloworld.Greeter rpc.method: SayHello ``` -------------------------------- ### Initialize Interceptor with Error Handling Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/README.md Demonstrates how to create a new interceptor and handle potential errors during initialization. This is typically used to set up OpenTelemetry tracing and metrics for Connect RPC services. ```go interceptor, err := otelconnect.NewInterceptor() if err != nil { // Error creating meter or tracer from provider // Usually indicates provider configuration issue log.Fatal(err) } ``` -------------------------------- ### otelconnect-go Usage Pattern Summary Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Provides a four-step summary for using the otelconnect-go interceptor. It covers creating the interceptor, applying it to handlers and clients, and adding custom attributes within handlers. ```go // 1. Create interceptor interceptor, err := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(tp), otelconnect.WithMeterProvider(mp), ) // 2. Use with handlers handler := service.NewServiceHandler( impl, connect.WithInterceptors(interceptor), ) // 3. Use with clients client := service.NewServiceClient( http.DefaultClient, "http://localhost:8080", connect.WithInterceptors(interceptor), ) // 4. Add custom attributes in handlers func (h *Handler) Method(ctx context.Context, req *connect.Request[Req]) (*connect.Response[Resp], error) { labeler, _ := otelconnect.LabelerFromContext(ctx) labeler.Add(attribute.String("key", "value")) // ... } ``` -------------------------------- ### Labeler Type Definition Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Thread-safe collector for custom attributes. Methods include Add() and Get(). ```go type Labeler struct { // Unexported fields } ``` -------------------------------- ### Configure Header Capture Options Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Include specific HTTP headers as trace attributes. Specify headers to be captured for tracing. ```go func WithTraceRequestHeader(keys ...string) Option func WithTraceResponseHeader(keys ...string) Option ``` -------------------------------- ### Global RPCSystem Variables Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Provides singleton instances for ConnectRPCSystem and gRPCSystem, representing choices for RPC system implementations. ```go var ( ConnectRPCSystem RPCSystem = connectRPCSystem{} GRPCSystem RPCSystem = gRPCSystem{} ) ``` -------------------------------- ### Update version for next development cycle (release candidate) Source: https://github.com/connectrpc/otelconnect-go/blob/main/RELEASE.md If a release candidate was just created, update the version to anticipate another candidate. Use the same version number, increment the candidate number, and append '-dev'. ```patch const ( - version = "1.0.0-rc4" + version = "1.0.0-rc5-dev" semanticVersion = "semver:" + version ) ``` -------------------------------- ### Filter RPCs by Context Value Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Use a filter function to trace RPCs only when a specific value is present in the context. This example traces if 'tracing_enabled' is set in the context. ```go filter := func(ctx context.Context, spec connect.Spec) bool { enabled := ctx.Value("tracing_enabled") return enabled != nil } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithFilter(filter), ) ``` -------------------------------- ### Skip Specific RPC Procedures Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Use a filter function to exclude specific RPC procedures from tracing. This example skips procedures containing 'Health' or 'Metrics'. ```go filter := func(ctx context.Context, spec connect.Spec) bool { proc := spec.Procedure return !strings.Contains(proc, "Health") && !strings.Contains(proc, "Metrics") } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithFilter(filter), ) ``` -------------------------------- ### Configure Semantic Conventions Option Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Force use of specific RPC semantic conventions (ConnectRPC or gRPC). This option ensures correct convention usage. ```go func WithRPCSystem(system RPCSystem) Option ``` -------------------------------- ### Configure Tracer Provider Option Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Customize OpenTelemetry providers for tracing, metrics, and context propagation. ```go func WithTracerProvider(provider trace.TracerProvider) Option func WithMeterProvider(provider metric.MeterProvider) Option func WithPropagator(propagator propagation.TextMapPropagator) Option ``` -------------------------------- ### Typical Labeler Usage in Connect RPC Handler Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/labeler.md Shows the common pattern for using a labeler within a Connect RPC handler to add request-specific attributes to metrics. ```go package myservice import ( "context" "connectrpc.com/connect" "connectrpc.com/otelconnect" "go.opentelemetry.io/otel/attribute" ) type MyHandler struct{} func (h *MyHandler) CreateUser( ctx context.Context, req *connect.Request[CreateUserRequest], ) (*connect.Response[User], error) { // Get the labeler labeler, _ := otelconnect.LabelerFromContext(ctx) // Add attributes based on the request labeler.Add( attribute.String("org_id", req.Msg.OrgID), attribute.String("role", req.Msg.Role), ) // Handler implementation user := &User{ID: generateID()} return connect.NewResponse(user), nil } ``` -------------------------------- ### Create New Interceptor Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Creates a new interceptor with the provided options. Returns an error if initialization fails. Use Variable number of Option values. ```go func NewInterceptor(options ...Option) (*Interceptor, error) ``` -------------------------------- ### Global RPCSystem Variables Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Provides singleton instances for Connect and gRPC systems, allowing selection of the desired RPC protocol. ```APIDOC ## Global RPCSystem Variables ### Description These global variables provide singleton instances that represent the available RPC system implementations. They are used to specify which RPC protocol (Connect or gRPC) the telemetry instrumentation should target. ### Variables - `ConnectRPCSystem RPCSystem`: A singleton instance representing the Connect RPC system. - `GRPCSystem RPCSystem`: A singleton instance representing the gRPC system. ### Usage These variables can be passed to the `otelconnect.WithRPCSystem` option when creating a new interceptor to explicitly choose the RPC protocol. ```go import ( "connectrpc.com/otelconnect" ) // Use Connect RPC system (default if not specified) interceptor := otelconnect.NewInterceptor( otelconnect.WithRPCSystem(otelconnect.ConnectRPCSystem), ) // Use gRPC system interceptor := otelconnect.NewInterceptor( otelconnect.WithRPCSystem(otelconnect.GRPCSystem), ) ``` ``` -------------------------------- ### Filtering Endpoints for Instrumentation Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/quick-start.md Implement a custom filter function to selectively instrument RPC methods. This example skips instrumentation for health check and internal metrics endpoints. ```go import ( "context" "strings" "connectrpc.com/connect" "connectrpc.com/otelconnect" ) filter := func(ctx context.Context, spec connect.Spec) bool { // Skip health checks if strings.Contains(spec.Procedure, "Health") { return false } // Skip internal metrics endpoints if strings.Contains(spec.Procedure, "InternalMetrics") { return false } return true } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithFilter(filter), ) ``` -------------------------------- ### Create Context with Labeler Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/labeler.md Creates a new context with a provided Labeler instance attached. This allows the labeler to be retrieved later using LabelerFromContext. The interceptor automatically calls this if a labeler is not already present in the context. ```go import ( "context" "connectrpc.com/otelconnect" ) func handleRequest(ctx context.Context) { labeler := &otelconnect.Labeler{} // Attach labeler to context ctx = otelconnect.ContextWithLabeler(ctx, labeler) // Now the labeler is available in this context and child contexts callDownstream(ctx) } ``` -------------------------------- ### Configure otelconnect-go Interceptor with Environment Variables Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Configure the otelconnect-go interceptor by passing OpenTelemetry providers that are themselves configured to read environment variables. This allows for external configuration of tracing and metrics collection. ```go // OpenTelemetry environment variables are read by provider exporters: // OTEL_EXPORTER_OTLP_ENDPOINT // OTEL_EXPORTER_OTLP_HEADERS // OTEL_SDK_DISABLED // (And many others depending on your exporter) import "go.opentelemetry.io/otel" // Tracer/Meter providers created elsewhere (e.g., in init.go) // will read environment variables if configured to do so tracer := otel.GetTracerProvider() meter := otel.GetMeterProvider() interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(tracer), otelconnect.WithMeterProvider(meter), ) ``` -------------------------------- ### Filter Attributes to Remove Sensitive Keys Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Use an attribute filter to remove sensitive information from trace attributes. This example removes keys containing 'password', 'token', 'secret', or 'api_key'. ```go filter := func(spec connect.Spec, attr attribute.KeyValue) bool { sensitiveKeys := []string{"password", "token", "secret", "api_key"} for _, key := range sensitiveKeys { if strings.Contains(string(attr.Key), key) { return false } } return true } interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithAttributeFilter(filter), ) ``` -------------------------------- ### Configure Custom Meter Provider Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/options.md Use `WithMeterProvider` to configure the interceptor with a custom OpenTelemetry `MeterProvider`. This is beneficial for managing metrics in specific environments. ```go import ( "go.opentelemetry.io/otel/sdk/metric" "github.com/your-org/otelconnect" ) provider := metric.NewMeterProvider( metric.WithReader(otlphttp.NewMetricReader(...)), ) interceptor, err := otelconnect.NewInterceptor( otelconnect.WithMeterProvider(provider), ) ``` -------------------------------- ### Get Labeler Attributes Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/labeler.md Returns a copy of all attributes added to the labeler. This allows for safe inspection of the labeler's content without the risk of concurrent modification, typically called internally by the interceptor. ```go labeler := &otelconnect.Labeler{} labeler.Add( attribute.String("env", "prod"), attribute.Int("version", 2), ) attrs := labeler.Get() // attrs is a copy: [attribute.String("env", "prod"), attribute.Int("version", 2)] ``` -------------------------------- ### Configure OTLP Tracer Provider Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Configure a custom OpenTelemetry tracer provider using OTLP exporter. This is useful for sending trace data to an OTLP-compatible backend. ```go import ( "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" ) exporter, _ := otlptracehttp.New(ctx) provider := trace.NewTracerProvider( trace.WithBatcher(exporter), ) interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(provider), ) ``` -------------------------------- ### Create Default otelconnect-go Interceptor Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Instantiate the otelconnect-go interceptor with default settings. This is useful when no custom configuration is immediately required. ```go interceptor, _ := otelconnect.NewInterceptor() ``` -------------------------------- ### Testing with Noop OpenTelemetry Providers Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/quick-start.md Create an otelconnect interceptor that uses noop (no operation) providers for tracing and metrics, suitable for testing environments. ```go import ( "connectrpc.com/otelconnect" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" ) func createTestInterceptor() (*otelconnect.Interceptor, error) { return otelconnect.NewInterceptor( otelconnect.WithTracerProvider(trace.NewNoopTracerProvider()), otelconnect.WithMeterProvider(noop.NewMeterProvider()), ) } ``` -------------------------------- ### Required OpenTelemetry Imports Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Lists the necessary OpenTelemetry packages that must be imported for custom configurations, including trace, metric, propagation, and attribute modules. ```go // OpenTelemetry trace import "go.opentelemetry.io/otel/trace" // OpenTelemetry metrics import "go.opentelemetry.io/otel/metric" // OpenTelemetry propagation import "go.opentelemetry.io/otel/propagation" // OpenTelemetry attributes import "go.opentelemetry.io/otel/attribute" // Connect import "connectrpc.com/connect" // Context import "context" ``` -------------------------------- ### Implement connect.Interceptor Interface Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md These methods wrap respective RPC types with instrumentation. They implement the connect.Interceptor interface. ```go func (i *Interceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc func (i *Interceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc func (i *Interceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc ``` -------------------------------- ### Configure Custom Tracer Provider Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/options.md Use `WithTracerProvider` to configure the interceptor with a custom OpenTelemetry `TracerProvider`. This is useful for testing or avoiding global state. ```go import ( "go.opentelemetry.io/otel/sdk/trace" "github.com/your-org/otelconnect" ) provider := trace.NewTracerProvider( trace.WithBatcher(otlphttp.NewExporter(...)), ) interceptor, err := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(provider), ) ``` -------------------------------- ### Create New Interceptor with Defaults Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/interceptor.md Creates a new Interceptor using global OpenTelemetry providers. This is the simplest way to add instrumentation to your Connect RPCs. ```go package main import ( "log" "net/http" "connectrpc.com/connect" "connectrpc.com/otelconnect" ) func main() { // Create a new interceptor with default configuration // (uses global OpenTelemetry providers) interceptor, err := otelconnect.NewInterceptor() if err != nil { log.Fatal(err) } // Use with a Connect service handler mux := http.NewServeMux() mux.Handle( "/v1.PingService/Ping", // Handler setup... connect.WithInterceptors(interceptor), ) } ``` -------------------------------- ### Configure Context Propagation Options Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Control how trace context is handled and propagated. These options affect trace context management. ```go func WithTrustRemote() Option func WithPropagateResponseHeader() Option ``` -------------------------------- ### connectRPCSystem Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/types.md Implementation of `RPCSystem` for ConnectRPC semantic conventions. ```APIDOC ## connectRPCSystem ### Description The `connectRPCSystem` struct is a concrete implementation of the `RPCSystem` interface, specifically tailored for ConnectRPC. It ensures that telemetry data adheres to the semantic conventions defined for the ConnectRPC protocol. ### Methods - `protocol() string` - Returns the string `"connect_rpc"`. ### Exported As This type is exported globally as the `ConnectRPCSystem` variable. ``` -------------------------------- ### Context Functions for Labeler Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Stores a labeler in a context or retrieves a labeler from a context, creating a new empty one if not found. ```go func ContextWithLabeler(parent context.Context, l *Labeler) context.Context func LabelerFromContext(ctx context.Context) (*Labeler, bool) ``` -------------------------------- ### Configure Filtering Options Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-summary.md Filter which RPC calls are instrumented and which attributes are recorded. Use these options to control instrumentation behavior. ```go func WithFilter(filter func(context.Context, connect.Spec) bool) Option func WithAttributeFilter(filter AttributeFilter) Option func WithoutServerPeerAttributes() Option ``` -------------------------------- ### ConnectRPCSystem Identifier Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/types.md Singleton instance indicating the use of ConnectRPC semantic conventions. Use this with otelconnect.WithRPCSystem(). ```go var ConnectRPCSystem = connectRPCSystem{} ``` -------------------------------- ### Complex OpenTelemetry Configuration Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/quick-start.md Configure OpenTelemetry with custom resource, tracer, meter providers, propagator, attribute filter, RPC filter, and header capture for production environments. ```go package main import ( "context" "log" "strings" "connectrpc.com/connect" "connectrpc.com/otelconnect" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func setupTelemetry() (*otelconnect.Interceptor, error) { // Resource res, _ := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("api-service"), semconv.ServiceVersionKey.String("1.0.0"), semconv.EnvironmentKey.String("production"), ), ) // Tracer provider traceExporter, _ := otlptracehttp.New(context.Background()) tracerProvider := trace.NewTracerProvider( trace.WithBatcher(traceExporter), trace.WithResource(res), ) // Meter provider metricExporter, _ := otlpmetrichttp.New(context.Background()) meterProvider := metric.NewMeterProvider( metric.WithResource(res), metric.WithReader(metric.NewPeriodicReader(metricExporter)), ) // Propagator propagator := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) // Attribute filter attributeFilter := func(spec connect.Spec, attr attribute.KeyValue) bool { key := string(attr.Key) // Remove peer info from server metrics if !spec.IsClient && (key == "net.peer.name" || key == "net.peer.port") { return false } // Remove sensitive data if strings.Contains(strings.ToLower(key), "password") || strings.Contains(strings.ToLower(key), "token") { return false } return true } // RPC filter rpcFilter := func(ctx context.Context, spec connect.Spec) bool { // Skip health checks return !strings.Contains(spec.Procedure, "Health") } // Create interceptor interceptor, err := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(tracerProvider), otelconnect.WithMeterProvider(meterProvider), otelconnect.WithPropagator(propagator), otelconnect.WithFilter(rpcFilter), otelconnect.WithAttributeFilter(attributeFilter), otelconnect.WithTraceRequestHeader("x-request-id", "x-user-id"), otelconnect.WithTrustRemote(), ) return interceptor, err } func main() { interceptor, err := setupTelemetry() if err != nil { log.Fatal(err) } // Use interceptor with handlers and clients... } ``` -------------------------------- ### Option Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/types.md Configuration option for the `Interceptor`. Allows for composable configuration passed to `NewInterceptor()`. ```APIDOC ## Option ### Description The `Option` interface defines a way to configure the `Interceptor`. It allows for composable configuration settings to be passed to the `NewInterceptor()` constructor, enabling customization of the OpenTelemetry instrumentation. ### Methods - `apply(*config)` - Applies the specific option's configuration to the internal `config` struct. ### Implemented By This interface is implemented by various concrete types such as `propagatorOption`, `tracerProviderOption`, `meterProviderOption`, `filterOption`, `attributeFilterOption`, `trustRemoteOption`, `traceRequestHeaderOption`, `traceResponseHeaderOption`, `omitTraceEventsOption`, `propagateResponseHeaderOption`, and `rpcSystemOption`. ### Creation Options are created using factory functions like `WithTracerProvider()`, `WithMeterProvider()`, `WithPropagator()`, `WithFilter()`, `WithAttributeFilter()`, `WithoutTracing()`, `WithoutMetrics()`, `WithoutServerPeerAttributes()`, `WithTrustRemote()`, `WithTraceRequestHeader()`, `WithTraceResponseHeader()`, `WithoutTraceEvents()`, `WithPropagateResponseHeader()`, and `WithRPCSystem()`. ``` -------------------------------- ### Client Span vs. Server Span with WithTrustRemote Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Illustrates the difference in span relationships between client and server calls. The default behavior creates a new root span on the server, while WithTrustRemote establishes a parent-child relationship for a continuous trace. ```text Client Span │ └─ (traced via link, not parent) Server Span (new root) ``` ```text Client Span │ └─ (parent-child relationship) Server Span ``` -------------------------------- ### Retrieve Labeler from Context Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/labeler.md Retrieves a labeler instance from the provided context. If found, custom attributes can be added to it. If not found, a new empty labeler is returned, and attributes added to it will not be included in metrics. ```go func (h *Handler) ProcessRequest(ctx context.Context, req *Request) error { // Get or create labeler labeler, found := otelconnect.LabelerFromContext(ctx) if found { // Labeler was found in context, add custom attributes labeler.Add( attribute.String("request_id", req.ID), attribute.String("source", req.Source), ) } // ... handler logic ... return nil } ``` -------------------------------- ### Configure Interceptor with Options Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/README.md Customizes the behavior of the otelconnect interceptor using various option functions. This allows control over tracer providers, filtering, and attribute collection. ```go otelconnect.NewInterceptor( otelconnect.WithTracerProvider(tp), otelconnect.WithFilter(myFilter), ) ``` -------------------------------- ### Enable OpenTelemetry Debug Logging Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md To debug span creation and values, enable OpenTelemetry debug logging by configuring a trace provider with a batcher. Spans will be visible in logs as they are created. ```go import "go.opentelemetry.io/otel/sdk/trace" provider := trace.NewTracerProvider( trace.WithBatcher(exporter), ) // Spans are now visible in logs as they're created ``` -------------------------------- ### Configure Meter Provider Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/configuration.md Configure a custom OpenTelemetry meter provider. This allows for integration with specific metric exporters, such as OTLP. ```go import ( "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" ) exporter, _ := otlpmetrichttp.New(ctx) provider := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(exporter)), ) interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithMeterProvider(provider), ) ``` -------------------------------- ### GRPCSystem Identifier Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/types.md Singleton instance indicating the use of gRPC semantic conventions. Use this with otelconnect.WithRPCSystem(). ```go var GRPCSystem = gRPCSystem{} ``` -------------------------------- ### Configure Filter for Tracing and Metrics Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/options.md Use WithFilter to control which RPCs are traced and metered. The filter function receives the RPC spec and determines if instrumentation should be applied. It must be safe for concurrent use. ```go func WithFilter(filter func(context.Context, connect.Spec) bool) Option ``` ```go // Only trace and meter calls to the admin service filter := func(ctx context.Context, spec connect.Spec) bool { return strings.Contains(spec.Procedure, "AdminService") } interceptor, err := otelconnect.NewInterceptor( otelconnect.WithFilter(filter), ) ``` -------------------------------- ### Wrap Streaming Client Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/interceptor.md Shows how to apply the Interceptor to a streaming client. The interceptor automatically instruments streaming calls, tracking message counts, sizes, and the overall stream lifecycle. ```go // When creating a streaming client: client := pingv1connect.NewPingServiceClient( http.DefaultClient, "http://localhost:8080", connect.WithInterceptors(interceptor), ) // The interceptor automatically wraps streaming calls ctx := context.Background() stream := client.EchoStream(ctx) // Stream Send/Receive operations are now traced and metered ``` -------------------------------- ### Create New Interceptor with Custom Providers Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/interceptor.md Creates a new Interceptor, allowing you to specify custom OpenTelemetry tracer and meter providers. This is useful for advanced configurations or when managing providers manually. ```go import ( "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/metric" ) tracer := trace.NewTracerProvider() meter := metric.NewMeterProvider() interceptor, err := otelconnect.NewInterceptor( otelconnect.WithTracerProvider(tracer), otelconnect.WithMeterProvider(meter), ) ``` -------------------------------- ### Capture Request and Response Headers in Traces Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/quick-start.md Include specific request and response headers as attributes in your trace spans. This helps in correlating requests and debugging. ```go interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTraceRequestHeader( "x-request-id", "x-trace-id", "x-user-id", "user-agent", ), otelconnect.WithTraceResponseHeader( "x-response-id", "cache-status", ), ) ``` -------------------------------- ### Tracing Request and Response Headers Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/spans-reference.md Demonstrates how to configure the interceptor to trace specific HTTP headers from requests and responses as span attributes. This helps in correlating traces with external request identifiers. ```go interceptor, _ := otelconnect.NewInterceptor( otelconnect.WithTraceRequestHeader("x-request-id", "user-agent"), otelconnect.WithTraceResponseHeader("x-response-id"), ) ``` -------------------------------- ### WithRPCSystem Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/options.md Forces the use of semantic conventions for a specific RPC system, ensuring uniform telemetry across different protocols. ```APIDOC ## WithRPCSystem ### Description Forces the use of semantic conventions for a specific RPC system. By default, the interceptor uses semantic conventions based on the actual wire protocol. This option forces uniform telemetry across all protocols by using the specified RPC system conventions. ### Method Signature ```go func WithRPCSystem(system RPCSystem) Option ``` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters Table | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | system | `RPCSystem` | Yes | — | The RPC system to use (ConnectRPCSystem or GRPCSystem) | ### Usage Example ```go // Force all telemetry to use gRPC conventions interceptor, err := otelconnect.NewInterceptor( otelconnect.WithRPCSystem(otelconnect.GRPCSystem), ) ``` ### Valid RPCSystem Values - `otelconnect.ConnectRPCSystem` - Use ConnectRPC semantic conventions - `otelconnect.GRPCSystem` - Use gRPC semantic conventions ``` -------------------------------- ### Wrap Unary RPC Handler Source: https://github.com/connectrpc/otelconnect-go/blob/main/_autodocs/api-reference/interceptor.md Demonstrates how the Interceptor is used internally by Connect to wrap unary RPC handlers. You typically don't call WrapUnary directly but rather pass the interceptor to Connect's handler configuration. ```go // This is typically used internally by Connect when configuring handlers // You don't call WrapUnary directly; instead pass the interceptor to Connect: handler := pingv1connect.NewPingServiceHandler( &pingv1connect.UnimplementedPingServiceHandler{}, connect.WithInterceptors(interceptor), ) ```