diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ae1f34d5a..6c9189a2549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108) - Add peer attributes to spans recorded by `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4873) +### Fixed + +- Do not panic in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` if `MeterProvider` returns a `nil` instrument. (#4875) + ## [1.22.0/0.47.0/0.16.0/0.2.0] - 2024-01-18 ### Added diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/config.go b/instrumentation/google.golang.org/grpc/otelgrpc/config.go index 67f8d733999..06282ce79c6 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/config.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/config.go @@ -18,6 +18,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" @@ -89,6 +90,9 @@ func newConfig(opts []Option, role string) *config { metric.WithUnit("ms")) if err != nil { otel.Handle(err) + if c.rpcDuration == nil { + c.rpcDuration = noop.Float64Histogram{} + } } c.rpcRequestSize, err = c.meter.Int64Histogram("rpc."+role+".request.size", @@ -96,6 +100,9 @@ func newConfig(opts []Option, role string) *config { metric.WithUnit("By")) if err != nil { otel.Handle(err) + if c.rpcRequestSize == nil { + c.rpcRequestSize = noop.Int64Histogram{} + } } c.rpcResponseSize, err = c.meter.Int64Histogram("rpc."+role+".response.size", @@ -103,6 +110,9 @@ func newConfig(opts []Option, role string) *config { metric.WithUnit("By")) if err != nil { otel.Handle(err) + if c.rpcResponseSize == nil { + c.rpcResponseSize = noop.Int64Histogram{} + } } c.rpcRequestsPerRPC, err = c.meter.Int64Histogram("rpc."+role+".requests_per_rpc", @@ -110,6 +120,9 @@ func newConfig(opts []Option, role string) *config { metric.WithUnit("{count}")) if err != nil { otel.Handle(err) + if c.rpcRequestsPerRPC == nil { + c.rpcRequestsPerRPC = noop.Int64Histogram{} + } } c.rpcResponsesPerRPC, err = c.meter.Int64Histogram("rpc."+role+".responses_per_rpc", @@ -117,6 +130,9 @@ func newConfig(opts []Option, role string) *config { metric.WithUnit("{count}")) if err != nil { otel.Handle(err) + if c.rpcResponsesPerRPC == nil { + c.rpcResponsesPerRPC = noop.Int64Histogram{} + } } return c diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/config_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/config_test.go new file mode 100644 index 00000000000..59467c24c24 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/otelgrpc/config_test.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otelgrpc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/embedded" +) + +func TestNilInstruments(t *testing.T) { + mp := meterProvider{} + c := newConfig([]Option{WithMeterProvider(mp)}, "test") + + ctx := context.Background() + assert.NotPanics(t, func() { c.rpcDuration.Record(ctx, 0) }, "rpcDuration") + assert.NotPanics(t, func() { c.rpcRequestSize.Record(ctx, 0) }, "rpcRequestSize") + assert.NotPanics(t, func() { c.rpcResponseSize.Record(ctx, 0) }, "rpcResponseSize") + assert.NotPanics(t, func() { c.rpcRequestsPerRPC.Record(ctx, 0) }, "rpcRequestsPerRPC") + assert.NotPanics(t, func() { c.rpcResponsesPerRPC.Record(ctx, 0) }, "rpcResponsesPerRPC") +} + +type meterProvider struct { + embedded.MeterProvider +} + +func (meterProvider) Meter(string, ...metric.MeterOption) metric.Meter { + return meter{} +} + +type meter struct { + // Panic for non-implemented methods. + metric.Meter +} + +func (meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { + return nil, assert.AnError +} + +func (meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { + return nil, assert.AnError +}