diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa4a7faa82d..9d722ed001c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,6 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: file: ./coverage.txt - fail_ci_if_error: true verbose: true - name: Store coverage test output uses: actions/upload-artifact@v4 diff --git a/.golangci.yml b/.golangci.yml index e46e4e37895..c6ce728bd4b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -23,7 +23,9 @@ linters: - staticcheck - tenv - typecheck + - unconvert - unused + - unparam issues: # Maximum issues count per one linter. diff --git a/bridge/opencensus/internal/ocmetric/metric.go b/bridge/opencensus/internal/ocmetric/metric.go index 4d053e8016d..f26f6bb2fb9 100644 --- a/bridge/opencensus/internal/ocmetric/metric.go +++ b/bridge/opencensus/internal/ocmetric/metric.go @@ -233,7 +233,7 @@ func convertKV(key string, value any) attribute.KeyValue { case uintptr: return uint64KV(key, uint64(typedVal)) case uint64: - return uint64KV(key, uint64(typedVal)) + return uint64KV(key, typedVal) case float32: return attribute.Float64(key, float64(typedVal)) case float64: diff --git a/exporters/otlp/otlplog/otlploggrpc/client.go b/exporters/otlp/otlplog/otlploggrpc/client.go index dcefef29c23..4f885bd417e 100644 --- a/exporters/otlp/otlplog/otlploggrpc/client.go +++ b/exporters/otlp/otlplog/otlploggrpc/client.go @@ -3,12 +3,144 @@ package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" +import ( + "time" + + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/encoding/gzip" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry" + collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1" +) + +// The methods of this type are not expected to be called concurrently. type client struct { - // TODO: implement. + metadata metadata.MD + exportTimeout time.Duration + requestFunc retry.RequestFunc + + // ourConn keeps track of where conn was created: true if created here in + // NewClient, or false if passed with an option. This is important on + // Shutdown as conn should only be closed if we created it. Otherwise, + // it is up to the processes that passed conn to close it. + ourConn bool + conn *grpc.ClientConn + lsc collogpb.LogsServiceClient } +// Used for testing. +var newGRPCClientFn = grpc.NewClient + // newClient creates a new gRPC log client. func newClient(cfg config) (*client, error) { - // TODO: implement. - return &client{}, nil + c := &client{ + exportTimeout: cfg.timeout.Value, + requestFunc: cfg.retryCfg.Value.RequestFunc(retryable), + conn: cfg.gRPCConn.Value, + } + + if len(cfg.headers.Value) > 0 { + c.metadata = metadata.New(cfg.headers.Value) + } + + if c.conn == nil { + // If the caller did not provide a ClientConn when the client was + // created, create one using the configuration they did provide. + dialOpts := newGRPCDialOptions(cfg) + + conn, err := newGRPCClientFn(cfg.endpoint.Value, dialOpts...) + if err != nil { + return nil, err + } + // Keep track that we own the lifecycle of this conn and need to close + // it on Shutdown. + c.ourConn = true + c.conn = conn + } + + c.lsc = collogpb.NewLogsServiceClient(c.conn) + + return c, nil +} + +func newGRPCDialOptions(cfg config) []grpc.DialOption { + userAgent := "OTel Go OTLP over gRPC logs exporter/" + Version() + dialOpts := []grpc.DialOption{grpc.WithUserAgent(userAgent)} + dialOpts = append(dialOpts, cfg.dialOptions.Value...) + + // Convert other grpc configs to the dial options. + // Service config + if cfg.serviceConfig.Value != "" { + dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(cfg.serviceConfig.Value)) + } + // Prioritize GRPCCredentials over Insecure (passing both is an error). + if cfg.gRPCCredentials.Value != nil { + dialOpts = append(dialOpts, grpc.WithTransportCredentials(cfg.gRPCCredentials.Value)) + } else if cfg.insecure.Value { + dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + // Default to using the host's root CA. + dialOpts = append(dialOpts, grpc.WithTransportCredentials( + credentials.NewTLS(nil), + )) + } + // Compression + if cfg.compression.Value == GzipCompression { + dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) + } + // Reconnection period + if cfg.reconnectionPeriod.Value != 0 { + p := grpc.ConnectParams{ + Backoff: backoff.DefaultConfig, + MinConnectTimeout: cfg.reconnectionPeriod.Value, + } + dialOpts = append(dialOpts, grpc.WithConnectParams(p)) + } + + return dialOpts +} + +// retryable returns if err identifies a request that can be retried and a +// duration to wait for if an explicit throttle time is included in err. +func retryable(err error) (bool, time.Duration) { + s := status.Convert(err) + return retryableGRPCStatus(s) +} + +func retryableGRPCStatus(s *status.Status) (bool, time.Duration) { + switch s.Code() { + case codes.Canceled, + codes.DeadlineExceeded, + codes.Aborted, + codes.OutOfRange, + codes.Unavailable, + codes.DataLoss: + // Additionally, handle RetryInfo. + _, d := throttleDelay(s) + return true, d + case codes.ResourceExhausted: + // Retry only if the server signals that the recovery from resource exhaustion is possible. + return throttleDelay(s) + } + + // Not a retry-able error. + return false, 0 +} + +// throttleDelay returns if the status is RetryInfo +// and the duration to wait for if an explicit throttle time is included. +func throttleDelay(s *status.Status) (bool, time.Duration) { + for _, detail := range s.Details() { + if t, ok := detail.(*errdetails.RetryInfo); ok { + return true, t.RetryDelay.AsDuration() + } + } + return false, 0 } diff --git a/exporters/otlp/otlplog/otlploggrpc/client_test.go b/exporters/otlp/otlplog/otlploggrpc/client_test.go new file mode 100644 index 00000000000..f3330dc4fbb --- /dev/null +++ b/exporters/otlp/otlplog/otlploggrpc/client_test.go @@ -0,0 +1,227 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/durationpb" + + collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1" + + "github.com/stretchr/testify/assert" +) + +func TestThrottleDelay(t *testing.T) { + c := codes.ResourceExhausted + testcases := []struct { + status *status.Status + wantOK bool + wantDuration time.Duration + }{ + { + status: status.New(c, "NoRetryInfo"), + wantOK: false, + wantDuration: 0, + }, + { + status: func() *status.Status { + s, err := status.New(c, "SingleRetryInfo").WithDetails( + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(15 * time.Millisecond), + }, + ) + require.NoError(t, err) + return s + }(), + wantOK: true, + wantDuration: 15 * time.Millisecond, + }, + { + status: func() *status.Status { + s, err := status.New(c, "ErrorInfo").WithDetails( + &errdetails.ErrorInfo{Reason: "no throttle detail"}, + ) + require.NoError(t, err) + return s + }(), + wantOK: false, + wantDuration: 0, + }, + { + status: func() *status.Status { + s, err := status.New(c, "ErrorAndRetryInfo").WithDetails( + &errdetails.ErrorInfo{Reason: "with throttle detail"}, + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(13 * time.Minute), + }, + ) + require.NoError(t, err) + return s + }(), + wantOK: true, + wantDuration: 13 * time.Minute, + }, + { + status: func() *status.Status { + s, err := status.New(c, "DoubleRetryInfo").WithDetails( + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(13 * time.Minute), + }, + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(15 * time.Minute), + }, + ) + require.NoError(t, err) + return s + }(), + wantOK: true, + wantDuration: 13 * time.Minute, + }, + } + + for _, tc := range testcases { + t.Run(tc.status.Message(), func(t *testing.T) { + ok, d := throttleDelay(tc.status) + assert.Equal(t, tc.wantOK, ok) + assert.Equal(t, tc.wantDuration, d) + }) + } +} + +func TestRetryable(t *testing.T) { + retryableCodes := map[codes.Code]bool{ + codes.OK: false, + codes.Canceled: true, + codes.Unknown: false, + codes.InvalidArgument: false, + codes.DeadlineExceeded: true, + codes.NotFound: false, + codes.AlreadyExists: false, + codes.PermissionDenied: false, + codes.ResourceExhausted: false, + codes.FailedPrecondition: false, + codes.Aborted: true, + codes.OutOfRange: true, + codes.Unimplemented: false, + codes.Internal: false, + codes.Unavailable: true, + codes.DataLoss: true, + codes.Unauthenticated: false, + } + + for c, want := range retryableCodes { + got, _ := retryable(status.Error(c, "")) + assert.Equalf(t, want, got, "evaluate(%s)", c) + } +} + +func TestRetryableGRPCStatusResourceExhaustedWithRetryInfo(t *testing.T) { + delay := 15 * time.Millisecond + s, err := status.New(codes.ResourceExhausted, "WithRetryInfo").WithDetails( + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(delay), + }, + ) + require.NoError(t, err) + + ok, d := retryableGRPCStatus(s) + assert.True(t, ok) + assert.Equal(t, delay, d) +} + +func TestNewClient(t *testing.T) { + newGRPCClientFnSwap := newGRPCClientFn + t.Cleanup(func() { + newGRPCClientFn = newGRPCClientFnSwap + }) + + // The gRPC connection created by newClient. + conn, err := grpc.NewClient("test", grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + newGRPCClientFn = func(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { + return conn, nil + } + + // The gRPC connection created by users. + userConn, err := grpc.NewClient("test 2", grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + + testCases := []struct { + name string + cfg config + cli *client + }{ + { + name: "empty config", + cli: &client{ + ourConn: true, + conn: conn, + lsc: collogpb.NewLogsServiceClient(conn), + }, + }, + { + name: "with headers", + cfg: config{ + headers: newSetting(map[string]string{ + "key": "value", + }), + }, + cli: &client{ + ourConn: true, + conn: conn, + lsc: collogpb.NewLogsServiceClient(conn), + metadata: map[string][]string{"key": {"value"}}, + }, + }, + { + name: "with gRPC connection", + cfg: config{ + gRPCConn: newSetting(userConn), + }, + cli: &client{ + ourConn: false, + conn: userConn, + lsc: collogpb.NewLogsServiceClient(userConn), + }, + }, + { + // It is not possible to compare grpc dial options directly, so we just check that the client is created + // and no panic occurs. + name: "with dial options", + cfg: config{ + serviceConfig: newSetting("service config"), + gRPCCredentials: newSetting(credentials.NewTLS(nil)), + compression: newSetting(GzipCompression), + reconnectionPeriod: newSetting(10 * time.Second), + }, + cli: &client{ + ourConn: true, + conn: conn, + lsc: collogpb.NewLogsServiceClient(conn), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cli, err := newClient(tc.cfg) + require.NoError(t, err) + + assert.Equal(t, tc.cli.metadata, cli.metadata) + assert.Equal(t, tc.cli.exportTimeout, cli.exportTimeout) + assert.Equal(t, tc.cli.ourConn, cli.ourConn) + assert.Equal(t, tc.cli.conn, cli.conn) + assert.Equal(t, tc.cli.lsc, cli.lsc) + }) + } +} diff --git a/exporters/otlp/otlplog/otlploggrpc/go.mod b/exporters/otlp/otlplog/otlploggrpc/go.mod index 8865928453c..3eee3d4cb80 100644 --- a/exporters/otlp/otlplog/otlploggrpc/go.mod +++ b/exporters/otlp/otlplog/otlploggrpc/go.mod @@ -7,7 +7,10 @@ require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.27.0 go.opentelemetry.io/otel/sdk/log v0.3.0 + go.opentelemetry.io/proto/otlp v1.3.1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.2 ) require ( @@ -15,7 +18,10 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect go.opentelemetry.io/otel/log v0.3.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect @@ -23,8 +29,7 @@ require ( golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/exporters/otlp/otlplog/otlploggrpc/go.sum b/exporters/otlp/otlplog/otlploggrpc/go.sum index 5ec988d1466..bcb38418237 100644 --- a/exporters/otlp/otlplog/otlploggrpc/go.sum +++ b/exporters/otlp/otlplog/otlploggrpc/go.sum @@ -1,5 +1,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -11,23 +12,36 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go index a24a9544556..8b935722d10 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go @@ -376,7 +376,7 @@ func TestConfigs(t *testing.T) { { name: "Test With Timeout", opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), + WithTimeout(5 * time.Second), }, asserts: func(t *testing.T, c *Config, grpcOption bool) { assert.Equal(t, 5*time.Second, c.Metrics.Timeout) diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/metricdata.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/metricdata.go index 669e25e8e95..975e3b7aa1a 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/metricdata.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform/metricdata.go @@ -79,7 +79,7 @@ func metric(m metricdata.Metrics) (*mpb.Metric, error) { out := &mpb.Metric{ Name: m.Name, Description: m.Description, - Unit: string(m.Unit), + Unit: m.Unit, } switch a := m.Data.(type) { case metricdata.Gauge[int64]: diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go index 3401f8ec524..b9bdb1c006a 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go @@ -376,7 +376,7 @@ func TestConfigs(t *testing.T) { { name: "Test With Timeout", opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), + WithTimeout(5 * time.Second), }, asserts: func(t *testing.T, c *Config, grpcOption bool) { assert.Equal(t, 5*time.Second, c.Metrics.Timeout) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go index 04c2ce75704..0a1a65c44d2 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go @@ -79,7 +79,7 @@ func metric(m metricdata.Metrics) (*mpb.Metric, error) { out := &mpb.Metric{ Name: m.Name, Description: m.Description, - Unit: string(m.Unit), + Unit: m.Unit, } switch a := m.Data.(type) { case metricdata.Gauge[int64]: diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go index 87fd281dfc5..715c04fd6b3 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go @@ -372,7 +372,7 @@ func TestConfigs(t *testing.T) { { name: "Test With Timeout", opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), + WithTimeout(5 * time.Second), }, asserts: func(t *testing.T, c *Config, grpcOption bool) { assert.Equal(t, 5*time.Second, c.Traces.Timeout) diff --git a/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go b/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go index 1df421a34dc..1a4e5aab110 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go +++ b/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go @@ -372,7 +372,7 @@ func TestConfigs(t *testing.T) { { name: "Test With Timeout", opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), + WithTimeout(5 * time.Second), }, asserts: func(t *testing.T, c *Config, grpcOption bool) { assert.Equal(t, 5*time.Second, c.Traces.Timeout) diff --git a/exporters/stdout/stdouttrace/config.go b/exporters/stdout/stdouttrace/config.go index 3f60e3ef089..0ba3424e295 100644 --- a/exporters/stdout/stdouttrace/config.go +++ b/exporters/stdout/stdouttrace/config.go @@ -29,7 +29,7 @@ type config struct { } // newConfig creates a validated Config configured with options. -func newConfig(options ...Option) (config, error) { +func newConfig(options ...Option) config { cfg := config{ Writer: defaultWriter, PrettyPrint: defaultPrettyPrint, @@ -38,7 +38,7 @@ func newConfig(options ...Option) (config, error) { for _, opt := range options { cfg = opt.apply(cfg) } - return cfg, nil + return cfg } // Option sets the value of an option for a Config. diff --git a/exporters/stdout/stdouttrace/trace.go b/exporters/stdout/stdouttrace/trace.go index b19eb0dfbed..bdb915ba803 100644 --- a/exporters/stdout/stdouttrace/trace.go +++ b/exporters/stdout/stdouttrace/trace.go @@ -19,10 +19,7 @@ var _ trace.SpanExporter = &Exporter{} // New creates an Exporter with the passed options. func New(options ...Option) (*Exporter, error) { - cfg, err := newConfig(options...) - if err != nil { - return nil, err - } + cfg := newConfig(options...) enc := json.NewEncoder(cfg.Writer) if cfg.PrettyPrint { diff --git a/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl index f266628e2ae..22843320dc1 100644 --- a/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl @@ -376,7 +376,7 @@ func TestConfigs(t *testing.T) { { name: "Test With Timeout", opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), + WithTimeout(5 * time.Second), }, asserts: func(t *testing.T, c *Config, grpcOption bool) { assert.Equal(t, 5*time.Second, c.Metrics.Timeout) diff --git a/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl b/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl index b6d0b76fa1f..1e1edc4f872 100644 --- a/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl @@ -79,7 +79,7 @@ func metric(m metricdata.Metrics) (*mpb.Metric, error) { out := &mpb.Metric{ Name: m.Name, Description: m.Description, - Unit: string(m.Unit), + Unit: m.Unit, } switch a := m.Data.(type) { case metricdata.Gauge[int64]: diff --git a/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl index 2a25c103e4d..8d670a5aa9a 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl @@ -372,7 +372,7 @@ func TestConfigs(t *testing.T) { { name: "Test With Timeout", opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), + WithTimeout(5 * time.Second), }, asserts: func(t *testing.T, c *Config, grpcOption bool) { assert.Equal(t, 5*time.Second, c.Traces.Timeout) diff --git a/log/keyvalue_test.go b/log/keyvalue_test.go index 043fab09dc1..d79b5686a8b 100644 --- a/log/keyvalue_test.go +++ b/log/keyvalue_test.go @@ -131,9 +131,9 @@ func TestEmptyGroupsPreserved(t *testing.T) { } func TestBool(t *testing.T) { - const key, val = "key", true + const key, val = "boolKey", true kv := log.Bool(key, val) - testKV(t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindBool t.Run("AsBool", func(t *testing.T) { @@ -148,9 +148,9 @@ func TestBool(t *testing.T) { } func TestFloat64(t *testing.T) { - const key, val = "key", 3.0 + const key, val = "float64Key", 3.0 kv := log.Float64(key, val) - testKV(t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindFloat64 t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -165,9 +165,9 @@ func TestFloat64(t *testing.T) { } func TestInt(t *testing.T) { - const key, val = "key", 1 + const key, val = "intKey", 1 kv := log.Int(key, val) - testKV[int64](t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindInt64 t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -182,9 +182,9 @@ func TestInt(t *testing.T) { } func TestInt64(t *testing.T) { - const key, val = "key", 1 + const key, val = "int64Key", 1 kv := log.Int64(key, val) - testKV[int64](t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindInt64 t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -199,9 +199,9 @@ func TestInt64(t *testing.T) { } func TestString(t *testing.T) { - const key, val = "key", "test string value" + const key, val = "stringKey", "test string value" kv := log.String(key, val) - testKV(t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindString t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -216,10 +216,10 @@ func TestString(t *testing.T) { } func TestBytes(t *testing.T) { - const key = "key" + const key = "bytesKey" val := []byte{3, 2, 1} kv := log.Bytes(key, val) - testKV(t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindBytes t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -234,10 +234,10 @@ func TestBytes(t *testing.T) { } func TestSlice(t *testing.T) { - const key = "key" + const key = "sliceKey" val := []log.Value{log.IntValue(3), log.StringValue("foo")} kv := log.Slice(key, val...) - testKV(t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindSlice t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -252,13 +252,13 @@ func TestSlice(t *testing.T) { } func TestMap(t *testing.T) { - const key = "key" + const key = "mapKey" val := []log.KeyValue{ log.Slice("l", log.IntValue(3), log.StringValue("foo")), log.Bytes("b", []byte{3, 5, 7}), } kv := log.Map(key, val...) - testKV(t, key, val, kv) + testKV(t, key, kv) v, k := kv.Value, log.KindMap t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) @@ -339,7 +339,7 @@ func testErrKind[T any](f func() T, msg string, k log.Kind) func(*testing.T) { } } -func testKV[T any](t *testing.T, key string, val T, kv log.KeyValue) { +func testKV(t *testing.T, key string, kv log.KeyValue) { t.Helper() assert.Equal(t, key, kv.Key, "incorrect key") diff --git a/metric/example_test.go b/metric/example_test.go index 162b206e245..acb5c70f5f9 100644 --- a/metric/example_test.go +++ b/metric/example_test.go @@ -7,6 +7,7 @@ import ( "context" "database/sql" "fmt" + "math/rand" "net/http" "runtime" "time" @@ -142,6 +143,45 @@ func ExampleMeter_upDownCounter() { } } +// Gauges can be used to record non-additive values when changes occur. +// +// Here's how you might report the current speed of a cpu fan. +func ExampleMeter_gauge() { + speedGauge, err := meter.Int64Gauge( + "cpu.fan.speed", + metric.WithDescription("Speed of CPU fan"), + metric.WithUnit("RPM"), + ) + if err != nil { + panic(err) + } + + getCPUFanSpeed := func() int64 { + // Generates a random fan speed for demonstration purpose. + // In real world applications, replace this to get the actual fan speed. + return int64(1500 + rand.Intn(1000)) + } + + fanSpeedSubscription := make(chan int64, 1) + go func() { + defer close(fanSpeedSubscription) + + for idx := 0; idx < 5; idx++ { + // Synchronous gauges are used when the measurement cycle is + // synchronous to an external change. + // Simulate that external cycle here. + time.Sleep(time.Duration(rand.Intn(3)) * time.Second) + fanSpeed := getCPUFanSpeed() + fanSpeedSubscription <- fanSpeed + } + }() + + ctx := context.Background() + for fanSpeed := range fanSpeedSubscription { + speedGauge.Record(ctx, fanSpeed) + } +} + // Histograms are used to measure a distribution of values over time. // // Here's how you might report a distribution of response times for an HTTP handler. diff --git a/sdk/internal/x/x_test.go b/sdk/internal/x/x_test.go index 26a7e964276..0c03f2fdb1f 100644 --- a/sdk/internal/x/x_test.go +++ b/sdk/internal/x/x_test.go @@ -31,7 +31,7 @@ func run(steps ...func(*testing.T)) func(*testing.T) { } } -func setenv(k, v string) func(t *testing.T) { +func setenv(k, v string) func(t *testing.T) { //nolint:unparam return func(t *testing.T) { t.Setenv(k, v) } } diff --git a/sdk/metric/internal/exemplar/reservoir_test.go b/sdk/metric/internal/exemplar/reservoir_test.go index f6d2d884453..b5fc5453d42 100644 --- a/sdk/metric/internal/exemplar/reservoir_test.go +++ b/sdk/metric/internal/exemplar/reservoir_test.go @@ -50,8 +50,8 @@ func ReservoirTest[N int64 | float64](f factory) func(*testing.T) { want := Exemplar{ Time: staticTime, Value: NewValue(N(10)), - SpanID: []byte(sID[:]), - TraceID: []byte(tID[:]), + SpanID: sID[:], + TraceID: tID[:], } require.Len(t, dest, 1, "number of collected exemplars") assert.Equal(t, want, dest[0]) diff --git a/sdk/trace/batch_span_processor.go b/sdk/trace/batch_span_processor.go index 8a89fffdb4a..1d399a75db2 100644 --- a/sdk/trace/batch_span_processor.go +++ b/sdk/trace/batch_span_processor.go @@ -381,7 +381,7 @@ func (bsp *batchSpanProcessor) enqueueBlockOnQueueFull(ctx context.Context, sd R } } -func (bsp *batchSpanProcessor) enqueueDrop(ctx context.Context, sd ReadOnlySpan) bool { +func (bsp *batchSpanProcessor) enqueueDrop(_ context.Context, sd ReadOnlySpan) bool { if !sd.SpanContext().IsSampled() { return false } diff --git a/sdk/trace/batch_span_processor_test.go b/sdk/trace/batch_span_processor_test.go index d34643d0851..85eef8984dc 100644 --- a/sdk/trace/batch_span_processor_test.go +++ b/sdk/trace/batch_span_processor_test.go @@ -340,7 +340,7 @@ func createAndRegisterBatchSP(option testOption, te *testBatchExporter) sdktrace return sdktrace.NewBatchSpanProcessor(te, options...) } -func generateSpan(t *testing.T, tr trace.Tracer, option testOption) { +func generateSpan(_ *testing.T, tr trace.Tracer, option testOption) { sc := getSpanContext() for i := 0; i < option.genNumSpans; i++ { @@ -353,7 +353,7 @@ func generateSpan(t *testing.T, tr trace.Tracer, option testOption) { } } -func generateSpanParallel(t *testing.T, tr trace.Tracer, option testOption) { +func generateSpanParallel(_ *testing.T, tr trace.Tracer, option testOption) { sc := getSpanContext() wg := &sync.WaitGroup{} diff --git a/sdk/trace/benchmark_test.go b/sdk/trace/benchmark_test.go index d51d6bf8a53..3582f0ddb8e 100644 --- a/sdk/trace/benchmark_test.go +++ b/sdk/trace/benchmark_test.go @@ -321,7 +321,7 @@ func traceBenchmark(b *testing.B, name string, fn func(*testing.B, trace.Tracer) }) } -func tracer(b *testing.B, name string, sampler sdktrace.Sampler) trace.Tracer { +func tracer(_ *testing.B, name string, sampler sdktrace.Sampler) trace.Tracer { tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sampler)) return tp.Tracer(name) } diff --git a/sdk/trace/util_test.go b/sdk/trace/util_test.go index 470f635bd8f..6ae195977a2 100644 --- a/sdk/trace/util_test.go +++ b/sdk/trace/util_test.go @@ -9,7 +9,7 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" ) -func basicTracerProvider(t *testing.T) *sdktrace.TracerProvider { +func basicTracerProvider(_ *testing.T) *sdktrace.TracerProvider { tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) return tp } diff --git a/semconv/internal/v2/net.go b/semconv/internal/v2/net.go index 45a1b06acf2..aa9e1017156 100644 --- a/semconv/internal/v2/net.go +++ b/semconv/internal/v2/net.go @@ -61,7 +61,7 @@ func (c *NetConv) Host(address string) []attribute.KeyValue { attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { - attrs = append(attrs, c.HostPort(int(p))) + attrs = append(attrs, c.HostPort(p)) } return attrs } @@ -252,7 +252,7 @@ func (c *NetConv) Peer(address string) []attribute.KeyValue { attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { - attrs = append(attrs, c.PeerPort(int(p))) + attrs = append(attrs, c.PeerPort(p)) } return attrs } diff --git a/semconv/internal/v3/net.go b/semconv/internal/v3/net.go index 86e5426057b..329588d9746 100644 --- a/semconv/internal/v3/net.go +++ b/semconv/internal/v3/net.go @@ -61,7 +61,7 @@ func (c *NetConv) Host(address string) []attribute.KeyValue { attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { - attrs = append(attrs, c.HostPort(int(p))) + attrs = append(attrs, c.HostPort(p)) } return attrs } @@ -252,7 +252,7 @@ func (c *NetConv) Peer(address string) []attribute.KeyValue { attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { - attrs = append(attrs, c.PeerPort(int(p))) + attrs = append(attrs, c.PeerPort(p)) } return attrs } diff --git a/semconv/internal/v4/net.go b/semconv/internal/v4/net.go index 65db0cb61fc..ff24e69e06e 100644 --- a/semconv/internal/v4/net.go +++ b/semconv/internal/v4/net.go @@ -61,7 +61,7 @@ func (c *NetConv) Host(address string) []attribute.KeyValue { attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { - attrs = append(attrs, c.HostPort(int(p))) + attrs = append(attrs, c.HostPort(p)) } return attrs } @@ -252,7 +252,7 @@ func (c *NetConv) Peer(address string) []attribute.KeyValue { attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { - attrs = append(attrs, c.PeerPort(int(p))) + attrs = append(attrs, c.PeerPort(p)) } return attrs }