diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d17e5855d..d326b38e4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -266,6 +266,16 @@ updates: schedule: day: sunday interval: weekly + - + package-ecosystem: gomod + directory: /exporters/otlp/internal/retry + labels: + - dependencies + - go + - "Skip Changelog" + schedule: + day: sunday + interval: weekly - package-ecosystem: gomod directory: /exporters/otlp/otlptrace diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d083f219..6f2041652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,30 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- Added an internal Logger. + This can be used by the SDK and API to provide users with feedback of the internal state. + To enable verbose logs configure the logger which will print V(1) logs. For debugging information configure to print V(5) logs. (#2343) +- Add the `WithRetry` `Option` and the `RetryConfig` type to the `go.opentelemetry.io/otel/exporter/otel/otlpmetric/otlpmetrichttp` package to specify retry behavior consistently. (#2425) + ### Changed - The `"go.opentelemetry.io/otel/exporter/otel/otlptrace/otlptracegrpc".Client` now uses the underlying gRPC `ClientConn` to handle name resolution, TCP connection establishment (with retries and backoff) and TLS handshakes, and handling errors on established connections by re-resolving the name and reconnecting. (#2329) - Changed the project minimum supported Go version from 1.15 to 1.16. (#2412) +- The `"go.opentelemetry.io/otel/exporter/otel/otlpmetric/otlpmetricgrpc".Client` now uses the underlying gRPC `ClientConn` to handle name resolution, TCP connection establishment (with retries and backoff) and TLS handshakes, and handling errors on established connections by re-resolving the name and reconnecting. (#2425) +- The `"go.opentelemetry.io/otel/exporter/otel/otlpmetric/otlpmetricgrpc".RetrySettings` type is renamed to `RetryConfig`. (#2425) + +### Deprecated + +- Deprecated the `"go.opentelemetry.io/otel/exporter/otel/otlpmetric/otlpmetrichttp".WithMaxAttempts` `Option`, use the new `WithRetry` `Option` instead. (#2425) +- Deprecated the `"go.opentelemetry.io/otel/exporter/otel/otlpmetric/otlpmetrichttp".WithBackoff` `Option`, use the new `WithRetry` `Option` instead. (#2425) ### Removed - Remove the metric Processor's ability to convert cumulative to delta aggregation temporality. (#2350) - Remove the metric Bound Instruments interface and implementations. (#2399) +- Remove the metric MinMaxSumCount kind aggregation and the corresponding OTLP export path. (#2423) ## [1.2.0] - 2021-11-12 diff --git a/bridge/opencensus/go.mod b/bridge/opencensus/go.mod index bda914818..0fc1be817 100644 --- a/bridge/opencensus/go.mod +++ b/bridge/opencensus/go.mod @@ -75,3 +75,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ./test replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/bridge/opencensus/go.sum b/bridge/opencensus/go.sum index 4486a5c44..242e03d18 100644 --- a/bridge/opencensus/go.sum +++ b/bridge/opencensus/go.sum @@ -5,6 +5,11 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/bridge/opencensus/test/go.mod b/bridge/opencensus/test/go.mod index 0cd9c971a..8899c89c8 100644 --- a/bridge/opencensus/test/go.mod +++ b/bridge/opencensus/test/go.mod @@ -71,3 +71,5 @@ replace go.opentelemetry.io/otel/trace => ../../../trace replace go.opentelemetry.io/otel/example/fib => ../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../../exporters/otlp/internal/retry diff --git a/bridge/opencensus/test/go.sum b/bridge/opencensus/test/go.sum index 2ea966dea..98de68494 100644 --- a/bridge/opencensus/test/go.sum +++ b/bridge/opencensus/test/go.sum @@ -11,6 +11,11 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= diff --git a/bridge/opentracing/go.mod b/bridge/opentracing/go.mod index 080c29bf5..5f883617c 100644 --- a/bridge/opentracing/go.mod +++ b/bridge/opentracing/go.mod @@ -71,3 +71,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../opencensus/test replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/bridge/opentracing/go.sum b/bridge/opentracing/go.sum index c8374cada..df3b15e6a 100644 --- a/bridge/opentracing/go.sum +++ b/bridge/opentracing/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= diff --git a/example/fib/go.mod b/example/fib/go.mod index a3b64a01e..99374884d 100644 --- a/example/fib/go.mod +++ b/example/fib/go.mod @@ -70,3 +70,5 @@ replace go.opentelemetry.io/otel/trace => ../../trace replace go.opentelemetry.io/otel/example/fib => ./ replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/fib/go.sum b/example/fib/go.sum index ee185b2c2..f550e8d32 100644 --- a/example/fib/go.sum +++ b/example/fib/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/example/jaeger/go.mod b/example/jaeger/go.mod index ee93a970e..19781c6bd 100644 --- a/example/jaeger/go.mod +++ b/example/jaeger/go.mod @@ -71,3 +71,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/jaeger/go.sum b/example/jaeger/go.sum index 0c4ddc5ee..75667504d 100644 --- a/example/jaeger/go.sum +++ b/example/jaeger/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/example/namedtracer/go.mod b/example/namedtracer/go.mod index 9957f9ff5..46b094007 100644 --- a/example/namedtracer/go.mod +++ b/example/namedtracer/go.mod @@ -8,6 +8,7 @@ replace ( ) require ( + github.com/go-logr/stdr v1.2.0 go.opentelemetry.io/otel v1.2.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0 go.opentelemetry.io/otel/sdk v1.2.0 @@ -73,3 +74,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/namedtracer/go.sum b/example/namedtracer/go.sum index ee185b2c2..f550e8d32 100644 --- a/example/namedtracer/go.sum +++ b/example/namedtracer/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/example/namedtracer/main.go b/example/namedtracer/main.go index ac96316c6..a091e88f6 100644 --- a/example/namedtracer/main.go +++ b/example/namedtracer/main.go @@ -18,6 +18,8 @@ import ( "context" "log" + "github.com/go-logr/stdr" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" @@ -52,6 +54,9 @@ func initTracer() { } func main() { + // Set logging level to info to see SDK status messages + stdr.SetVerbosity(1) + // initialize trace provider. initTracer() diff --git a/example/opencensus/go.mod b/example/opencensus/go.mod index 16ece919f..732cf4926 100644 --- a/example/opencensus/go.mod +++ b/example/opencensus/go.mod @@ -75,3 +75,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/opencensus/go.sum b/example/opencensus/go.sum index cd91ca6fa..4bf4049d1 100644 --- a/example/opencensus/go.sum +++ b/example/opencensus/go.sum @@ -5,6 +5,11 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/example/otel-collector/go.mod b/example/otel-collector/go.mod index 3979adf40..8712f8cf9 100644 --- a/example/otel-collector/go.mod +++ b/example/otel-collector/go.mod @@ -74,3 +74,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/otel-collector/go.sum b/example/otel-collector/go.sum index a07f3f683..3da524272 100644 --- a/example/otel-collector/go.sum +++ b/example/otel-collector/go.sum @@ -22,6 +22,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/example/passthrough/go.mod b/example/passthrough/go.mod index 2ef80d6db..956a4c3b6 100644 --- a/example/passthrough/go.mod +++ b/example/passthrough/go.mod @@ -74,3 +74,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/passthrough/go.sum b/example/passthrough/go.sum index ee185b2c2..f550e8d32 100644 --- a/example/passthrough/go.sum +++ b/example/passthrough/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/example/prometheus/go.mod b/example/prometheus/go.mod index e960ef7fa..138292d7d 100644 --- a/example/prometheus/go.mod +++ b/example/prometheus/go.mod @@ -73,3 +73,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/prometheus/go.sum b/example/prometheus/go.sum index 2d76fedbf..6f9c6f280 100644 --- a/example/prometheus/go.sum +++ b/example/prometheus/go.sum @@ -21,6 +21,11 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/example/zipkin/go.mod b/example/zipkin/go.mod index 4b5a05030..b77dc4e64 100644 --- a/example/zipkin/go.mod +++ b/example/zipkin/go.mod @@ -72,3 +72,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/example/zipkin/go.sum b/example/zipkin/go.sum index 55f5ed6d7..6a383ef74 100644 --- a/example/zipkin/go.sum +++ b/example/zipkin/go.sum @@ -29,6 +29,11 @@ github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/exporters/jaeger/go.mod b/exporters/jaeger/go.mod index 321f4570f..45d76ccf0 100644 --- a/exporters/jaeger/go.mod +++ b/exporters/jaeger/go.mod @@ -30,8 +30,6 @@ replace go.opentelemetry.io/otel/example/zipkin => ../../example/zipkin replace go.opentelemetry.io/otel/exporters/prometheus => ../prometheus -replace go.opentelemetry.io/otel/exporters/otlp => ../otlp - replace go.opentelemetry.io/otel/exporters/jaeger => ./ replace go.opentelemetry.io/otel/internal/tools => ../../internal/tools @@ -75,3 +73,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../otlp/internal/retry diff --git a/exporters/jaeger/go.sum b/exporters/jaeger/go.sum index f0e2991c1..04abb6863 100644 --- a/exporters/jaeger/go.sum +++ b/exporters/jaeger/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/exporters/otlp/internal/retry/go.mod b/exporters/otlp/internal/retry/go.mod new file mode 100644 index 000000000..8e064415d --- /dev/null +++ b/exporters/otlp/internal/retry/go.mod @@ -0,0 +1,72 @@ +module go.opentelemetry.io/otel/exporters/otlp/internal/retry + +go 1.16 + +require ( + github.com/cenkalti/backoff/v4 v4.1.2 + github.com/stretchr/testify v1.7.0 +) + +replace go.opentelemetry.io/otel => ../../../.. + +replace go.opentelemetry.io/otel/bridge/opencensus => ../../../../bridge/opencensus + +replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/opencensus/test + +replace go.opentelemetry.io/otel/bridge/opentracing => ../../../../bridge/opentracing + +replace go.opentelemetry.io/otel/example/fib => ../../../../example/fib + +replace go.opentelemetry.io/otel/example/jaeger => ../../../../example/jaeger + +replace go.opentelemetry.io/otel/example/namedtracer => ../../../../example/namedtracer + +replace go.opentelemetry.io/otel/example/opencensus => ../../../../example/opencensus + +replace go.opentelemetry.io/otel/example/otel-collector => ../../../../example/otel-collector + +replace go.opentelemetry.io/otel/example/passthrough => ../../../../example/passthrough + +replace go.opentelemetry.io/otel/example/prometheus => ../../../../example/prometheus + +replace go.opentelemetry.io/otel/example/zipkin => ../../../../example/zipkin + +replace go.opentelemetry.io/otel/exporters/jaeger => ../../../jaeger + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ./ + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../../otlpmetric + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ../../otlpmetric/otlpmetricgrpc + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp => ../../otlpmetric/otlpmetrichttp + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../otlptrace + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../../otlptrace/otlptracegrpc + +replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => ../../otlptrace/otlptracehttp + +replace go.opentelemetry.io/otel/exporters/prometheus => ../../../prometheus + +replace go.opentelemetry.io/otel/exporters/stdout/stdoutmetric => ../../../stdout/stdoutmetric + +replace go.opentelemetry.io/otel/exporters/stdout/stdouttrace => ../../../stdout/stdouttrace + +replace go.opentelemetry.io/otel/exporters/zipkin => ../../../zipkin + +replace go.opentelemetry.io/otel/internal/metric => ../../../../internal/metric + +replace go.opentelemetry.io/otel/internal/tools => ../../../../internal/tools + +replace go.opentelemetry.io/otel/metric => ../../../../metric + +replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/sdk => ../../../../sdk + +replace go.opentelemetry.io/otel/sdk/export/metric => ../../../../sdk/export/metric + +replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric + +replace go.opentelemetry.io/otel/trace => ../../../../trace diff --git a/exporters/otlp/internal/retry/go.sum b/exporters/otlp/internal/retry/go.sum new file mode 100644 index 000000000..de50e3411 --- /dev/null +++ b/exporters/otlp/internal/retry/go.sum @@ -0,0 +1,13 @@ +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/exporters/otlp/otlptrace/internal/retry/retry.go b/exporters/otlp/internal/retry/retry.go similarity index 84% rename from exporters/otlp/otlptrace/internal/retry/retry.go rename to exporters/otlp/internal/retry/retry.go index 37b9dabe5..3d43f7aea 100644 --- a/exporters/otlp/otlptrace/internal/retry/retry.go +++ b/exporters/otlp/internal/retry/retry.go @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package retry // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/retry" +// Package retry provides request retry functionality that can perform +// configurable exponential backoff for transient errors and honor any +// explicit throttle responses received. +package retry // import "go.opentelemetry.io/otel/exporters/otlp/internal/retry" import ( "context" @@ -54,8 +57,18 @@ type RequestFunc func(context.Context, func(context.Context) error) error // EvaluateFunc returns if an error is retry-able and if an explicit throttle // duration should be honored that was included in the error. +// +// The function must return true if the error argument is retry-able, +// otherwise it must return false for the first return parameter. +// +// The function must return a non-zero time.Duration if the error contains +// explicit throttle duration that should be honored, otherwise it must return +// a zero valued time.Duration. type EvaluateFunc func(error) (bool, time.Duration) +// RequestFunc returns a RequestFunc using the evaluate function to determine +// if requests can be retried and based on the exponential backoff +// configuration of c. func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { if !c.Enabled { return func(ctx context.Context, fn func(context.Context) error) error { diff --git a/exporters/otlp/otlptrace/internal/retry/retry_test.go b/exporters/otlp/internal/retry/retry_test.go similarity index 100% rename from exporters/otlp/otlptrace/internal/retry/retry_test.go rename to exporters/otlp/internal/retry/retry_test.go diff --git a/exporters/otlp/otlpmetric/go.mod b/exporters/otlp/otlpmetric/go.mod index bebf80f77..bc7d3828f 100644 --- a/exporters/otlp/otlpmetric/go.mod +++ b/exporters/otlp/otlpmetric/go.mod @@ -3,16 +3,15 @@ module go.opentelemetry.io/otel/exporters/otlp/otlpmetric go 1.16 require ( - github.com/cenkalti/backoff/v4 v4.1.2 github.com/google/go-cmp v0.5.6 github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/metric v0.25.0 go.opentelemetry.io/otel/sdk v1.2.0 go.opentelemetry.io/otel/sdk/export/metric v0.25.0 go.opentelemetry.io/otel/sdk/metric v0.25.0 go.opentelemetry.io/proto/otlp v0.11.0 - google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 google.golang.org/grpc v1.42.0 google.golang.org/protobuf v1.27.1 ) @@ -21,8 +20,6 @@ replace go.opentelemetry.io/otel => ../../.. replace go.opentelemetry.io/otel/sdk => ../../../sdk -replace go.opentelemetry.io/otel/exporters/otlp => ../ - replace go.opentelemetry.io/otel/metric => ../../../metric replace go.opentelemetry.io/otel/trace => ../../../trace @@ -45,12 +42,12 @@ replace go.opentelemetry.io/otel/example/otel-collector => ../../../example/otel replace go.opentelemetry.io/otel/example/passthrough => ../../../example/passthrough -replace go.opentelemetry.io/otel/example/prom-collector => ../../../example/prom-collector - replace go.opentelemetry.io/otel/example/prometheus => ../../../example/prometheus replace go.opentelemetry.io/otel/example/zipkin => ../../../example/zipkin +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../internal/retry + replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ./ replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc => ./otlpmetricgrpc diff --git a/exporters/otlp/otlpmetric/go.sum b/exporters/otlp/otlpmetric/go.sum index abf3eb08c..31b24cd6e 100644 --- a/exporters/otlp/otlpmetric/go.sum +++ b/exporters/otlp/otlpmetric/go.sum @@ -24,6 +24,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/otlp/otlpmetric/internal/connection/connection.go b/exporters/otlp/otlpmetric/internal/connection/connection.go deleted file mode 100644 index dcb6db403..000000000 --- a/exporters/otlp/otlpmetric/internal/connection/connection.go +++ /dev/null @@ -1,431 +0,0 @@ -// 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 connection // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/connection" - -import ( - "context" - "fmt" - "math/rand" - "sync" - "sync/atomic" - "time" - "unsafe" - - "github.com/cenkalti/backoff/v4" - "google.golang.org/genproto/googleapis/rpc/errdetails" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/encoding/gzip" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" -) - -type Connection struct { - // Ensure pointer is 64-bit aligned for atomic operations on both 32 and 64 bit machines. - lastConnectErrPtr unsafe.Pointer - - // mu protects the Connection as it is accessed by the - // exporter goroutines and background Connection goroutine - mu sync.Mutex - cc *grpc.ClientConn - - // these fields are read-only after constructor is finished - cfg otlpconfig.Config - SCfg otlpconfig.SignalConfig - metadata metadata.MD - newConnectionHandler func(cc *grpc.ClientConn) - - // these channels are created once - disconnectedCh chan bool - backgroundConnectionDoneCh chan struct{} - stopCh chan struct{} - - // this is for tests, so they can replace the closing - // routine without a worry of modifying some global variable - // or changing it back to original after the test is done - closeBackgroundConnectionDoneCh func(ch chan struct{}) -} - -func NewConnection(cfg otlpconfig.Config, sCfg otlpconfig.SignalConfig, handler func(cc *grpc.ClientConn)) *Connection { - c := new(Connection) - c.newConnectionHandler = handler - c.cfg = cfg - c.SCfg = sCfg - if len(c.SCfg.Headers) > 0 { - c.metadata = metadata.New(c.SCfg.Headers) - } - c.closeBackgroundConnectionDoneCh = func(ch chan struct{}) { - close(ch) - } - return c -} - -func (c *Connection) StartConnection(ctx context.Context) error { - c.stopCh = make(chan struct{}) - c.disconnectedCh = make(chan bool, 1) - c.backgroundConnectionDoneCh = make(chan struct{}) - - if err := c.connect(ctx); err == nil { - c.setStateConnected() - } else { - c.SetStateDisconnected(err) - } - go c.indefiniteBackgroundConnection() - - // TODO: proper error handling when initializing connections. - // We can report permanent errors, e.g., invalid settings. - return nil -} - -func (c *Connection) LastConnectError() error { - errPtr := (*error)(atomic.LoadPointer(&c.lastConnectErrPtr)) - if errPtr == nil { - return nil - } - return *errPtr -} - -func (c *Connection) saveLastConnectError(err error) { - var errPtr *error - if err != nil { - errPtr = &err - } - atomic.StorePointer(&c.lastConnectErrPtr, unsafe.Pointer(errPtr)) -} - -func (c *Connection) SetStateDisconnected(err error) { - c.saveLastConnectError(err) - select { - case c.disconnectedCh <- true: - default: - } - c.newConnectionHandler(nil) -} - -func (c *Connection) setStateConnected() { - c.saveLastConnectError(nil) -} - -func (c *Connection) Connected() bool { - return c.LastConnectError() == nil -} - -const defaultConnReattemptPeriod = 10 * time.Second - -func (c *Connection) indefiniteBackgroundConnection() { - defer func() { - c.closeBackgroundConnectionDoneCh(c.backgroundConnectionDoneCh) - }() - - connReattemptPeriod := c.cfg.ReconnectionPeriod - if connReattemptPeriod <= 0 { - connReattemptPeriod = defaultConnReattemptPeriod - } - - // No strong seeding required, nano time can - // already help with pseudo uniqueness. - rng := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63n(1024))) - - // maxJitterNanos: 70% of the connectionReattemptPeriod - maxJitterNanos := int64(0.7 * float64(connReattemptPeriod)) - - for { - // Otherwise these will be the normal scenarios to enable - // reconnection if we trip out. - // 1. If we've stopped, return entirely - // 2. Otherwise block until we are disconnected, and - // then retry connecting - select { - case <-c.stopCh: - return - - case <-c.disconnectedCh: - // Quickly check if we haven't stopped at the - // same time. - select { - case <-c.stopCh: - return - - default: - } - - // Normal scenario that we'll wait for - } - - if err := c.connect(context.Background()); err == nil { - c.setStateConnected() - } else { - // this code is unreachable in most cases - // c.connect does not establish Connection - c.SetStateDisconnected(err) - } - - // Apply some jitter to avoid lockstep retrials of other - // collector-exporters. Lockstep retrials could result in an - // innocent DDOS, by clogging the machine's resources and network. - jitter := time.Duration(rng.Int63n(maxJitterNanos)) - select { - case <-c.stopCh: - return - case <-time.After(connReattemptPeriod + jitter): - } - } -} - -func (c *Connection) connect(ctx context.Context) error { - cc, err := c.dialToCollector(ctx) - if err != nil { - return err - } - c.setConnection(cc) - c.newConnectionHandler(cc) - return nil -} - -// setConnection sets cc as the client Connection and returns true if -// the Connection state changed. -func (c *Connection) setConnection(cc *grpc.ClientConn) bool { - c.mu.Lock() - defer c.mu.Unlock() - - // If previous clientConn is same as the current then just return. - // This doesn't happen right now as this func is only called with new ClientConn. - // It is more about future-proofing. - if c.cc == cc { - return false - } - - // If the previous clientConn was non-nil, close it - if c.cc != nil { - _ = c.cc.Close() - } - c.cc = cc - return true -} - -func (c *Connection) dialToCollector(ctx context.Context) (*grpc.ClientConn, error) { - if c.cfg.GRPCConn != nil { - return c.cfg.GRPCConn, nil - } - - dialOpts := []grpc.DialOption{} - if c.cfg.ServiceConfig != "" { - dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(c.cfg.ServiceConfig)) - } - if c.SCfg.GRPCCredentials != nil { - dialOpts = append(dialOpts, grpc.WithTransportCredentials(c.SCfg.GRPCCredentials)) - } else if c.SCfg.Insecure { - dialOpts = append(dialOpts, grpc.WithInsecure()) - } - if c.SCfg.Compression == otlpconfig.GzipCompression { - dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) - } - if len(c.cfg.DialOptions) != 0 { - dialOpts = append(dialOpts, c.cfg.DialOptions...) - } - - ctx, cancel := c.ContextWithStop(ctx) - defer cancel() - ctx = c.ContextWithMetadata(ctx) - return grpc.DialContext(ctx, c.SCfg.Endpoint, dialOpts...) -} - -func (c *Connection) ContextWithMetadata(ctx context.Context) context.Context { - if c.metadata.Len() > 0 { - return metadata.NewOutgoingContext(ctx, c.metadata) - } - return ctx -} - -func (c *Connection) Shutdown(ctx context.Context) error { - close(c.stopCh) - // Ensure that the backgroundConnector returns - select { - case <-c.backgroundConnectionDoneCh: - case <-ctx.Done(): - return ctx.Err() - } - - c.mu.Lock() - cc := c.cc - c.cc = nil - c.mu.Unlock() - - if cc != nil { - return cc.Close() - } - - return nil -} - -func (c *Connection) ContextWithStop(ctx context.Context) (context.Context, context.CancelFunc) { - // Unify the parent context Done signal with the Connection's - // stop channel. - ctx, cancel := context.WithCancel(ctx) - go func(ctx context.Context, cancel context.CancelFunc) { - select { - case <-ctx.Done(): - // Nothing to do, either cancelled or deadline - // happened. - case <-c.stopCh: - cancel() - } - }(ctx, cancel) - return ctx, cancel -} - -func (c *Connection) DoRequest(ctx context.Context, fn func(context.Context) error) error { - expBackoff := newExponentialBackoff(c.cfg.RetrySettings) - - for { - err := fn(ctx) - if err == nil { - // request succeeded. - return nil - } - - if !c.cfg.RetrySettings.Enabled { - return err - } - - // We have an error, check gRPC status code. - st := status.Convert(err) - if st.Code() == codes.OK { - // Not really an error, still success. - return nil - } - - // Now, this is this a real error. - - if !shouldRetry(st.Code()) { - // It is not a retryable error, we should not retry. - return err - } - - // Need to retry. - - throttle := getThrottleDuration(st) - - backoffDelay := expBackoff.NextBackOff() - if backoffDelay == backoff.Stop { - // throw away the batch - err = fmt.Errorf("max elapsed time expired: %w", err) - return err - } - - var delay time.Duration - - if backoffDelay > throttle { - delay = backoffDelay - } else { - if expBackoff.GetElapsedTime()+throttle > expBackoff.MaxElapsedTime { - err = fmt.Errorf("max elapsed time expired when respecting server throttle: %w", err) - return err - } - - // Respect server throttling. - delay = throttle - } - - // back-off, but get interrupted when shutting down or request is cancelled or timed out. - err = func() error { - dt := time.NewTimer(delay) - defer dt.Stop() - - select { - case <-ctx.Done(): - return ctx.Err() - case <-c.stopCh: - return fmt.Errorf("interrupted due to shutdown: %w", err) - case <-dt.C: - } - - return nil - }() - - if err != nil { - return err - } - - } -} - -func shouldRetry(code codes.Code) bool { - switch code { - case codes.OK: - // Success. This function should not be called for this code, the best we - // can do is tell the caller not to retry. - return false - - case codes.Canceled, - codes.DeadlineExceeded, - codes.ResourceExhausted, - codes.Aborted, - codes.OutOfRange, - codes.Unavailable, - codes.DataLoss: - // These are retryable errors. - return true - - case codes.Unknown, - codes.InvalidArgument, - codes.Unauthenticated, - codes.PermissionDenied, - codes.NotFound, - codes.AlreadyExists, - codes.FailedPrecondition, - codes.Unimplemented, - codes.Internal: - // These are fatal errors, don't retry. - return false - - default: - // Don't retry on unknown codes. - return false - } -} - -func getThrottleDuration(status *status.Status) time.Duration { - // See if throttling information is available. - for _, detail := range status.Details() { - if t, ok := detail.(*errdetails.RetryInfo); ok { - if t.RetryDelay.Seconds > 0 || t.RetryDelay.Nanos > 0 { - // We are throttled. Wait before retrying as requested by the server. - return time.Duration(t.RetryDelay.Seconds)*time.Second + time.Duration(t.RetryDelay.Nanos)*time.Nanosecond - } - return 0 - } - } - return 0 -} - -func newExponentialBackoff(rs otlpconfig.RetrySettings) *backoff.ExponentialBackOff { - // Do not use NewExponentialBackOff since it calls Reset and the code here must - // call Reset after changing the InitialInterval (this saves an unnecessary call to Now). - expBackoff := &backoff.ExponentialBackOff{ - InitialInterval: rs.InitialInterval, - RandomizationFactor: backoff.DefaultRandomizationFactor, - Multiplier: backoff.DefaultMultiplier, - MaxInterval: rs.MaxInterval, - MaxElapsedTime: rs.MaxElapsedTime, - Stop: backoff.Stop, - Clock: backoff.SystemClock, - } - expBackoff.Reset() - - return expBackoff -} diff --git a/exporters/otlp/otlpmetric/internal/connection/connection_test.go b/exporters/otlp/otlpmetric/internal/connection/connection_test.go deleted file mode 100644 index f842fbd48..000000000 --- a/exporters/otlp/otlpmetric/internal/connection/connection_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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 connection - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - "google.golang.org/genproto/googleapis/rpc/errdetails" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/durationpb" -) - -func TestGetThrottleDuration(t *testing.T) { - tts := []struct { - stsFn func() (*status.Status, error) - throttle time.Duration - }{ - { - stsFn: func() (*status.Status, error) { - return status.New( - codes.OK, - "status with no retry info", - ), nil - }, - throttle: 0, - }, - { - stsFn: func() (*status.Status, error) { - st := status.New(codes.ResourceExhausted, "status with retry info") - return st.WithDetails( - &errdetails.RetryInfo{RetryDelay: durationpb.New(15 * time.Millisecond)}, - ) - }, - throttle: 15 * time.Millisecond, - }, - { - stsFn: func() (*status.Status, error) { - st := status.New(codes.ResourceExhausted, "status with error info detail") - return st.WithDetails( - &errdetails.ErrorInfo{Reason: "no throttle detail"}, - ) - }, - throttle: 0, - }, - { - stsFn: func() (*status.Status, error) { - st := status.New(codes.ResourceExhausted, "status with error info and retry info") - return st.WithDetails( - &errdetails.ErrorInfo{Reason: "no throttle detail"}, - &errdetails.RetryInfo{RetryDelay: durationpb.New(13 * time.Minute)}, - ) - }, - throttle: 13 * time.Minute, - }, - { - stsFn: func() (*status.Status, error) { - st := status.New(codes.ResourceExhausted, "status with two retry info should take the first") - return st.WithDetails( - &errdetails.RetryInfo{RetryDelay: durationpb.New(13 * time.Minute)}, - &errdetails.RetryInfo{RetryDelay: durationpb.New(18 * time.Minute)}, - ) - }, - throttle: 13 * time.Minute, - }, - } - - for _, tt := range tts { - sts, _ := tt.stsFn() - t.Run(sts.Message(), func(t *testing.T) { - th := getThrottleDuration(sts) - require.Equal(t, tt.throttle, th) - }) - } -} diff --git a/exporters/otlp/otlpmetric/internal/metrictransform/metric.go b/exporters/otlp/otlpmetric/internal/metrictransform/metric.go index 96c732640..ac3673d72 100644 --- a/exporters/otlp/otlpmetric/internal/metrictransform/metric.go +++ b/exporters/otlp/otlpmetric/internal/metrictransform/metric.go @@ -239,13 +239,6 @@ func sink(ctx context.Context, in <-chan result) ([]*metricpb.Metric, error) { func Record(temporalitySelector aggregation.TemporalitySelector, r export.Record) (*metricpb.Metric, error) { agg := r.Aggregation() switch agg.Kind() { - case aggregation.MinMaxSumCountKind: - mmsc, ok := agg.(aggregation.MinMaxSumCount) - if !ok { - return nil, fmt.Errorf("%w: %T", ErrIncompatibleAgg, agg) - } - return minMaxSumCount(r, mmsc) - case aggregation.HistogramKind: h, ok := agg.(aggregation.Histogram) if !ok { @@ -390,64 +383,6 @@ func sumPoint(record export.Record, num number.Number, start, end time.Time, tem return m, nil } -// minMaxSumCountValue returns the values of the MinMaxSumCount Aggregator -// as discrete values. -func minMaxSumCountValues(a aggregation.MinMaxSumCount) (min, max, sum number.Number, count uint64, err error) { - if min, err = a.Min(); err != nil { - return - } - if max, err = a.Max(); err != nil { - return - } - if sum, err = a.Sum(); err != nil { - return - } - if count, err = a.Count(); err != nil { - return - } - return -} - -// minMaxSumCount transforms a MinMaxSumCount Aggregator into an OTLP Metric. -func minMaxSumCount(record export.Record, a aggregation.MinMaxSumCount) (*metricpb.Metric, error) { - desc := record.Descriptor() - labels := record.Labels() - min, max, sum, count, err := minMaxSumCountValues(a) - if err != nil { - return nil, err - } - - m := &metricpb.Metric{ - Name: desc.Name(), - Description: desc.Description(), - Unit: string(desc.Unit()), - Data: &metricpb.Metric_Summary{ - Summary: &metricpb.Summary{ - DataPoints: []*metricpb.SummaryDataPoint{ - { - Sum: sum.CoerceToFloat64(desc.NumberKind()), - Attributes: Iterator(labels.Iter()), - StartTimeUnixNano: toNanos(record.StartTime()), - TimeUnixNano: toNanos(record.EndTime()), - Count: uint64(count), - QuantileValues: []*metricpb.SummaryDataPoint_ValueAtQuantile{ - { - Quantile: 0.0, - Value: min.CoerceToFloat64(desc.NumberKind()), - }, - { - Quantile: 1.0, - Value: max.CoerceToFloat64(desc.NumberKind()), - }, - }, - }, - }, - }, - }, - } - return m, nil -} - func histogramValues(a aggregation.Histogram) (boundaries []float64, counts []uint64, err error) { var buckets aggregation.Buckets if buckets, err = a.Histogram(); err != nil { diff --git a/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go b/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go index 120e85852..3dd6c1db8 100644 --- a/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go +++ b/exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go @@ -31,7 +31,6 @@ import ( export "go.opentelemetry.io/otel/sdk/export/metric" "go.opentelemetry.io/otel/sdk/export/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" - "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" commonpb "go.opentelemetry.io/proto/otlp/common/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" @@ -96,84 +95,6 @@ func TestStringKeyValues(t *testing.T) { } } -func TestMinMaxSumCountValue(t *testing.T) { - mmscs := minmaxsumcount.New(2, &sdkapi.Descriptor{}) - mmsc, ckpt := &mmscs[0], &mmscs[1] - - assert.NoError(t, mmsc.Update(context.Background(), 1, &sdkapi.Descriptor{})) - assert.NoError(t, mmsc.Update(context.Background(), 10, &sdkapi.Descriptor{})) - - // Prior to checkpointing ErrNoData should be returned. - _, _, _, _, err := minMaxSumCountValues(ckpt) - assert.EqualError(t, err, aggregation.ErrNoData.Error()) - - // Checkpoint to set non-zero values - require.NoError(t, mmsc.SynchronizedMove(ckpt, &sdkapi.Descriptor{})) - min, max, sum, count, err := minMaxSumCountValues(ckpt) - if assert.NoError(t, err) { - assert.Equal(t, min, number.NewInt64Number(1)) - assert.Equal(t, max, number.NewInt64Number(10)) - assert.Equal(t, sum, number.NewInt64Number(11)) - assert.Equal(t, count, uint64(2)) - } -} - -func TestMinMaxSumCountDatapoints(t *testing.T) { - desc := metrictest.NewDescriptor("", sdkapi.HistogramInstrumentKind, number.Int64Kind) - labels := attribute.NewSet(attribute.String("one", "1")) - mmscs := minmaxsumcount.New(2, &sdkapi.Descriptor{}) - mmsc, ckpt := &mmscs[0], &mmscs[1] - - assert.NoError(t, mmsc.Update(context.Background(), 1, &desc)) - assert.NoError(t, mmsc.Update(context.Background(), 10, &desc)) - require.NoError(t, mmsc.SynchronizedMove(ckpt, &desc)) - expected := []*metricpb.SummaryDataPoint{ - { - Count: 2, - Sum: 11, - StartTimeUnixNano: uint64(intervalStart.UnixNano()), - TimeUnixNano: uint64(intervalEnd.UnixNano()), - Attributes: []*commonpb.KeyValue{ - { - Key: "one", - Value: &commonpb.AnyValue{Value: &commonpb.AnyValue_StringValue{StringValue: "1"}}, - }, - }, - QuantileValues: []*metricpb.SummaryDataPoint_ValueAtQuantile{ - { - Quantile: 0.0, - Value: 1.0, - }, - { - Quantile: 1.0, - Value: 10.0, - }, - }, - }, - } - record := export.NewRecord(&desc, &labels, ckpt.Aggregation(), intervalStart, intervalEnd) - m, err := minMaxSumCount(record, ckpt) - if assert.NoError(t, err) { - assert.Nil(t, m.GetGauge()) - assert.Nil(t, m.GetSum()) - assert.Nil(t, m.GetHistogram()) - assert.Equal(t, expected, m.GetSummary().DataPoints) - assert.Nil(t, m.GetIntGauge()) // nolint - assert.Nil(t, m.GetIntSum()) // nolint - assert.Nil(t, m.GetIntHistogram()) // nolint - } -} - -func TestMinMaxSumCountPropagatesErrors(t *testing.T) { - // ErrNoData should be returned by both the Min and Max values of - // a MinMaxSumCount Aggregator. Use this fact to check the error is - // correctly returned. - mmsc := &minmaxsumcount.New(1, &sdkapi.Descriptor{})[0] - _, _, _, _, err := minMaxSumCountValues(mmsc) - assert.Error(t, err) - assert.Equal(t, aggregation.ErrNoData, err) -} - func TestSumIntDataPoints(t *testing.T) { desc := metrictest.NewDescriptor("", sdkapi.HistogramInstrumentKind, number.Int64Kind) labels := attribute.NewSet(attribute.String("one", "1")) @@ -335,10 +256,6 @@ type testErrLastValue struct { err error } -type testErrMinMaxSumCount struct { - testErrSum -} - func (te *testErrLastValue) LastValue() (number.Number, time.Time, error) { return 0, time.Time{}, te.err } @@ -353,23 +270,10 @@ func (te *testErrSum) Kind() aggregation.Kind { return aggregation.SumKind } -func (te *testErrMinMaxSumCount) Min() (number.Number, error) { - return 0, te.err -} - -func (te *testErrMinMaxSumCount) Max() (number.Number, error) { - return 0, te.err -} - -func (te *testErrMinMaxSumCount) Count() (uint64, error) { - return 0, te.err -} - var _ export.Aggregator = &testAgg{} var _ aggregation.Aggregation = &testAgg{} var _ aggregation.Sum = &testErrSum{} var _ aggregation.LastValue = &testErrLastValue{} -var _ aggregation.MinMaxSumCount = &testErrMinMaxSumCount{} func TestRecordAggregatorIncompatibleErrors(t *testing.T) { makeMpb := func(kind aggregation.Kind, agg aggregation.Aggregation) (*metricpb.Metric, error) { @@ -393,12 +297,6 @@ func TestRecordAggregatorIncompatibleErrors(t *testing.T) { require.Error(t, err) require.Nil(t, mpb) require.True(t, errors.Is(err, ErrIncompatibleAgg)) - - mpb, err = makeMpb(aggregation.MinMaxSumCountKind, &lastvalue.New(1)[0]) - - require.Error(t, err) - require.Nil(t, mpb) - require.True(t, errors.Is(err, ErrIncompatibleAgg)) } func TestRecordAggregatorUnexpectedErrors(t *testing.T) { @@ -421,10 +319,4 @@ func TestRecordAggregatorUnexpectedErrors(t *testing.T) { require.Error(t, err) require.Nil(t, mpb) require.True(t, errors.Is(err, errEx)) - - mpb, err = makeMpb(aggregation.MinMaxSumCountKind, &testErrMinMaxSumCount{testErrSum{errEx}}) - - require.Error(t, err) - require.Nil(t, mpb) - require.True(t, errors.Is(err, errEx)) } diff --git a/exporters/otlp/otlpmetric/internal/otlpconfig/options.go b/exporters/otlp/otlpmetric/internal/otlpconfig/options.go index 4a9c326f4..067c72fd4 100644 --- a/exporters/otlp/otlpmetric/internal/otlpconfig/options.go +++ b/exporters/otlp/otlpmetric/internal/otlpconfig/options.go @@ -20,7 +20,11 @@ import ( "time" "google.golang.org/grpc" + "google.golang.org/grpc/backoff" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding/gzip" + + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" ) const ( @@ -39,16 +43,6 @@ const ( DefaultTimeout time.Duration = 10 * time.Second ) -var ( - // defaultRetrySettings is a default settings for the retry policy. - defaultRetrySettings = RetrySettings{ - Enabled: true, - InitialInterval: 5 * time.Second, - MaxInterval: 30 * time.Second, - MaxElapsedTime: time.Minute, - } -) - type ( SignalConfig struct { Endpoint string @@ -67,16 +61,13 @@ type ( // Signal specific configurations Metrics SignalConfig - // HTTP configurations - MaxAttempts int - Backoff time.Duration + RetryConfig retry.Config // gRPC configurations ReconnectionPeriod time.Duration ServiceConfig string DialOptions []grpc.DialOption GRPCConn *grpc.ClientConn - RetrySettings RetrySettings } ) @@ -88,12 +79,46 @@ func NewDefaultConfig() Config { Compression: NoCompression, Timeout: DefaultTimeout, }, - RetrySettings: defaultRetrySettings, + RetryConfig: retry.DefaultConfig, } return c } +// NewGRPCConfig returns a new Config with all settings applied from opts and +// any unset setting using the default gRPC config values. +func NewGRPCConfig(opts ...GRPCOption) Config { + cfg := NewDefaultConfig() + ApplyGRPCEnvConfigs(&cfg) + for _, opt := range opts { + opt.ApplyGRPCOption(&cfg) + } + + if cfg.ServiceConfig != "" { + cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultServiceConfig(cfg.ServiceConfig)) + } + if cfg.Metrics.GRPCCredentials != nil { + cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(cfg.Metrics.GRPCCredentials)) + } else if cfg.Metrics.Insecure { + cfg.DialOptions = append(cfg.DialOptions, grpc.WithInsecure()) + } + if cfg.Metrics.Compression == GzipCompression { + cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) + } + if len(cfg.DialOptions) != 0 { + cfg.DialOptions = append(cfg.DialOptions, cfg.DialOptions...) + } + if cfg.ReconnectionPeriod != 0 { + p := grpc.ConnectParams{ + Backoff: backoff.DefaultConfig, + MinConnectTimeout: cfg.ReconnectionPeriod, + } + cfg.DialOptions = append(cfg.DialOptions, grpc.WithConnectParams(p)) + } + + return cfg +} + type ( // GenericOption applies an option to the HTTP or gRPC driver. GenericOption interface { @@ -218,9 +243,9 @@ func WithURLPath(urlPath string) GenericOption { }) } -func WithRetry(settings RetrySettings) GenericOption { +func WithRetry(rc retry.Config) GenericOption { return newGenericOption(func(cfg *Config) { - cfg.RetrySettings = settings + cfg.RetryConfig = rc }) } @@ -255,15 +280,3 @@ func WithTimeout(duration time.Duration) GenericOption { cfg.Metrics.Timeout = duration }) } - -func WithMaxAttempts(maxAttempts int) GenericOption { - return newGenericOption(func(cfg *Config) { - cfg.MaxAttempts = maxAttempts - }) -} - -func WithBackoff(duration time.Duration) GenericOption { - return newGenericOption(func(cfg *Config) { - cfg.Backoff = duration - }) -} diff --git a/exporters/otlp/otlpmetric/internal/otlpmetrictest/client.go b/exporters/otlp/otlpmetric/internal/otlpmetrictest/client.go index fb29e3ede..c248521ee 100644 --- a/exporters/otlp/otlpmetric/internal/otlpmetrictest/client.go +++ b/exporters/otlp/otlpmetric/internal/otlpmetrictest/client.go @@ -55,6 +55,14 @@ func initializeExporter(t *testing.T, client otlpmetric.Client) *otlpmetric.Expo } func testClientStopHonorsTimeout(t *testing.T, client otlpmetric.Client) { + t.Cleanup(func() { + // The test is looking for a failed shut down. Call Stop a second time + // with an un-expired context to give the client a second chance at + // cleaning up. There is not guarantee from the Client interface this + // will succeed, therefore, no need to check the error (just give it a + // best try). + _ = client.Stop(context.Background()) + }) e := initializeExporter(t, client) innerCtx, innerCancel := context.WithTimeout(context.Background(), time.Microsecond) @@ -68,6 +76,14 @@ func testClientStopHonorsTimeout(t *testing.T, client otlpmetric.Client) { } func testClientStopHonorsCancel(t *testing.T, client otlpmetric.Client) { + t.Cleanup(func() { + // The test is looking for a failed shut down. Call Stop a second time + // with an un-expired context to give the client a second chance at + // cleaning up. There is not guarantee from the Client interface this + // will succeed, therefore, no need to check the error (just give it a + // best try). + _ = client.Stop(context.Background()) + }) e := initializeExporter(t, client) ctx, innerCancel := context.WithCancel(context.Background()) diff --git a/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go b/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go index 82e5d53ae..e14d548e8 100644 --- a/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go +++ b/exporters/otlp/otlpmetric/internal/otlpmetrictest/otlptest.go @@ -54,8 +54,6 @@ func RunEndToEndTest(ctx context.Context, t *testing.T, exp *otlpmetric.Exporter instruments := map[string]data{ "test-int64-counter": {sdkapi.CounterInstrumentKind, number.Int64Kind, 1}, "test-float64-counter": {sdkapi.CounterInstrumentKind, number.Float64Kind, 1}, - "test-int64-histogram": {sdkapi.HistogramInstrumentKind, number.Int64Kind, 2}, - "test-float64-histogram": {sdkapi.HistogramInstrumentKind, number.Float64Kind, 2}, "test-int64-gaugeobserver": {sdkapi.GaugeObserverInstrumentKind, number.Int64Kind, 3}, "test-float64-gaugeobserver": {sdkapi.GaugeObserverInstrumentKind, number.Float64Kind, 3}, } diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go index c70be399f..ee08f8a00 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go @@ -17,91 +17,259 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme import ( "context" "errors" - "fmt" "sync" + "time" + "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/connection" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) type client struct { - connection *connection.Connection + endpoint string + dialOpts []grpc.DialOption + metadata metadata.MD + exportTimeout time.Duration + requestFunc retry.RequestFunc - lock sync.Mutex - metricsClient colmetricpb.MetricsServiceClient + // stopCtx is used as a parent context for all exports. Therefore, when it + // is canceled with the stopFunc all exports are canceled. + stopCtx context.Context + // stopFunc cancels stopCtx, stopping any active exports. + stopFunc context.CancelFunc + + // ourConn keeps track of where conn was created: true if created here on + // Start, or false if passed with an option. This is important on Shutdown + // as the conn should only be closed if created here on start. Otherwise, + // it is up to the processes that passed the conn to close it. + ourConn bool + conn *grpc.ClientConn + mscMu sync.RWMutex + msc colmetricpb.MetricsServiceClient } -var ( - errNoClient = errors.New("no client") -) +// Compile time check *client implements otlpmetric.Client. +var _ otlpmetric.Client = (*client)(nil) // NewClient creates a new gRPC metric client. func NewClient(opts ...Option) otlpmetric.Client { - cfg := otlpconfig.NewDefaultConfig() - otlpconfig.ApplyGRPCEnvConfigs(&cfg) - for _, opt := range opts { - opt.applyGRPCOption(&cfg) + return newClient(opts...) +} + +func newClient(opts ...Option) *client { + cfg := otlpconfig.NewGRPCConfig(asGRPCOptions(opts)...) + + ctx, cancel := context.WithCancel(context.Background()) + + c := &client{ + endpoint: cfg.Metrics.Endpoint, + exportTimeout: cfg.Metrics.Timeout, + requestFunc: cfg.RetryConfig.RequestFunc(retryable), + dialOpts: cfg.DialOptions, + stopCtx: ctx, + stopFunc: cancel, + conn: cfg.GRPCConn, } - c := &client{} - c.connection = connection.NewConnection(cfg, cfg.Metrics, c.handleNewConnection) + if len(cfg.Metrics.Headers) > 0 { + c.metadata = metadata.New(cfg.Metrics.Headers) + } return c } -func (c *client) handleNewConnection(cc *grpc.ClientConn) { - c.lock.Lock() - defer c.lock.Unlock() - if cc != nil { - c.metricsClient = colmetricpb.NewMetricsServiceClient(cc) - } else { - c.metricsClient = nil - } -} - -// Start establishes a connection to the collector. +// Start establishes a gRPC connection to the collector. func (c *client) Start(ctx context.Context) error { - return c.connection.StartConnection(ctx) -} - -// Stop shuts down the connection to the collector. -func (c *client) Stop(ctx context.Context) error { - return c.connection.Shutdown(ctx) -} - -// UploadMetrics sends a batch of metrics to the collector. -func (c *client) UploadMetrics(ctx context.Context, protoMetrics []*metricpb.ResourceMetrics) error { - if !c.connection.Connected() { - return fmt.Errorf("metrics exporter is disconnected from the server %s: %w", c.connection.SCfg.Endpoint, c.connection.LastConnectError()) + 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. + conn, err := grpc.DialContext(ctx, c.endpoint, c.dialOpts...) + if err != nil { + return err + } + // Keep track that we own the lifecycle of this conn and need to close + // it on Shutdown. + c.ourConn = true + c.conn = conn } - ctx, cancel := c.connection.ContextWithStop(ctx) - defer cancel() - ctx, tCancel := context.WithTimeout(ctx, c.connection.SCfg.Timeout) - defer tCancel() + // The otlpmetric.Client interface states this method is called just once, + // so no need to check if already started. + c.mscMu.Lock() + c.msc = colmetricpb.NewMetricsServiceClient(c.conn) + c.mscMu.Unlock() - ctx = c.connection.ContextWithMetadata(ctx) - err := func() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.metricsClient == nil { - return errNoClient - } + return nil +} - return c.connection.DoRequest(ctx, func(ctx context.Context) error { - _, err := c.metricsClient.Export(ctx, &colmetricpb.ExportMetricsServiceRequest{ - ResourceMetrics: protoMetrics, - }) - return err - }) +var errAlreadyStopped = errors.New("the client is already stopped") + +// Stop shuts down the client. +// +// Any active connections to a remote endpoint are closed if they were created +// by the client. Any gRPC connection passed during creation using +// WithGRPCConn will not be closed. It is the caller's responsibility to +// handle cleanup of that resource. +// +// This method synchronizes with the UploadMetrics method of the client. It +// will wait for any active calls to that method to complete unimpeded, or it +// will cancel any active calls if ctx expires. If ctx expires, the context +// error will be forwarded as the returned error. All client held resources +// will still be released in this situation. +// +// If the client has already stopped, an error will be returned describing +// this. +func (c *client) Stop(ctx context.Context) error { + // Acquire the c.mscMu lock within the ctx lifetime. + acquired := make(chan struct{}) + go func() { + c.mscMu.Lock() + close(acquired) }() - if err != nil { - c.connection.SetStateDisconnected(err) + var err error + select { + case <-ctx.Done(): + // The Stop timeout is reached. Kill any remaining exports to force + // the clear of the lock and save the timeout error to return and + // signal the shutdown timed out before cleanly stopping. + c.stopFunc() + err = ctx.Err() + + // To ensure the client is not left in a dirty state c.msc needs to be + // set to nil. To avoid the race condition when doing this, ensure + // that all the exports are killed (initiated by c.stopFunc). + <-acquired + case <-acquired: + } + // Hold the mscMu lock for the rest of the function to ensure no new + // exports are started. + defer c.mscMu.Unlock() + + // The otlpmetric.Client interface states this method is called only + // once, but there is no guarantee it is called after Start. Ensure the + // client is started before doing anything and let the called know if they + // made a mistake. + if c.msc == nil { + return errAlreadyStopped + } + + // Clear c.msc to signal the client is stopped. + c.msc = nil + + if c.ourConn { + closeErr := c.conn.Close() + // A context timeout error takes precedence over this error. + if err == nil && closeErr != nil { + err = closeErr + } } return err } + +var errShutdown = errors.New("the client is shutdown") + +// UploadMetrics sends a batch of spans. +// +// Retryable errors from the server will be handled according to any +// RetryConfig the client was created with. +func (c *client) UploadMetrics(ctx context.Context, protoMetrics []*metricpb.ResourceMetrics) error { + // Hold a read lock to ensure a shut down initiated after this starts does + // not abandon the export. This read lock acquire has less priority than a + // write lock acquire (i.e. Stop), meaning if the client is shutting down + // this will come after the shut down. + c.mscMu.RLock() + defer c.mscMu.RUnlock() + + if c.msc == nil { + return errShutdown + } + + ctx, cancel := c.exportContext(ctx) + defer cancel() + + return c.requestFunc(ctx, func(iCtx context.Context) error { + _, err := c.msc.Export(iCtx, &colmetricpb.ExportMetricsServiceRequest{ + ResourceMetrics: protoMetrics, + }) + // nil is converted to OK. + if status.Code(err) == codes.OK { + // Success. + return nil + } + return err + }) +} + +// exportContext returns a copy of parent with an appropriate deadline and +// cancellation function. +// +// It is the callers responsibility to cancel the returned context once its +// use is complete, via the parent or directly with the returned CancelFunc, to +// ensure all resources are correctly released. +func (c *client) exportContext(parent context.Context) (context.Context, context.CancelFunc) { + var ( + ctx context.Context + cancel context.CancelFunc + ) + + if c.exportTimeout > 0 { + ctx, cancel = context.WithTimeout(parent, c.exportTimeout) + } else { + ctx, cancel = context.WithCancel(parent) + } + + if c.metadata.Len() > 0 { + ctx = metadata.NewOutgoingContext(ctx, c.metadata) + } + + // Unify the client stopCtx with the parent. + go func() { + select { + case <-ctx.Done(): + case <-c.stopCtx.Done(): + // Cancel the export as the shutdown has timed out. + cancel() + } + }() + + return ctx, cancel +} + +// 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) { + //func retryable(err error) (bool, time.Duration) { + s := status.Convert(err) + switch s.Code() { + case codes.Canceled, + codes.DeadlineExceeded, + codes.ResourceExhausted, + codes.Aborted, + codes.OutOfRange, + codes.Unavailable, + codes.DataLoss: + return true, throttleDelay(s) + } + + // Not a retry-able error. + return false, 0 +} + +// throttleDelay returns a duration to wait for if an explicit throttle time +// is included in the response status. +func throttleDelay(status *status.Status) time.Duration { + for _, detail := range status.Details() { + if t, ok := detail.(*errdetails.RetryInfo); ok { + return t.RetryDelay.AsDuration() + } + } + return 0 +} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go index bf55eccfb..dbd8f4799 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go @@ -24,12 +24,10 @@ import ( "github.com/stretchr/testify/assert" "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/encoding/gzip" "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/durationpb" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpmetrictest" @@ -165,386 +163,6 @@ func TestNewExporter_invokeStartThenStopManyTimes(t *testing.T) { } } -func TestNewExporter_collectorConnectionDiesThenReconnectsWhenInRestMode(t *testing.T) { - // TODO: Fix this test #1527 - t.Skip("This test is flaky and needs to be rewritten") - mc := runMockCollector(t) - - reconnectionPeriod := 20 * time.Millisecond - ctx := context.Background() - exp := newGRPCExporter(t, ctx, mc.endpoint, - otlpmetricgrpc.WithRetry(otlpmetricgrpc.RetrySettings{Enabled: false}), - otlpmetricgrpc.WithReconnectionPeriod(reconnectionPeriod)) - defer func() { require.NoError(t, exp.Shutdown(ctx)) }() - - // Wait for a connection. - mc.ln.WaitForConn() - - // We'll now stop the collector right away to simulate a connection - // dying in the midst of communication or even not existing before. - require.NoError(t, mc.stop()) - - // first export, it will send disconnected message to the channel on export failure, - // trigger almost immediate reconnection - require.Error(t, exp.Export(ctx, testResource, oneRecord)) - - // second export, it will detect connection issue, change state of exporter to disconnected and - // send message to disconnected channel but this time reconnection gouroutine will be in (rest mode, not listening to the disconnected channel) - require.Error(t, exp.Export(ctx, testResource, oneRecord)) - - // as a result we have exporter in disconnected state waiting for disconnection message to reconnect - - // resurrect collector - nmc := runMockCollectorAtEndpoint(t, mc.endpoint) - - // make sure reconnection loop hits beginning and goes back to waiting mode - // after hitting beginning of the loop it should reconnect - nmc.ln.WaitForConn() - - n := 10 - for i := 0; i < n; i++ { - // when disconnected exp.Export doesnt send disconnected messages again - // it just quits and return last connection error - require.NoError(t, exp.Export(ctx, testResource, oneRecord)) - } - - nmaMetrics := nmc.getMetrics() - - if g, w := len(nmaMetrics), n; g != w { - t.Fatalf("Connected collector: metrics: got %d want %d", g, w) - } - - dMetrics := mc.getMetrics() - // Expecting 0 metrics to have been received by the original but now dead collector - if g, w := len(dMetrics), 0; g != w { - t.Fatalf("Disconnected collector: spans: got %d want %d", g, w) - } - - require.NoError(t, nmc.Stop()) -} - -func TestExporterExportFailureAndRecoveryModes(t *testing.T) { - tts := []struct { - name string - errors []error - rs otlpmetricgrpc.RetrySettings - fn func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) - opts []otlpmetricgrpc.Option - }{ - { - name: "Do not retry if succeeded", - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - require.NoError(t, exp.Export(ctx, testResource, oneRecord)) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 1) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 success request.") - }, - }, - { - name: "Do not retry if 'error' is ok", - errors: []error{ - status.Error(codes.OK, ""), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - require.NoError(t, exp.Export(ctx, testResource, oneRecord)) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 0) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 error OK request.") - }, - }, - { - name: "Fail three times and succeed", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: true, - MaxElapsedTime: 300 * time.Millisecond, - InitialInterval: 2 * time.Millisecond, - MaxInterval: 10 * time.Millisecond, - }, - errors: []error{ - status.Error(codes.Unavailable, "backend under pressure"), - status.Error(codes.Unavailable, "backend under pressure"), - status.Error(codes.Unavailable, "backend under pressure"), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - require.NoError(t, exp.Export(ctx, testResource, oneRecord)) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 1) - require.Equal(t, 4, mc.metricSvc.requests, "metric service must receive 3 failure requests and 1 success request.") - }, - }, - { - name: "Permanent error should not be retried", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: true, - MaxElapsedTime: 300 * time.Millisecond, - InitialInterval: 2 * time.Millisecond, - MaxInterval: 10 * time.Millisecond, - }, - errors: []error{ - status.Error(codes.InvalidArgument, "invalid arguments"), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - require.Error(t, exp.Export(ctx, testResource, oneRecord)) - - metric := mc.getMetrics() - - require.Len(t, metric, 0) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 error requests.") - }, - }, - { - name: "Test all transient errors and succeed", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: true, - MaxElapsedTime: 500 * time.Millisecond, - InitialInterval: 1 * time.Millisecond, - MaxInterval: 2 * time.Millisecond, - }, - errors: []error{ - status.Error(codes.Canceled, ""), - status.Error(codes.DeadlineExceeded, ""), - status.Error(codes.ResourceExhausted, ""), - status.Error(codes.Aborted, ""), - status.Error(codes.OutOfRange, ""), - status.Error(codes.Unavailable, ""), - status.Error(codes.DataLoss, ""), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - require.NoError(t, exp.Export(ctx, testResource, oneRecord)) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 1) - require.Equal(t, 8, mc.metricSvc.requests, "metric service must receive 9 failure requests and 1 success request.") - }, - }, - { - name: "Retry should honor server throttling", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: true, - MaxElapsedTime: time.Minute, - InitialInterval: time.Nanosecond, - MaxInterval: time.Nanosecond, - }, - opts: []otlpmetricgrpc.Option{ - otlpmetricgrpc.WithTimeout(time.Millisecond * 100), - }, - errors: []error{ - newThrottlingError(codes.ResourceExhausted, time.Second*30), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - err := exp.Export(ctx, testResource, oneRecord) - require.Error(t, err) - require.Equal(t, "context deadline exceeded", err.Error()) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 0) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 failure requests and 1 success request.") - }, - }, - { - name: "Retry should fail if server throttling is higher than the MaxElapsedTime", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: true, - MaxElapsedTime: time.Millisecond * 100, - InitialInterval: time.Nanosecond, - MaxInterval: time.Nanosecond, - }, - errors: []error{ - newThrottlingError(codes.ResourceExhausted, time.Minute), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - err := exp.Export(ctx, testResource, oneRecord) - require.Error(t, err) - require.Equal(t, "max elapsed time expired when respecting server throttle: rpc error: code = ResourceExhausted desc = ", err.Error()) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 0) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 failure requests and 1 success request.") - }, - }, - { - name: "Retry stops if takes too long", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: true, - MaxElapsedTime: time.Millisecond * 100, - InitialInterval: time.Millisecond * 50, - MaxInterval: time.Millisecond * 50, - }, - errors: []error{ - status.Error(codes.Unavailable, "unavailable"), - status.Error(codes.Unavailable, "unavailable"), - status.Error(codes.Unavailable, "unavailable"), - status.Error(codes.Unavailable, "unavailable"), - status.Error(codes.Unavailable, "unavailable"), - status.Error(codes.Unavailable, "unavailable"), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - err := exp.Export(ctx, testResource, oneRecord) - require.Error(t, err) - - require.Equal(t, "max elapsed time expired: rpc error: code = Unavailable desc = unavailable", err.Error()) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 0) - require.LessOrEqual(t, 1, mc.metricSvc.requests, "metric service must receive at least 1 failure requests.") - }, - }, - { - name: "Disabled retry", - rs: otlpmetricgrpc.RetrySettings{ - Enabled: false, - }, - errors: []error{ - status.Error(codes.Unavailable, "unavailable"), - }, - fn: func(t *testing.T, ctx context.Context, exp *otlpmetric.Exporter, mc *mockCollector) { - err := exp.Export(ctx, testResource, oneRecord) - require.Error(t, err) - - require.Equal(t, "rpc error: code = Unavailable desc = unavailable", err.Error()) - - metrics := mc.getMetrics() - - require.Len(t, metrics, 0) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 failure requests.") - }, - }, - } - - for _, tt := range tts { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - - mc := runMockCollectorWithConfig(t, &mockConfig{ - errors: tt.errors, - }) - - opts := []otlpmetricgrpc.Option{ - otlpmetricgrpc.WithRetry(tt.rs), - } - - if len(tt.opts) != 0 { - opts = append(opts, tt.opts...) - } - - exp := newGRPCExporter(t, ctx, mc.endpoint, opts...) - - tt.fn(t, ctx, exp, mc) - - require.NoError(t, mc.Stop()) - require.NoError(t, exp.Shutdown(ctx)) - }) - } - -} - -func TestPermanentErrorsShouldNotBeRetried(t *testing.T) { - permanentErrors := []*status.Status{ - status.New(codes.Unknown, "Unknown"), - status.New(codes.InvalidArgument, "InvalidArgument"), - status.New(codes.NotFound, "NotFound"), - status.New(codes.AlreadyExists, "AlreadyExists"), - status.New(codes.FailedPrecondition, "FailedPrecondition"), - status.New(codes.Unimplemented, "Unimplemented"), - status.New(codes.Internal, "Internal"), - status.New(codes.PermissionDenied, ""), - status.New(codes.Unauthenticated, ""), - } - - for _, sts := range permanentErrors { - t.Run(sts.Code().String(), func(t *testing.T) { - ctx := context.Background() - - mc := runMockCollectorWithConfig(t, &mockConfig{ - errors: []error{sts.Err()}, - }) - - exp := newGRPCExporter(t, ctx, mc.endpoint) - - err := exp.Export(ctx, testResource, oneRecord) - require.Error(t, err) - require.Len(t, mc.getMetrics(), 0) - require.Equal(t, 1, mc.metricSvc.requests, "metric service must receive 1 permanent error requests.") - - require.NoError(t, mc.Stop()) - require.NoError(t, exp.Shutdown(ctx)) - }) - } -} - -func newThrottlingError(code codes.Code, duration time.Duration) error { - s := status.New(code, "") - - s, _ = s.WithDetails(&errdetails.RetryInfo{RetryDelay: durationpb.New(duration)}) - - return s.Err() -} - -func TestNewExporter_collectorConnectionDiesThenReconnects(t *testing.T) { - // TODO: Fix this test #1527 - t.Skip("This test is flaky and needs to be rewritten") - mc := runMockCollector(t) - - reconnectionPeriod := 50 * time.Millisecond - ctx := context.Background() - exp := newGRPCExporter(t, ctx, mc.endpoint, - otlpmetricgrpc.WithRetry(otlpmetricgrpc.RetrySettings{Enabled: false}), - otlpmetricgrpc.WithReconnectionPeriod(reconnectionPeriod)) - defer func() { require.NoError(t, exp.Shutdown(ctx)) }() - - mc.ln.WaitForConn() - - // We'll now stop the collector right away to simulate a connection - // dying in the midst of communication or even not existing before. - require.NoError(t, mc.stop()) - - // In the test below, we'll stop the collector many times, - // while exporting metrics and test to ensure that we can - // reconnect. - for j := 0; j < 3; j++ { - - // No endpoint up. - require.Error(t, exp.Export(ctx, testResource, oneRecord)) - - // Now resurrect the collector by making a new one but reusing the - // old endpoint, and the collector should reconnect automatically. - nmc := runMockCollectorAtEndpoint(t, mc.endpoint) - - // Give the exporter sometime to reconnect - nmc.ln.WaitForConn() - - n := 10 - for i := 0; i < n; i++ { - require.NoError(t, exp.Export(ctx, testResource, oneRecord)) - } - - nmaMetrics := nmc.getMetrics() - // Expecting 10 metrics that were sampled, given that - if g, w := len(nmaMetrics), n; g != w { - t.Fatalf("Round #%d: Connected collector: spans: got %d want %d", j, g, w) - } - - dMetrics := mc.getMetrics() - // Expecting 0 metrics to have been received by the original but now dead collector - if g, w := len(dMetrics), 0; g != w { - t.Fatalf("Round #%d: Disconnected collector: spans: got %d want %d", j, g, w) - } - - // Disconnect for the next try. - require.NoError(t, nmc.stop()) - } -} - // This test takes a long time to run: to skip it, run tests using: -short func TestNewExporter_collectorOnBadConnection(t *testing.T) { if testing.Short() { @@ -641,7 +259,7 @@ func TestNewExporter_WithTimeout(t *testing.T) { }() ctx := context.Background() - exp := newGRPCExporter(t, ctx, mc.endpoint, otlpmetricgrpc.WithTimeout(tt.timeout), otlpmetricgrpc.WithRetry(otlpmetricgrpc.RetrySettings{Enabled: false})) + exp := newGRPCExporter(t, ctx, mc.endpoint, otlpmetricgrpc.WithTimeout(tt.timeout), otlpmetricgrpc.WithRetry(otlpmetricgrpc.RetryConfig{Enabled: false})) defer func() { _ = exp.Shutdown(ctx) }() @@ -662,48 +280,31 @@ func TestNewExporter_WithTimeout(t *testing.T) { } } -func TestNewExporter_withInvalidSecurityConfiguration(t *testing.T) { +func TestStartErrorInvalidSecurityConfiguration(t *testing.T) { mc := runMockCollector(t) defer func() { _ = mc.stop() }() - ctx := context.Background() client := otlpmetricgrpc.NewClient(otlpmetricgrpc.WithEndpoint(mc.endpoint)) - exp, err := otlpmetric.New(ctx, client) - if err != nil { - t.Fatalf("failed to create a new collector exporter: %v", err) - } - - err = exp.Export(ctx, testResource, oneRecord) - - expectedErr := fmt.Sprintf("metrics exporter is disconnected from the server %s: grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)", mc.endpoint) - - require.Error(t, err) - require.Equal(t, expectedErr, err.Error()) - - defer func() { - _ = exp.Shutdown(ctx) - }() + err := client.Start(context.Background()) + // https://github.com/grpc/grpc-go/blob/a671967dfbaab779d37fd7e597d9248f13806087/clientconn.go#L82 + assert.EqualError(t, err, "grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)") } -func TestDisconnected(t *testing.T) { - ctx := context.Background() - // The endpoint is whatever, we want to be disconnected. But we - // setting a blocking connection, so dialing to the invalid - // endpoint actually fails. - exp := newGRPCExporter(t, ctx, "invalid", - otlpmetricgrpc.WithReconnectionPeriod(time.Hour), +func TestStartErrorInvalidAddress(t *testing.T) { + client := otlpmetricgrpc.NewClient( + otlpmetricgrpc.WithInsecure(), + // Validate the connection in Start (which should return the error). otlpmetricgrpc.WithDialOption( grpc.WithBlock(), grpc.FailOnNonTempDialError(true), ), + otlpmetricgrpc.WithEndpoint("invalid"), + otlpmetricgrpc.WithReconnectionPeriod(time.Hour), ) - defer func() { - assert.NoError(t, exp.Shutdown(ctx)) - }() - - assert.Error(t, exp.Export(ctx, testResource, oneRecord)) + err := client.Start(context.Background()) + assert.EqualError(t, err, `connection error: desc = "transport: error while dialing: dial tcp: address invalid: missing port in address"`) } func TestEmptyData(t *testing.T) { diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_unit_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_unit_test.go new file mode 100644 index 000000000..ccd4ade13 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_unit_test.go @@ -0,0 +1,193 @@ +// 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 otlpmetricgrpc + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/durationpb" +) + +func TestThrottleDuration(t *testing.T) { + c := codes.ResourceExhausted + testcases := []struct { + status *status.Status + expected time.Duration + }{ + { + status: status.New(c, "no retry info"), + expected: 0, + }, + { + status: func() *status.Status { + s, err := status.New(c, "single retry info").WithDetails( + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(15 * time.Millisecond), + }, + ) + require.NoError(t, err) + return s + }(), + expected: 15 * time.Millisecond, + }, + { + status: func() *status.Status { + s, err := status.New(c, "error info").WithDetails( + &errdetails.ErrorInfo{Reason: "no throttle detail"}, + ) + require.NoError(t, err) + return s + }(), + expected: 0, + }, + { + status: func() *status.Status { + s, err := status.New(c, "error and retry info").WithDetails( + &errdetails.ErrorInfo{Reason: "with throttle detail"}, + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(13 * time.Minute), + }, + ) + require.NoError(t, err) + return s + }(), + expected: 13 * time.Minute, + }, + { + status: func() *status.Status { + s, err := status.New(c, "double retry info").WithDetails( + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(13 * time.Minute), + }, + &errdetails.RetryInfo{ + RetryDelay: durationpb.New(15 * time.Minute), + }, + ) + require.NoError(t, err) + return s + }(), + expected: 13 * time.Minute, + }, + } + + for _, tc := range testcases { + t.Run(tc.status.Message(), func(t *testing.T) { + require.Equal(t, tc.expected, throttleDelay(tc.status)) + }) + } +} + +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: true, + 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 TestUnstartedStop(t *testing.T) { + client := NewClient() + assert.ErrorIs(t, client.Stop(context.Background()), errAlreadyStopped) +} + +func TestUnstartedUploadMetric(t *testing.T) { + client := NewClient() + assert.ErrorIs(t, client.UploadMetrics(context.Background(), nil), errShutdown) +} + +func TestExportContextHonorsParentDeadline(t *testing.T) { + now := time.Now() + ctx, cancel := context.WithDeadline(context.Background(), now) + t.Cleanup(cancel) + + // Without a client timeout, the parent deadline should be used. + client := newClient(WithTimeout(0)) + eCtx, eCancel := client.exportContext(ctx) + t.Cleanup(eCancel) + + deadline, ok := eCtx.Deadline() + assert.True(t, ok, "deadline not propagated to child context") + assert.Equal(t, now, deadline) +} + +func TestExportContextHonorsClientTimeout(t *testing.T) { + // Setting a timeout should ensure a deadline is set on the context. + client := newClient(WithTimeout(1 * time.Second)) + ctx, cancel := client.exportContext(context.Background()) + t.Cleanup(cancel) + + _, ok := ctx.Deadline() + assert.True(t, ok, "timeout not set as deadline for child context") +} + +func TestExportContextLinksStopSignal(t *testing.T) { + rootCtx := context.Background() + + client := newClient(WithInsecure()) + t.Cleanup(func() { require.NoError(t, client.Stop(rootCtx)) }) + require.NoError(t, client.Start(rootCtx)) + + ctx, cancel := client.exportContext(rootCtx) + t.Cleanup(cancel) + + require.False(t, func() bool { + select { + case <-ctx.Done(): + return true + default: + } + return false + }(), "context should not be done prior to canceling it") + + // The client.stopFunc cancels the client.stopCtx. This should have been + // setup as a parent of ctx. Therefore, it should cancel ctx as well. + client.stopFunc() + + // Assert this with Eventually to account for goroutine scheduler timing. + assert.Eventually(t, func() bool { + select { + case <-ctx.Done(): + return true + default: + } + return false + }, 10*time.Second, time.Microsecond) +} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/go.mod b/exporters/otlp/otlpmetric/otlpmetricgrpc/go.mod index 32250b203..e697ddfad 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.25.0 go.opentelemetry.io/otel/metric v0.25.0 go.opentelemetry.io/otel/sdk v1.2.0 @@ -21,8 +22,6 @@ replace go.opentelemetry.io/otel/sdk => ../../../../sdk replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric -replace go.opentelemetry.io/otel/exporters/otlp => ../.. - replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../ replace go.opentelemetry.io/otel/metric => ../../../../metric @@ -80,3 +79,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/op replace go.opentelemetry.io/otel/example/fib => ../../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../internal/retry diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/go.sum b/exporters/otlp/otlpmetric/otlpmetricgrpc/go.sum index abf3eb08c..31b24cd6e 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/go.sum +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/go.sum @@ -24,6 +24,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/mock_collector_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/mock_collector_test.go index ea28c1a7f..3eecfef39 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/mock_collector_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/mock_collector_test.go @@ -18,8 +18,6 @@ import ( "context" "fmt" "net" - "runtime" - "strings" "sync" "testing" "time" @@ -94,7 +92,6 @@ type mockCollector struct { metricSvc *mockMetricService endpoint string - ln *listener stopFunc func() stopOnce sync.Once } @@ -160,9 +157,8 @@ func runMockCollectorWithConfig(t *testing.T, mockConfig *mockConfig) *mockColle srv := grpc.NewServer() mc := makeMockCollector(t, mockConfig) collectormetricpb.RegisterMetricsServiceServer(srv, mc.metricSvc) - mc.ln = newListener(ln) go func() { - _ = srv.Serve((net.Listener)(mc.ln)) + _ = srv.Serve(ln) }() mc.endpoint = ln.Addr().String() @@ -171,59 +167,3 @@ func runMockCollectorWithConfig(t *testing.T, mockConfig *mockConfig) *mockColle return mc } - -type listener struct { - closeOnce sync.Once - wrapped net.Listener - C chan struct{} -} - -func newListener(wrapped net.Listener) *listener { - return &listener{ - wrapped: wrapped, - C: make(chan struct{}, 1), - } -} - -func (l *listener) Close() error { return l.wrapped.Close() } - -func (l *listener) Addr() net.Addr { return l.wrapped.Addr() } - -// Accept waits for and returns the next connection to the listener. It will -// send a signal on l.C that a connection has been made before returning. -func (l *listener) Accept() (net.Conn, error) { - conn, err := l.wrapped.Accept() - if err != nil { - // Go 1.16 exported net.ErrClosed that could clean up this check, but to - // remain backwards compatible with previous versions of Go that we - // support the following string evaluation is used instead to keep in line - // with the previously recommended way to check this: - // https://github.com/golang/go/issues/4373#issuecomment-353076799 - if strings.Contains(err.Error(), "use of closed network connection") { - // If the listener has been closed, do not allow callers of - // WaitForConn to wait for a connection that will never come. - l.closeOnce.Do(func() { close(l.C) }) - } - return conn, err - } - - select { - case l.C <- struct{}{}: - default: - // If C is full, assume nobody is listening and move on. - } - return conn, nil -} - -// WaitForConn will wait indefintely for a connection to be estabilished with -// the listener before returning. -func (l *listener) WaitForConn() { - for { - select { - case <-l.C: - return - default: - runtime.Gosched() - } - } -} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/options.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/options.go index 1145fdc9f..e6d3919ea 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/options.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/options.go @@ -22,17 +22,29 @@ import ( "google.golang.org/grpc/credentials" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" ) -// Option applies an option to the gRPC client. +// Option applies an option to the gRPC driver. type Option interface { applyGRPCOption(*otlpconfig.Config) } -// RetrySettings defines configuration for retrying batches in case of export failure -// using an exponential backoff. -type RetrySettings otlpconfig.RetrySettings +func asGRPCOptions(opts []Option) []otlpconfig.GRPCOption { + converted := make([]otlpconfig.GRPCOption, len(opts)) + for i, o := range opts { + converted[i] = otlpconfig.NewGRPCOption(o.applyGRPCOption) + } + return converted +} + +// RetryConfig defines configuration for retrying export of span batches that +// failed to be received by the target endpoint. +// +// This configuration does not define any network retry strategy. That is +// entirely handled by the gRPC ClientConn. +type RetryConfig retry.Config type wrappedOption struct { otlpconfig.GRPCOption @@ -42,22 +54,28 @@ func (w wrappedOption) applyGRPCOption(cfg *otlpconfig.Config) { w.ApplyGRPCOption(cfg) } -// WithInsecure disables client transport security for the exporter's gRPC connection -// just like grpc.WithInsecure() https://pkg.go.dev/google.golang.org/grpc#WithInsecure -// does. Note, by default, client security is required unless WithInsecure is used. +// WithInsecure disables client transport security for the exporter's gRPC +// connection just like grpc.WithInsecure() +// (https://pkg.go.dev/google.golang.org/grpc#WithInsecure) does. Note, by +// default, client security is required unless WithInsecure is used. +// +// This option has no effect if WithGRPCConn is used. func WithInsecure() Option { return wrappedOption{otlpconfig.WithInsecure()} } -// WithEndpoint allows one to set the endpoint that the exporter will -// connect to the collector on. If unset, it will instead try to use -// connect to DefaultCollectorHost:DefaultCollectorPort. +// WithEndpoint sets the target endpoint the exporter will connect to. If +// unset, localhost:4317 will be used as a default. +// +// This option has no effect if WithGRPCConn is used. func WithEndpoint(endpoint string) Option { return wrappedOption{otlpconfig.WithEndpoint(endpoint)} } -// WithReconnectionPeriod allows one to set the delay between next connection attempt -// after failing to connect with the collector. +// WithReconnectionPeriod set the minimum amount of time between connection +// attempts to the target endpoint. +// +// This option has no effect if WithGRPCConn is used. func WithReconnectionPeriod(rp time.Duration) Option { return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) { cfg.ReconnectionPeriod = rp @@ -74,25 +92,30 @@ func compressorToCompression(compressor string) otlpconfig.Compression { return otlpconfig.NoCompression } -// WithCompressor will set the compressor for the gRPC client to use when sending requests. -// It is the responsibility of the caller to ensure that the compressor set has been registered -// with google.golang.org/grpc/encoding. This can be done by encoding.RegisterCompressor. Some -// compressors auto-register on import, such as gzip, which can be registered by calling +// WithCompressor sets the compressor for the gRPC client to use when sending +// requests. It is the responsibility of the caller to ensure that the +// compressor set has been registered with google.golang.org/grpc/encoding. +// This can be done by encoding.RegisterCompressor. Some compressors +// auto-register on import, such as gzip, which can be registered by calling // `import _ "google.golang.org/grpc/encoding/gzip"`. +// +// This option has no effect if WithGRPCConn is used. func WithCompressor(compressor string) Option { return wrappedOption{otlpconfig.WithCompression(compressorToCompression(compressor))} } -// WithHeaders will send the provided headers with gRPC requests. +// WithHeaders will send the provided headers with each gRPC requests. func WithHeaders(headers map[string]string) Option { return wrappedOption{otlpconfig.WithHeaders(headers)} } -// WithTLSCredentials allows the connection to use TLS credentials -// when talking to the server. It takes in grpc.TransportCredentials instead -// of say a Certificate file or a tls.Certificate, because the retrieving of -// these credentials can be done in many ways e.g. plain file, in code tls.Config -// or by certificate rotation, so it is up to the caller to decide what to use. +// WithTLSCredentials allows the connection to use TLS credentials when +// talking to the server. It takes in grpc.TransportCredentials instead of say +// a Certificate file or a tls.Certificate, because the retrieving of these +// credentials can be done in many ways e.g. plain file, in code tls.Config or +// by certificate rotation, so it is up to the caller to decide what to use. +// +// This option has no effect if WithGRPCConn is used. func WithTLSCredentials(creds credentials.TransportCredentials) Option { return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) { cfg.Metrics.GRPCCredentials = creds @@ -100,40 +123,63 @@ func WithTLSCredentials(creds credentials.TransportCredentials) Option { } // WithServiceConfig defines the default gRPC service config used. +// +// This option has no effect if WithGRPCConn is used. func WithServiceConfig(serviceConfig string) Option { return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) { cfg.ServiceConfig = serviceConfig })} } -// WithDialOption opens support to any grpc.DialOption to be used. If it conflicts -// with some other configuration the GRPC specified via the collector the ones here will -// take preference since they are set last. +// WithDialOption sets explicit grpc.DialOptions to use when making a +// connection. The options here are appended to the internal grpc.DialOptions +// used so they will take precedence over any other internal grpc.DialOptions +// they might conflict with. +// +// This option has no effect if WithGRPCConn is used. func WithDialOption(opts ...grpc.DialOption) Option { return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) { cfg.DialOptions = opts })} } -// WithGRPCConn allows reusing existing gRPC connection when it has already been -// established for other services. When set, other dial options will be ignored. +// WithGRPCConn sets conn as the gRPC ClientConn used for all communication. +// +// This option takes precedence over any other option that relates to +// establishing or persisting a gRPC connection to a target endpoint. Any +// other option of those types passed will be ignored. +// +// It is the callers responsibility to close the passed conn. The client +// Shutdown method will not close this connection. func WithGRPCConn(conn *grpc.ClientConn) Option { return wrappedOption{otlpconfig.NewGRPCOption(func(cfg *otlpconfig.Config) { cfg.GRPCConn = conn })} } -// WithTimeout tells the client the max waiting time for the backend to process -// each metrics batch. If unset, the default will be 10 seconds. +// WithTimeout sets the max amount of time a client will attempt to export a +// batch of spans. This takes precedence over any retry settings defined with +// WithRetry, once this time limit has been reached the export is abandoned +// and the batch of spans is dropped. +// +// If unset, the default timeout will be set to 10 seconds. func WithTimeout(duration time.Duration) Option { return wrappedOption{otlpconfig.WithTimeout(duration)} } -// WithRetry configures the retry policy for transient errors that may occurs when -// exporting metrics. An exponential back-off algorithm is used to -// ensure endpoints are not overwhelmed with retries. If unset, the default -// retry policy will retry after 5 seconds and increase exponentially after each -// error for a total of 1 minute. -func WithRetry(settings RetrySettings) Option { - return wrappedOption{otlpconfig.WithRetry(otlpconfig.RetrySettings(settings))} +// WithRetry sets the retry policy for transient retryable errors that may be +// returned by the target endpoint when exporting a batch of spans. +// +// If the target endpoint responds with not only a retryable error, but +// explicitly returns a backoff time in the response. That time will take +// precedence over these settings. +// +// These settings do not define any network retry strategy. That is entirely +// handled by the gRPC ClientConn. +// +// If unset, the default retry policy will be used. It will retry the export +// 5 seconds after receiving a retryable error and increase exponentially +// after each error for no more than a total time of 1 minute. +func WithRetry(settings RetryConfig) Option { + return wrappedOption{otlpconfig.WithRetry(retry.Config(settings))} } diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index e4e5c67e6..b78067dc0 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -21,16 +21,17 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" "net" "net/http" "path" + "strconv" "strings" + "sync" "time" "google.golang.org/protobuf/proto" - "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" @@ -39,6 +40,13 @@ import ( const contentTypeProto = "application/x-protobuf" +var gzPool = sync.Pool{ + New: func() interface{} { + w := gzip.NewWriter(ioutil.Discard) + return w + }, +} + // Keep it in sync with golang's DefaultTransport from net/http! We // have our own copy to avoid handling a situation where the // DefaultTransport is overwritten with some different implementation @@ -57,11 +65,13 @@ var ourTransport = &http.Transport{ } type client struct { - name string - cfg otlpconfig.SignalConfig - generalCfg otlpconfig.Config - client *http.Client - stopCh chan struct{} + name string + cfg otlpconfig.SignalConfig + generalCfg otlpconfig.Config + requestFunc retry.RequestFunc + client *http.Client + stopCh chan struct{} + stopOnce sync.Once } // NewClient creates a new HTTP metric client. @@ -73,7 +83,7 @@ func NewClient(opts ...Option) otlpmetric.Client { } for pathPtr, defaultPath := range map[*string]string{ - &cfg.Metrics.URLPath: defaultMetricsPath, + &cfg.Metrics.URLPath: otlpconfig.DefaultMetricsPath, } { tmp := strings.TrimSpace(*pathPtr) if tmp == "" { @@ -86,15 +96,6 @@ func NewClient(opts ...Option) otlpmetric.Client { } *pathPtr = tmp } - if cfg.MaxAttempts <= 0 { - cfg.MaxAttempts = defaultMaxAttempts - } - if cfg.MaxAttempts > defaultMaxAttempts { - cfg.MaxAttempts = defaultMaxAttempts - } - if cfg.Backoff <= 0 { - cfg.Backoff = defaultBackoff - } httpClient := &http.Client{ Transport: ourTransport, @@ -108,11 +109,12 @@ func NewClient(opts ...Option) otlpmetric.Client { stopCh := make(chan struct{}) return &client{ - name: "metrics", - cfg: cfg.Metrics, - generalCfg: cfg, - stopCh: stopCh, - client: httpClient, + name: "metrics", + cfg: cfg.Metrics, + generalCfg: cfg, + requestFunc: cfg.RetryConfig.RequestFunc(evaluate), + stopCh: stopCh, + client: httpClient, } } @@ -129,7 +131,9 @@ func (d *client) Start(ctx context.Context) error { // Stop shuts down the client and interrupt any in-flight request. func (d *client) Stop(ctx context.Context) error { - close(d.stopCh) + d.stopOnce.Do(func() { + close(d.stopCh) + }) select { case <-ctx.Done(): return ctx.Err() @@ -147,41 +151,150 @@ func (d *client) UploadMetrics(ctx context.Context, protoMetrics []*metricpb.Res if err != nil { return err } - return d.send(ctx, rawRequest) -} -func (d *client) send(ctx context.Context, rawRequest []byte) error { - address := fmt.Sprintf("%s://%s%s", d.getScheme(), d.cfg.Endpoint, d.cfg.URLPath) - var cancel context.CancelFunc - ctx, cancel = d.contextWithStop(ctx) + ctx, cancel := d.contextWithStop(ctx) defer cancel() - for i := 0; i < d.generalCfg.MaxAttempts; i++ { - response, err := d.singleSend(ctx, rawRequest, address) + + request, err := d.newRequest(rawRequest) + if err != nil { + return err + } + + return d.requestFunc(ctx, func(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + request.reset(ctx) + resp, err := d.client.Do(request.Request) if err != nil { return err } - // We don't care about the body, so try to read it - // into /dev/null and close it immediately. The - // reading part is to facilitate connection reuse. - _, _ = io.Copy(ioutil.Discard, response.Body) - _ = response.Body.Close() - switch response.StatusCode { + + var rErr error + switch resp.StatusCode { case http.StatusOK: - return nil - case http.StatusTooManyRequests: - fallthrough - case http.StatusServiceUnavailable: - select { - case <-time.After(getWaitDuration(d.generalCfg.Backoff, i)): - continue - case <-ctx.Done(): - return ctx.Err() + // Success, do not retry. + case http.StatusTooManyRequests, + http.StatusServiceUnavailable: + // Retry-able failure. + rErr = newResponseError(resp.Header) + + // Going to retry, drain the body to reuse the connection. + if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil { + _ = resp.Body.Close() + return err } default: - return fmt.Errorf("failed to send %s to %s with HTTP status %s", d.name, address, response.Status) + rErr = fmt.Errorf("failed to send %s to %s: %s", d.name, request.URL, resp.Status) + } + + if err := resp.Body.Close(); err != nil { + return err + } + return rErr + }) +} + +func (d *client) newRequest(body []byte) (request, error) { + address := fmt.Sprintf("%s://%s%s", d.getScheme(), d.cfg.Endpoint, d.cfg.URLPath) + r, err := http.NewRequest(http.MethodPost, address, nil) + if err != nil { + return request{Request: r}, err + } + + for k, v := range d.cfg.Headers { + r.Header.Set(k, v) + } + r.Header.Set("Content-Type", contentTypeProto) + + req := request{Request: r} + switch Compression(d.cfg.Compression) { + case NoCompression: + r.ContentLength = (int64)(len(body)) + req.bodyReader = bodyReader(body) + case GzipCompression: + // Ensure the content length is not used. + r.ContentLength = -1 + r.Header.Set("Content-Encoding", "gzip") + + gz := gzPool.Get().(*gzip.Writer) + defer gzPool.Put(gz) + + var b bytes.Buffer + gz.Reset(&b) + + if _, err := gz.Write(body); err != nil { + return req, err + } + // Close needs to be called to ensure body if fully written. + if err := gz.Close(); err != nil { + return req, err + } + + req.bodyReader = bodyReader(b.Bytes()) + } + + return req, nil +} + +// bodyReader returns a closure returning a new reader for buf. +func bodyReader(buf []byte) func() io.ReadCloser { + return func() io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader(buf)) + } +} + +// request wraps an http.Request with a resettable body reader. +type request struct { + *http.Request + + // bodyReader allows the same body to be used for multiple requests. + bodyReader func() io.ReadCloser +} + +// reset reinitializes the request Body and uses ctx for the request. +func (r *request) reset(ctx context.Context) { + r.Body = r.bodyReader() + r.Request = r.Request.WithContext(ctx) +} + +// retryableError represents a request failure that can be retried. +type retryableError struct { + throttle int64 +} + +// newResponseError returns a retryableError and will extract any explicit +// throttle delay contained in headers. +func newResponseError(header http.Header) error { + var rErr retryableError + if s, ok := header["Retry-After"]; ok { + if t, err := strconv.ParseInt(s[0], 10, 64); err == nil { + rErr.throttle = t } } - return fmt.Errorf("failed to send data to %s after %d tries", address, d.generalCfg.MaxAttempts) + return rErr +} + +func (e retryableError) Error() string { + return "retry-able request failure" +} + +// evaluate returns if err is retry-able. If it is and it includes an explicit +// throttling delay, that delay is also returned. +func evaluate(err error) (bool, time.Duration) { + if err == nil { + return false, 0 + } + + rErr, ok := err.(retryableError) + if !ok { + return false, 0 + } + + return true, time.Duration(rErr.throttle) } func (d *client) getScheme() string { @@ -191,26 +304,6 @@ func (d *client) getScheme() string { return "https" } -func getWaitDuration(backoff time.Duration, i int) time.Duration { - // Strategy: after nth failed attempt, attempt resending after - // k * initialBackoff + jitter, where k is a random number in - // range [0, 2^n-1), and jitter is a random percentage of - // initialBackoff from [-5%, 5%). - // - // Based on - // https://en.wikipedia.org/wiki/Exponential_backoff#Example_exponential_backoff_algorithm - // - // Jitter is our addition. - - // There won't be an overflow, since i is capped to - // defaultMaxAttempts (5). - upperK := (int64)(1) << (i + 1) - jitterPercent := (rand.Float64() - 0.5) / 10. - jitter := jitterPercent * (float64)(backoff) - k := rand.Int63n(upperK) - return (time.Duration)(k)*backoff + (time.Duration)(jitter) -} - func (d *client) contextWithStop(ctx context.Context) (context.Context, context.CancelFunc) { // Unify the parent context Done signal with the client's stop // channel. @@ -226,51 +319,3 @@ func (d *client) contextWithStop(ctx context.Context) (context.Context, context. }(ctx, cancel) return ctx, cancel } - -func (d *client) singleSend(ctx context.Context, rawRequest []byte, address string) (*http.Response, error) { - request, err := http.NewRequestWithContext(ctx, http.MethodPost, address, nil) - if err != nil { - return nil, err - } - bodyReader, contentLength, headers := d.prepareBody(rawRequest) - // Not closing bodyReader through defer, the HTTP Client's - // Transport will do it for us - request.Body = bodyReader - request.ContentLength = contentLength - for key, values := range headers { - for _, value := range values { - request.Header.Add(key, value) - } - } - return d.client.Do(request) -} - -func (d *client) prepareBody(rawRequest []byte) (io.ReadCloser, int64, http.Header) { - var bodyReader io.ReadCloser - headers := http.Header{} - for k, v := range d.cfg.Headers { - headers.Set(k, v) - } - contentLength := (int64)(len(rawRequest)) - headers.Set("Content-Type", contentTypeProto) - requestReader := bytes.NewBuffer(rawRequest) - switch Compression(d.cfg.Compression) { - case NoCompression: - bodyReader = ioutil.NopCloser(requestReader) - case GzipCompression: - preader, pwriter := io.Pipe() - go func() { - defer pwriter.Close() - gzipper := gzip.NewWriter(pwriter) - defer gzipper.Close() - _, err := io.Copy(gzipper, requestReader) - if err != nil { - otel.Handle(fmt.Errorf("otlphttp: failed to gzip request: %v", err)) - } - }() - headers.Set("Content-Encoding", "gzip") - bodyReader = preader - contentLength = -1 - } - return bodyReader, contentLength, headers -} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index 4fb116972..e15d8b35d 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -16,7 +16,6 @@ package otlpmetrichttp_test import ( "context" - "fmt" "net/http" "os" "testing" @@ -144,32 +143,6 @@ func TestExporterShutdown(t *testing.T) { }) } -func TestRetry(t *testing.T) { - statuses := []int{ - http.StatusTooManyRequests, - http.StatusServiceUnavailable, - } - mcCfg := mockCollectorConfig{ - InjectHTTPStatus: statuses, - } - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - client := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithMaxAttempts(len(statuses)+1), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, client) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - err = exporter.Export(ctx, testResource, oneRecord) - assert.NoError(t, err) - assert.Len(t, mc.GetMetrics(), 1) -} - func TestTimeout(t *testing.T) { mcCfg := mockCollectorConfig{ InjectDelay: 100 * time.Millisecond, @@ -191,58 +164,6 @@ func TestTimeout(t *testing.T) { assert.Equal(t, true, os.IsTimeout(err)) } -func TestRetryFailed(t *testing.T) { - statuses := []int{ - http.StatusTooManyRequests, - http.StatusServiceUnavailable, - } - mcCfg := mockCollectorConfig{ - InjectHTTPStatus: statuses, - } - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - driver := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithMaxAttempts(1), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - err = exporter.Export(ctx, testResource, oneRecord) - assert.Error(t, err) - assert.Empty(t, mc.GetMetrics()) -} - -func TestNoRetry(t *testing.T) { - statuses := []int{ - http.StatusBadRequest, - } - mcCfg := mockCollectorConfig{ - InjectHTTPStatus: statuses, - } - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - driver := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithMaxAttempts(len(statuses)+1), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - err = exporter.Export(ctx, testResource, oneRecord) - assert.Error(t, err) - assert.Equal(t, fmt.Sprintf("failed to send metrics to http://%s/v1/metrics with HTTP status 400 Bad Request", mc.endpoint), err.Error()) - assert.Empty(t, mc.GetMetrics()) -} - func TestEmptyData(t *testing.T) { mcCfg := mockCollectorConfig{} mc := runMockCollector(t, mcCfg) @@ -263,88 +184,6 @@ func TestEmptyData(t *testing.T) { assert.NotEmpty(t, mc.GetMetrics()) } -func TestUnreasonableMaxAttempts(t *testing.T) { - // Max attempts is 5, we set collector to fail 7 times and try - // to configure max attempts to be either negative or too - // large. Since we set max attempts to 5 in such cases, - // exporting to the collector should fail. - type testcase struct { - name string - maxAttempts int - } - for _, tc := range []testcase{ - { - name: "negative max attempts", - maxAttempts: -3, - }, - { - name: "too large max attempts", - maxAttempts: 10, - }, - } { - t.Run(tc.name, func(t *testing.T) { - statuses := make([]int, 0, 7) - for i := 0; i < cap(statuses); i++ { - statuses = append(statuses, http.StatusTooManyRequests) - } - mcCfg := mockCollectorConfig{ - InjectHTTPStatus: statuses, - } - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - driver := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithMaxAttempts(tc.maxAttempts), - otlpmetrichttp.WithBackoff(time.Millisecond), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - err = exporter.Export(ctx, testResource, oneRecord) - assert.Error(t, err) - assert.Empty(t, mc.GetMetrics()) - }) - } -} - -func TestUnreasonableBackoff(t *testing.T) { - // This sets backoff to negative value, which gets corrected - // to default backoff instead of being used. Default max - // attempts is 5, so we set the collector to fail 4 times, but - // we set the deadline to 3 times of the default backoff, so - // this should show that deadline is not met, meaning that the - // retries weren't immediate (as negative backoff could - // imply). - statuses := make([]int, 0, 4) - for i := 0; i < cap(statuses); i++ { - statuses = append(statuses, http.StatusTooManyRequests) - } - mcCfg := mockCollectorConfig{ - InjectHTTPStatus: statuses, - } - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - driver := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithBackoff(-time.Millisecond), - ) - ctx, cancel := context.WithTimeout(context.Background(), 3*(300*time.Millisecond)) - defer cancel() - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(context.Background())) - }() - err = exporter.Export(ctx, testResource, oneRecord) - assert.Error(t, err) - assert.Empty(t, mc.GetMetrics()) -} - func TestCancelledContext(t *testing.T) { statuses := []int{ http.StatusBadRequest, diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go new file mode 100644 index 000000000..4ba01c85e --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go @@ -0,0 +1,68 @@ +// 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 otlpmetrichttp + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnreasonableBackoff(t *testing.T) { + cIface := NewClient( + WithEndpoint("http://localhost"), + WithInsecure(), + WithBackoff(-time.Microsecond), + ) + require.IsType(t, &client{}, cIface) + c := cIface.(*client) + assert.True(t, c.generalCfg.RetryConfig.Enabled) + assert.Equal(t, 5*time.Second, c.generalCfg.RetryConfig.InitialInterval) + assert.Equal(t, 300*time.Millisecond, c.generalCfg.RetryConfig.MaxInterval) + assert.Equal(t, time.Minute, c.generalCfg.RetryConfig.MaxElapsedTime) +} + +func TestUnreasonableMaxAttempts(t *testing.T) { + type testcase struct { + name string + maxAttempts int + } + for _, tc := range []testcase{ + { + name: "negative max attempts", + maxAttempts: -3, + }, + { + name: "too large max attempts", + maxAttempts: 10, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cIface := NewClient( + WithEndpoint("http://localhost"), + WithInsecure(), + WithMaxAttempts(tc.maxAttempts), + ) + require.IsType(t, &client{}, cIface) + c := cIface.(*client) + assert.True(t, c.generalCfg.RetryConfig.Enabled) + assert.Equal(t, 5*time.Second, c.generalCfg.RetryConfig.InitialInterval) + assert.Equal(t, 30*time.Second, c.generalCfg.RetryConfig.MaxInterval) + assert.Equal(t, 145*time.Second, c.generalCfg.RetryConfig.MaxElapsedTime) + }) + } +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index 14740d119..f8cc9ddc8 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -4,7 +4,7 @@ go 1.16 require ( github.com/stretchr/testify v1.7.0 - go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.25.0 go.opentelemetry.io/otel/sdk v1.2.0 go.opentelemetry.io/proto/otlp v0.11.0 @@ -17,8 +17,6 @@ replace go.opentelemetry.io/otel/sdk => ../../../../sdk replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric -replace go.opentelemetry.io/otel/exporters/otlp => ../.. - replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../ replace go.opentelemetry.io/otel/metric => ../../../../metric @@ -82,3 +80,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/op replace go.opentelemetry.io/otel/example/fib => ../../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../internal/retry diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum index ecbbaba5f..31b24cd6e 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum @@ -4,6 +4,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -23,6 +24,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/options.go b/exporters/otlp/otlpmetric/otlpmetrichttp/options.go index ccb59415a..61dbccc30 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/options.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/options.go @@ -18,22 +18,10 @@ import ( "crypto/tls" "time" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" ) -const ( - // defaultMaxAttempts describes how many times the driver - // should retry the sending of the payload in case of a - // retryable error. - defaultMaxAttempts int = 5 - // defaultMetricsPath is a default URL path for endpoint that - // receives metrics. - defaultMetricsPath string = "/v1/metrics" - // defaultBackoff is a default base backoff time used in the - // exponential backoff strategy. - defaultBackoff time.Duration = 300 * time.Millisecond -) - // Compression describes the compression used for payloads sent to the // collector. type Compression otlpconfig.Compression @@ -52,6 +40,10 @@ type Option interface { applyHTTPOption(*otlpconfig.Config) } +// RetryConfig defines configuration for retrying batches in case of export +// failure using an exponential backoff. +type RetryConfig retry.Config + type wrappedOption struct { otlpconfig.HTTPOption } @@ -84,15 +76,67 @@ func WithURLPath(urlPath string) Option { // will try to send the payload in case of retryable errors. // The max attempts is limited to at most 5 retries. If unset, // default (5) will be used. +// +// Deprecated: Use WithRetry instead. func WithMaxAttempts(maxAttempts int) Option { - return wrappedOption{otlpconfig.WithMaxAttempts(maxAttempts)} + if maxAttempts > 5 || maxAttempts < 0 { + maxAttempts = 5 + } + return wrappedOption{ + otlpconfig.NewHTTPOption(func(cfg *otlpconfig.Config) { + cfg.RetryConfig.Enabled = true + + var ( + init = cfg.RetryConfig.InitialInterval + maxI = cfg.RetryConfig.MaxInterval + maxE = cfg.RetryConfig.MaxElapsedTime + ) + + if init == 0 { + init = retry.DefaultConfig.InitialInterval + } + if maxI == 0 { + maxI = retry.DefaultConfig.MaxInterval + } + if maxE == 0 { + maxE = retry.DefaultConfig.MaxElapsedTime + } + attempts := int64(maxE+init) / int64(maxI) + + if int64(maxAttempts) == attempts { + return + } + + maxE = time.Duration(int64(maxAttempts)*int64(maxI)) - init + + cfg.RetryConfig.InitialInterval = init + cfg.RetryConfig.MaxInterval = maxI + cfg.RetryConfig.MaxElapsedTime = maxE + }), + } } // WithBackoff tells the driver to use the duration as a base of the // exponential backoff strategy. If unset, default (300ms) will be // used. +// +// Deprecated: Use WithRetry instead. func WithBackoff(duration time.Duration) Option { - return wrappedOption{otlpconfig.WithBackoff(duration)} + if duration < 0 { + duration = 300 * time.Millisecond + } + return wrappedOption{ + otlpconfig.NewHTTPOption(func(cfg *otlpconfig.Config) { + cfg.RetryConfig.Enabled = true + cfg.RetryConfig.MaxInterval = duration + if cfg.RetryConfig.InitialInterval == 0 { + cfg.RetryConfig.InitialInterval = retry.DefaultConfig.InitialInterval + } + if cfg.RetryConfig.MaxElapsedTime == 0 { + cfg.RetryConfig.MaxElapsedTime = retry.DefaultConfig.MaxElapsedTime + } + }), + } } // WithTLSClientConfig can be used to set up a custom TLS @@ -120,3 +164,12 @@ func WithHeaders(headers map[string]string) Option { func WithTimeout(duration time.Duration) Option { return wrappedOption{otlpconfig.WithTimeout(duration)} } + +// WithRetry configures the retry policy for transient errors that may occurs +// when exporting traces. An exponential back-off algorithm is used to ensure +// endpoints are not overwhelmed with retries. If unset, the default retry +// policy will retry after 5 seconds and increase exponentially after each +// error for a total of 1 minute. +func WithRetry(rc RetryConfig) Option { + return wrappedOption{otlpconfig.WithRetry(retry.Config(rc))} +} diff --git a/exporters/otlp/otlptrace/go.mod b/exporters/otlp/otlptrace/go.mod index d24a4ecea..b70ce4c28 100644 --- a/exporters/otlp/otlptrace/go.mod +++ b/exporters/otlp/otlptrace/go.mod @@ -3,10 +3,10 @@ module go.opentelemetry.io/otel/exporters/otlp/otlptrace go 1.16 require ( - github.com/cenkalti/backoff/v4 v4.1.2 github.com/google/go-cmp v0.5.6 github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/sdk v1.2.0 go.opentelemetry.io/otel/trace v1.2.0 go.opentelemetry.io/proto/otlp v0.11.0 @@ -77,3 +77,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../bridge/openc replace go.opentelemetry.io/otel/example/fib => ../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../internal/retry diff --git a/exporters/otlp/otlptrace/go.sum b/exporters/otlp/otlptrace/go.sum index 6431c94e4..154c31544 100644 --- a/exporters/otlp/otlptrace/go.sum +++ b/exporters/otlp/otlptrace/go.sum @@ -22,6 +22,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/otlp/otlptrace/internal/otlpconfig/options.go b/exporters/otlp/otlptrace/internal/otlpconfig/options.go index 0e2c330a0..6cfb9fd2e 100644 --- a/exporters/otlp/otlptrace/internal/otlpconfig/options.go +++ b/exporters/otlp/otlptrace/internal/otlpconfig/options.go @@ -24,7 +24,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/retry" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" ) const ( diff --git a/exporters/otlp/otlptrace/otlptracegrpc/client.go b/exporters/otlp/otlptrace/otlptracegrpc/client.go index 03bb359f1..d709ffa96 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/client.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/client.go @@ -26,9 +26,9 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/retry" coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" ) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/go.mod b/exporters/otlp/otlptrace/otlptracegrpc/go.mod index 2959daf4b..63e48fe5d 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/go.mod +++ b/exporters/otlp/otlptrace/otlptracegrpc/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.2.0 go.opentelemetry.io/otel/sdk v1.2.0 go.opentelemetry.io/proto/otlp v0.11.0 @@ -77,3 +78,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/op replace go.opentelemetry.io/otel/example/fib => ../../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../internal/retry diff --git a/exporters/otlp/otlptrace/otlptracegrpc/go.sum b/exporters/otlp/otlptrace/otlptracegrpc/go.sum index 819fbaac1..1e6992c36 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/go.sum +++ b/exporters/otlp/otlptrace/otlptracegrpc/go.sum @@ -22,6 +22,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/otlp/otlptrace/otlptracegrpc/options.go b/exporters/otlp/otlptrace/otlptracegrpc/options.go index 8948c0e9c..0b27a35a8 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/options.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/options.go @@ -22,8 +22,8 @@ import ( "google.golang.org/grpc/credentials" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/retry" ) // Option applies an option to the gRPC driver. diff --git a/exporters/otlp/otlptrace/otlptracehttp/client.go b/exporters/otlp/otlptrace/otlptracehttp/client.go index f51e4bc78..93a7e4a7e 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/client.go +++ b/exporters/otlp/otlptrace/otlptracehttp/client.go @@ -31,9 +31,9 @@ import ( "google.golang.org/protobuf/proto" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/retry" coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" ) diff --git a/exporters/otlp/otlptrace/otlptracehttp/go.mod b/exporters/otlp/otlptrace/otlptracehttp/go.mod index c12452faf..317cc6b95 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/go.mod +++ b/exporters/otlp/otlptrace/otlptracehttp/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v1.2.0 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.2.0 go.opentelemetry.io/otel/sdk v1.2.0 go.opentelemetry.io/otel/trace v1.2.0 @@ -75,3 +76,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../../bridge/op replace go.opentelemetry.io/otel/example/fib => ../../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../internal/retry diff --git a/exporters/otlp/otlptrace/otlptracehttp/go.sum b/exporters/otlp/otlptrace/otlptracehttp/go.sum index 6431c94e4..154c31544 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/go.sum +++ b/exporters/otlp/otlptrace/otlptracehttp/go.sum @@ -22,6 +22,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/otlp/otlptrace/otlptracehttp/options.go b/exporters/otlp/otlptrace/otlptracehttp/options.go index 7cb45010e..5b52f8fc6 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/options.go +++ b/exporters/otlp/otlptrace/otlptracehttp/options.go @@ -18,8 +18,8 @@ import ( "crypto/tls" "time" + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/retry" ) // Compression describes the compression used for payloads sent to the diff --git a/exporters/prometheus/go.mod b/exporters/prometheus/go.mod index 1459bc713..60126cac9 100644 --- a/exporters/prometheus/go.mod +++ b/exporters/prometheus/go.mod @@ -34,8 +34,6 @@ replace go.opentelemetry.io/otel/example/prometheus => ../../example/prometheus replace go.opentelemetry.io/otel/example/zipkin => ../../example/zipkin -replace go.opentelemetry.io/otel/exporters/otlp => ../otlp - replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../otlp/otlptrace replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => ../otlp/otlptrace/otlptracegrpc @@ -77,3 +75,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../otlp/internal/retry diff --git a/exporters/prometheus/go.sum b/exporters/prometheus/go.sum index 2726808ee..aca5dcc5e 100644 --- a/exporters/prometheus/go.sum +++ b/exporters/prometheus/go.sum @@ -21,6 +21,11 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/exporters/stdout/stdoutmetric/go.mod b/exporters/stdout/stdoutmetric/go.mod index 757ea6f9f..3e9195b8c 100644 --- a/exporters/stdout/stdoutmetric/go.mod +++ b/exporters/stdout/stdoutmetric/go.mod @@ -75,3 +75,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../bridge/openc replace go.opentelemetry.io/otel/example/fib => ../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../otlp/internal/retry diff --git a/exporters/stdout/stdoutmetric/go.sum b/exporters/stdout/stdoutmetric/go.sum index e9663039d..a4acc8a9b 100644 --- a/exporters/stdout/stdoutmetric/go.sum +++ b/exporters/stdout/stdoutmetric/go.sum @@ -2,6 +2,11 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/exporters/stdout/stdoutmetric/metric.go b/exporters/stdout/stdoutmetric/metric.go index 07333c64f..4dde782da 100644 --- a/exporters/stdout/stdoutmetric/metric.go +++ b/exporters/stdout/stdoutmetric/metric.go @@ -37,8 +37,6 @@ var _ exportmetric.Exporter = &metricExporter{} type line struct { Name string `json:"Name"` - Min interface{} `json:"Min,omitempty"` - Max interface{} `json:"Max,omitempty"` Sum interface{} `json:"Sum,omitempty"` Count interface{} `json:"Count,omitempty"` LastValue interface{} `json:"Last,omitempty"` @@ -83,26 +81,6 @@ func (e *metricExporter) Export(_ context.Context, res *resource.Resource, reade return err } expose.Sum = value.AsInterface(kind) - } - - if mmsc, ok := agg.(aggregation.MinMaxSumCount); ok { - count, err := mmsc.Count() - if err != nil { - return err - } - expose.Count = count - - max, err := mmsc.Max() - if err != nil { - return err - } - expose.Max = max.AsInterface(kind) - - min, err := mmsc.Min() - if err != nil { - return err - } - expose.Min = min.AsInterface(kind) } else if lv, ok := agg.(aggregation.LastValue); ok { value, timestamp, err := lv.LastValue() if err != nil { diff --git a/exporters/stdout/stdoutmetric/metric_test.go b/exporters/stdout/stdoutmetric/metric_test.go index 0fb243f5d..2106e6550 100644 --- a/exporters/stdout/stdoutmetric/metric_test.go +++ b/exporters/stdout/stdoutmetric/metric_test.go @@ -156,18 +156,6 @@ func TestStdoutLastValueFormat(t *testing.T) { require.Equal(t, `[{"Name":"name.lastvalue{R=V,instrumentation.name=test,A=B,C=D}","Last":123.456}]`, fix.Output()) } -func TestStdoutMinMaxSumCount(t *testing.T) { - fix := newFixture(t) - - counter := metric.Must(fix.meter).NewFloat64Counter("name.minmaxsumcount") - counter.Add(fix.ctx, 123.456, attribute.String("A", "B"), attribute.String("C", "D")) - counter.Add(fix.ctx, 876.543, attribute.String("A", "B"), attribute.String("C", "D")) - - require.NoError(t, fix.cont.Stop(fix.ctx)) - - require.Equal(t, `[{"Name":"name.minmaxsumcount{R=V,instrumentation.name=test,A=B,C=D}","Min":123.456,"Max":876.543,"Sum":999.999,"Count":2}]`, fix.Output()) -} - func TestStdoutHistogramFormat(t *testing.T) { fix := newFixture(t, stdoutmetric.WithPrettyPrint()) @@ -201,7 +189,6 @@ func TestStdoutNoData(t *testing.T) { } runTwoAggs("lastvalue") - runTwoAggs("minmaxsumcount") } func TestStdoutResource(t *testing.T) { diff --git a/exporters/stdout/stdouttrace/go.mod b/exporters/stdout/stdouttrace/go.mod index 7a8945e43..433947486 100644 --- a/exporters/stdout/stdouttrace/go.mod +++ b/exporters/stdout/stdouttrace/go.mod @@ -73,3 +73,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../bridge/openc replace go.opentelemetry.io/otel/example/fib => ../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../otlp/internal/retry diff --git a/exporters/stdout/stdouttrace/go.sum b/exporters/stdout/stdouttrace/go.sum index 893275e19..7c328dde1 100644 --- a/exporters/stdout/stdouttrace/go.sum +++ b/exporters/stdout/stdouttrace/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/exporters/zipkin/go.mod b/exporters/zipkin/go.mod index d39fa877f..b78cfb085 100644 --- a/exporters/zipkin/go.mod +++ b/exporters/zipkin/go.mod @@ -31,8 +31,6 @@ replace go.opentelemetry.io/otel/example/zipkin => ../../example/zipkin replace go.opentelemetry.io/otel/exporters/prometheus => ../prometheus -replace go.opentelemetry.io/otel/exporters/otlp => ../otlp - replace go.opentelemetry.io/otel/exporters/jaeger => ../jaeger replace go.opentelemetry.io/otel/exporters/zipkin => ./ @@ -76,3 +74,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../otlp/internal/retry diff --git a/exporters/zipkin/go.sum b/exporters/zipkin/go.sum index 59e5b271e..b69dcb4d2 100644 --- a/exporters/zipkin/go.sum +++ b/exporters/zipkin/go.sum @@ -29,6 +29,11 @@ github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/go.mod b/go.mod index 3b8141637..0867c67df 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module go.opentelemetry.io/otel go 1.16 require ( + github.com/go-logr/logr v1.2.1 + github.com/go-logr/stdr v1.2.0 github.com/google/go-cmp v0.5.6 github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel/trace v1.2.0 @@ -71,3 +73,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ./bridge/opencensus/t replace go.opentelemetry.io/otel/example/fib => ./example/fib replace go.opentelemetry.io/otel/schema => ./schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ./exporters/otlp/internal/retry diff --git a/go.sum b/go.sum index f212493d5..eebdae0c2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/internal/global/internal_logging.go b/internal/global/internal_logging.go new file mode 100644 index 000000000..0a378476b --- /dev/null +++ b/internal/global/internal_logging.go @@ -0,0 +1,63 @@ +// 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 global // import "go.opentelemetry.io/otel/internal/global" + +import ( + "log" + "os" + "sync" + + "github.com/go-logr/logr" + "github.com/go-logr/stdr" +) + +// globalLogger is the logging interface used within the otel api and sdk provide deatails of the internals. +// +// The default logger uses stdr which is backed by the standard `log.Logger` +// interface. This logger will only show messages at the Error Level. +var globalLogger logr.Logger = stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)) +var globalLoggerLock = &sync.RWMutex{} + +// SetLogger overrides the globalLogger with l. +// +// To see Info messages use a logger with `l.V(1).Enabled() == true` +// To see Debug messages use a logger with `l.V(5).Enabled() == true` +func SetLogger(l logr.Logger) { + globalLoggerLock.Lock() + defer globalLoggerLock.Unlock() + globalLogger = l +} + +// Info prints messages about the general state of the API or SDK. +// This should usually be less then 5 messages a minute +func Info(msg string, keysAndValues ...interface{}) { + globalLoggerLock.RLock() + defer globalLoggerLock.RUnlock() + globalLogger.V(1).Info(msg, keysAndValues...) +} + +// Error prints messages about exceptional states of the API or SDK. +func Error(err error, msg string, keysAndValues ...interface{}) { + globalLoggerLock.RLock() + defer globalLoggerLock.RUnlock() + globalLogger.Error(err, msg, keysAndValues...) +} + +// Debug prints messages about all internal changes in the API or SDK. +func Debug(msg string, keysAndValues ...interface{}) { + globalLoggerLock.RLock() + defer globalLoggerLock.RUnlock() + globalLogger.V(5).Info(msg, keysAndValues...) +} diff --git a/exporters/otlp/otlpmetric/internal/connection/alignment_test.go b/internal/global/internal_logging_test.go similarity index 61% rename from exporters/otlp/otlpmetric/internal/connection/alignment_test.go rename to internal/global/internal_logging_test.go index aad85902c..d116486a8 100644 --- a/exporters/otlp/otlpmetric/internal/connection/alignment_test.go +++ b/internal/global/internal_logging_test.go @@ -12,27 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package connection +package global import ( + "log" "os" "testing" - "unsafe" - ottest "go.opentelemetry.io/otel/internal/internaltest" + "github.com/go-logr/stdr" ) -// Ensure struct alignment prior to running tests. -func TestMain(m *testing.M) { - fields := []ottest.FieldOffset{ - { - Name: "Connection.lastConnectErrPtr", - Offset: unsafe.Offsetof(Connection{}.lastConnectErrPtr), - }, - } - if !ottest.Aligned8Byte(fields, os.Stderr) { - os.Exit(1) - } - - os.Exit(m.Run()) +func TestRace(t *testing.T) { + go SetLogger(stdr.New(log.New(os.Stderr, "", 0))) + go Info("") } diff --git a/internal/metric/go.mod b/internal/metric/go.mod index 388901346..18be98c68 100644 --- a/internal/metric/go.mod +++ b/internal/metric/go.mod @@ -71,3 +71,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/internal/metric/go.sum b/internal/metric/go.sum index c9c90f143..094bf423f 100644 --- a/internal/metric/go.sum +++ b/internal/metric/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/internal/tools/go.mod b/internal/tools/go.mod index d173e9cc8..f67b70401 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -6,13 +6,12 @@ require ( github.com/client9/misspell v0.3.4 github.com/gogo/protobuf v1.3.2 github.com/golangci/golangci-lint v1.43.0 - github.com/itchyny/gojq v0.12.5 + github.com/itchyny/gojq v0.12.6 github.com/jcchavezs/porto v0.4.0 github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad go.opentelemetry.io/build-tools/multimod v0.0.0-20210920164323-2ceabab23375 go.opentelemetry.io/build-tools/semconvgen v0.0.0-20210920164323-2ceabab23375 - golang.org/x/mod v0.5.1 // indirect - golang.org/x/tools v0.1.7 + golang.org/x/tools v0.1.8 ) replace go.opentelemetry.io/otel => ../.. @@ -78,3 +77,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/internal/tools/go.sum b/internal/tools/go.sum index d8246590b..6212d1d9b 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -434,9 +434,8 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA= -github.com/itchyny/gojq v0.12.5 h1:6SJ1BQ1VAwJAlIvLSIZmqHP/RUEq3qfVWvsRxrqhsD0= -github.com/itchyny/gojq v0.12.5/go.mod h1:3e1hZXv+Kwvdp6V9HXpVrvddiHVApi5EDZwS+zLFeiE= +github.com/itchyny/gojq v0.12.6 h1:VjaFn59Em2wTxDNGcrRkDK9ZHMNa8IksOgL13sLL4d0= +github.com/itchyny/gojq v0.12.6/go.mod h1:ZHrkfu7A+RbZLy5J1/JKpS4poEqrzItSTGDItqsfP0A= github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU= github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -535,7 +534,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -806,6 +804,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= @@ -946,8 +945,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1051,12 +1051,13 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1175,8 +1176,9 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal_logging.go b/internal_logging.go new file mode 100644 index 000000000..c4f8acd5d --- /dev/null +++ b/internal_logging.go @@ -0,0 +1,26 @@ +// 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 otel // import "go.opentelemetry.io/otel" + +import ( + "github.com/go-logr/logr" + + "go.opentelemetry.io/otel/internal/global" +) + +// SetLogger configures the logger used internally to opentelemetry. +func SetLogger(logger logr.Logger) { + global.SetLogger(logger) +} diff --git a/internal_logging_test.go b/internal_logging_test.go new file mode 100644 index 000000000..57006334a --- /dev/null +++ b/internal_logging_test.go @@ -0,0 +1,29 @@ +// 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 otel_test + +import ( + "log" + "os" + + "github.com/go-logr/stdr" + + "go.opentelemetry.io/otel" +) + +func ExampleSetLogger() { + logger := stdr.New(log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)) + otel.SetLogger(logger) +} diff --git a/metric/go.mod b/metric/go.mod index b67c511dd..0f0d9175a 100644 --- a/metric/go.mod +++ b/metric/go.mod @@ -72,3 +72,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../bridge/opencensus/ replace go.opentelemetry.io/otel/example/fib => ../example/fib replace go.opentelemetry.io/otel/schema => ../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../exporters/otlp/internal/retry diff --git a/metric/go.sum b/metric/go.sum index f212493d5..eebdae0c2 100644 --- a/metric/go.sum +++ b/metric/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/schema/go.mod b/schema/go.mod index 7e601a633..f98187eea 100644 --- a/schema/go.mod +++ b/schema/go.mod @@ -69,3 +69,5 @@ replace go.opentelemetry.io/otel/sdk/export/metric => ../sdk/export/metric replace go.opentelemetry.io/otel/sdk/metric => ../sdk/metric replace go.opentelemetry.io/otel/trace => ../trace + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../exporters/otlp/internal/retry diff --git a/sdk/export/metric/aggregation/aggregation.go b/sdk/export/metric/aggregation/aggregation.go index 6d0f45e87..800ec88fd 100644 --- a/sdk/export/metric/aggregation/aggregation.go +++ b/sdk/export/metric/aggregation/aggregation.go @@ -84,15 +84,6 @@ type ( Sum() (number.Number, error) Histogram() (Buckets, error) } - - // MinMaxSumCount supports the Min, Max, Sum, and Count interfaces. - MinMaxSumCount interface { - Aggregation - Min() (number.Number, error) - Max() (number.Number, error) - Sum() (number.Number, error) - Count() (uint64, error) - } ) type ( @@ -106,18 +97,16 @@ type ( // deciding how to expose metric data. This enables // user-supplied Aggregators to replace builtin Aggregators. // - // For example, test for a Distribution before testing for a - // MinMaxSumCount, test for a Histogram before testing for a + // For example, test for a Histogram before testing for a // Sum, and so on. Kind string ) // Kind description constants. const ( - SumKind Kind = "Sum" - MinMaxSumCountKind Kind = "MinMaxSumCount" - HistogramKind Kind = "Histogram" - LastValueKind Kind = "Lastvalue" + SumKind Kind = "Sum" + HistogramKind Kind = "Histogram" + LastValueKind Kind = "Lastvalue" ) // Sentinel errors for Aggregation interface. diff --git a/sdk/export/metric/go.mod b/sdk/export/metric/go.mod index 75aca8ee8..bce145bd7 100644 --- a/sdk/export/metric/go.mod +++ b/sdk/export/metric/go.mod @@ -72,3 +72,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../../bridge/openc replace go.opentelemetry.io/otel/example/fib => ../../../example/fib replace go.opentelemetry.io/otel/schema => ../../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../../exporters/otlp/internal/retry diff --git a/sdk/export/metric/go.sum b/sdk/export/metric/go.sum index 893275e19..7c328dde1 100644 --- a/sdk/export/metric/go.sum +++ b/sdk/export/metric/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/sdk/export/metric/metric.go b/sdk/export/metric/metric.go index 65f99911f..81e579b8b 100644 --- a/sdk/export/metric/metric.go +++ b/sdk/export/metric/metric.go @@ -139,8 +139,7 @@ type CheckpointerFactory interface { // // Note that any Aggregator may be attached to any instrument--this is // the result of the OpenTelemetry API/SDK separation. It is possible -// to attach a Sum aggregator to a Histogram instrument or a -// MinMaxSumCount aggregator to a Counter instrument. +// to attach a Sum aggregator to a Histogram instrument. type Aggregator interface { // Aggregation returns an Aggregation interface to access the // current state of this Aggregator. The caller is diff --git a/sdk/go.mod b/sdk/go.mod index 1bf14c826..e1c9f43f4 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -73,3 +73,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../bridge/opencensus/ replace go.opentelemetry.io/otel/example/fib => ../example/fib replace go.opentelemetry.io/otel/schema => ../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../exporters/otlp/internal/retry diff --git a/sdk/go.sum b/sdk/go.sum index 2ae2b35b2..ff5b32d74 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -1,5 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/sdk/metric/aggregator/minmaxsumcount/mmsc.go b/sdk/metric/aggregator/minmaxsumcount/mmsc.go deleted file mode 100644 index 663cb4e0d..000000000 --- a/sdk/metric/aggregator/minmaxsumcount/mmsc.go +++ /dev/null @@ -1,165 +0,0 @@ -// 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 minmaxsumcount // import "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" - -import ( - "context" - "sync" - - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" - export "go.opentelemetry.io/otel/sdk/export/metric" - "go.opentelemetry.io/otel/sdk/export/metric/aggregation" - "go.opentelemetry.io/otel/sdk/metric/aggregator" -) - -type ( - // Aggregator aggregates events that form a distribution, - // keeping only the min, max, sum, and count. - Aggregator struct { - lock sync.Mutex - kind number.Kind - state - } - - state struct { - sum number.Number - min number.Number - max number.Number - count uint64 - } -) - -var _ export.Aggregator = &Aggregator{} -var _ aggregation.MinMaxSumCount = &Aggregator{} - -// New returns a new aggregator for computing the min, max, sum, and -// count. -// -// This type uses a mutex for Update() and SynchronizedMove() concurrency. -func New(cnt int, desc *sdkapi.Descriptor) []Aggregator { - kind := desc.NumberKind() - aggs := make([]Aggregator, cnt) - for i := range aggs { - aggs[i] = Aggregator{ - kind: kind, - state: emptyState(kind), - } - } - return aggs -} - -// Aggregation returns an interface for reading the state of this aggregator. -func (c *Aggregator) Aggregation() aggregation.Aggregation { - return c -} - -// Kind returns aggregation.MinMaxSumCountKind. -func (c *Aggregator) Kind() aggregation.Kind { - return aggregation.MinMaxSumCountKind -} - -// Sum returns the sum of values in the checkpoint. -func (c *Aggregator) Sum() (number.Number, error) { - return c.sum, nil -} - -// Count returns the number of values in the checkpoint. -func (c *Aggregator) Count() (uint64, error) { - return c.count, nil -} - -// Min returns the minimum value in the checkpoint. -// The error value aggregation.ErrNoData will be returned -// if there were no measurements recorded during the checkpoint. -func (c *Aggregator) Min() (number.Number, error) { - if c.count == 0 { - return 0, aggregation.ErrNoData - } - return c.min, nil -} - -// Max returns the maximum value in the checkpoint. -// The error value aggregation.ErrNoData will be returned -// if there were no measurements recorded during the checkpoint. -func (c *Aggregator) Max() (number.Number, error) { - if c.count == 0 { - return 0, aggregation.ErrNoData - } - return c.max, nil -} - -// SynchronizedMove saves the current state into oa and resets the current state to -// the empty set. -func (c *Aggregator) SynchronizedMove(oa export.Aggregator, desc *sdkapi.Descriptor) error { - o, _ := oa.(*Aggregator) - - if oa != nil && o == nil { - return aggregator.NewInconsistentAggregatorError(c, oa) - } - c.lock.Lock() - if o != nil { - o.state = c.state - } - c.state = emptyState(c.kind) - c.lock.Unlock() - - return nil -} - -func emptyState(kind number.Kind) state { - return state{ - count: 0, - sum: 0, - min: kind.Maximum(), - max: kind.Minimum(), - } -} - -// Update adds the recorded measurement to the current data set. -func (c *Aggregator) Update(_ context.Context, number number.Number, desc *sdkapi.Descriptor) error { - kind := desc.NumberKind() - - c.lock.Lock() - defer c.lock.Unlock() - c.count++ - c.sum.AddNumber(kind, number) - if number.CompareNumber(kind, c.min) < 0 { - c.min = number - } - if number.CompareNumber(kind, c.max) > 0 { - c.max = number - } - return nil -} - -// Merge combines two data sets into one. -func (c *Aggregator) Merge(oa export.Aggregator, desc *sdkapi.Descriptor) error { - o, _ := oa.(*Aggregator) - if o == nil { - return aggregator.NewInconsistentAggregatorError(c, oa) - } - - c.count += o.count - c.sum.AddNumber(desc.NumberKind(), o.sum) - - if c.min.CompareNumber(desc.NumberKind(), o.min) > 0 { - c.min.SetNumber(o.min) - } - if c.max.CompareNumber(desc.NumberKind(), o.max) < 0 { - c.max.SetNumber(o.max) - } - return nil -} diff --git a/sdk/metric/aggregator/minmaxsumcount/mmsc_test.go b/sdk/metric/aggregator/minmaxsumcount/mmsc_test.go deleted file mode 100644 index b0dc7f8fb..000000000 --- a/sdk/metric/aggregator/minmaxsumcount/mmsc_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// 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 minmaxsumcount - -import ( - "errors" - "math" - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" - export "go.opentelemetry.io/otel/sdk/export/metric" - "go.opentelemetry.io/otel/sdk/export/metric/aggregation" - "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" -) - -const count = 100 - -type policy struct { - name string - absolute bool - sign func() int -} - -var ( - positiveOnly = policy{ - name: "absolute", - absolute: true, - sign: func() int { return +1 }, - } - negativeOnly = policy{ - name: "negative", - absolute: false, - sign: func() int { return -1 }, - } - positiveAndNegative = policy{ - name: "positiveAndNegative", - absolute: false, - sign: func() int { - if rand.Uint32() > math.MaxUint32/2 { - return -1 - } - return 1 - }, - } -) - -func TestMinMaxSumCountAbsolute(t *testing.T) { - aggregatortest.RunProfiles(t, func(t *testing.T, profile aggregatortest.Profile) { - minMaxSumCount(t, profile, positiveOnly) - }) -} - -func TestMinMaxSumCountNegativeOnly(t *testing.T) { - aggregatortest.RunProfiles(t, func(t *testing.T, profile aggregatortest.Profile) { - minMaxSumCount(t, profile, negativeOnly) - }) -} - -func TestMinMaxSumCountPositiveAndNegative(t *testing.T) { - aggregatortest.RunProfiles(t, func(t *testing.T, profile aggregatortest.Profile) { - minMaxSumCount(t, profile, positiveAndNegative) - }) -} - -func new2(desc *sdkapi.Descriptor) (_, _ *Aggregator) { - alloc := New(2, desc) - return &alloc[0], &alloc[1] -} - -func new4(desc *sdkapi.Descriptor) (_, _, _, _ *Aggregator) { - alloc := New(4, desc) - return &alloc[0], &alloc[1], &alloc[2], &alloc[3] -} - -func checkZero(t *testing.T, agg *Aggregator, desc *sdkapi.Descriptor) { - kind := desc.NumberKind() - - sum, err := agg.Sum() - require.NoError(t, err) - require.Equal(t, kind.Zero(), sum) - - count, err := agg.Count() - require.NoError(t, err) - require.Equal(t, uint64(0), count) - - max, err := agg.Max() - require.True(t, errors.Is(err, aggregation.ErrNoData)) - require.Equal(t, kind.Zero(), max) - - min, err := agg.Min() - require.True(t, errors.Is(err, aggregation.ErrNoData)) - require.Equal(t, kind.Zero(), min) -} - -// Validates min, max, sum and count for a given profile and policy -func minMaxSumCount(t *testing.T, profile aggregatortest.Profile, policy policy) { - descriptor := aggregatortest.NewAggregatorTest(sdkapi.HistogramInstrumentKind, profile.NumberKind) - - agg, ckpt := new2(descriptor) - - all := aggregatortest.NewNumbers(profile.NumberKind) - - for i := 0; i < count; i++ { - x := profile.Random(policy.sign()) - all.Append(x) - aggregatortest.CheckedUpdate(t, agg, x, descriptor) - } - - require.NoError(t, agg.SynchronizedMove(ckpt, descriptor)) - - checkZero(t, agg, descriptor) - - all.Sort() - - aggSum, err := ckpt.Sum() - require.Nil(t, err) - allSum := all.Sum() - require.InEpsilon(t, - (&allSum).CoerceToFloat64(profile.NumberKind), - aggSum.CoerceToFloat64(profile.NumberKind), - 0.000000001, - "Same sum - "+policy.name) - - count, err := ckpt.Count() - require.Equal(t, all.Count(), count, "Same count -"+policy.name) - require.Nil(t, err) - - min, err := ckpt.Min() - require.Nil(t, err) - require.Equal(t, - all.Min(), - min, - "Same min -"+policy.name) - - max, err := ckpt.Max() - require.Nil(t, err) - require.Equal(t, - all.Max(), - max, - "Same max -"+policy.name) -} - -func TestMinMaxSumCountMerge(t *testing.T) { - aggregatortest.RunProfiles(t, func(t *testing.T, profile aggregatortest.Profile) { - descriptor := aggregatortest.NewAggregatorTest(sdkapi.HistogramInstrumentKind, profile.NumberKind) - - agg1, agg2, ckpt1, ckpt2 := new4(descriptor) - - all := aggregatortest.NewNumbers(profile.NumberKind) - - for i := 0; i < count; i++ { - x := profile.Random(+1) - all.Append(x) - aggregatortest.CheckedUpdate(t, agg1, x, descriptor) - } - for i := 0; i < count; i++ { - x := profile.Random(+1) - all.Append(x) - aggregatortest.CheckedUpdate(t, agg2, x, descriptor) - } - - require.NoError(t, agg1.SynchronizedMove(ckpt1, descriptor)) - require.NoError(t, agg2.SynchronizedMove(ckpt2, descriptor)) - - checkZero(t, agg1, descriptor) - checkZero(t, agg2, descriptor) - - aggregatortest.CheckedMerge(t, ckpt1, ckpt2, descriptor) - - all.Sort() - - aggSum, err := ckpt1.Sum() - require.Nil(t, err) - allSum := all.Sum() - require.InEpsilon(t, - (&allSum).CoerceToFloat64(profile.NumberKind), - aggSum.CoerceToFloat64(profile.NumberKind), - 0.000000001, - "Same sum - absolute") - - count, err := ckpt1.Count() - require.Equal(t, all.Count(), count, "Same count - absolute") - require.Nil(t, err) - - min, err := ckpt1.Min() - require.Nil(t, err) - require.Equal(t, - all.Min(), - min, - "Same min - absolute") - - max, err := ckpt1.Max() - require.Nil(t, err) - require.Equal(t, - all.Max(), - max, - "Same max - absolute") - }) -} - -func TestMaxSumCountNotSet(t *testing.T) { - aggregatortest.RunProfiles(t, func(t *testing.T, profile aggregatortest.Profile) { - descriptor := aggregatortest.NewAggregatorTest(sdkapi.HistogramInstrumentKind, profile.NumberKind) - - alloc := New(2, descriptor) - agg, ckpt := &alloc[0], &alloc[1] - - require.NoError(t, agg.SynchronizedMove(ckpt, descriptor)) - - asum, err := ckpt.Sum() - require.Equal(t, number.Number(0), asum, "Empty checkpoint sum = 0") - require.Nil(t, err) - - count, err := ckpt.Count() - require.Equal(t, uint64(0), count, "Empty checkpoint count = 0") - require.Nil(t, err) - - max, err := ckpt.Max() - require.Equal(t, aggregation.ErrNoData, err) - require.Equal(t, number.Number(0), max) - }) -} - -func TestSynchronizedMoveReset(t *testing.T) { - aggregatortest.SynchronizedMoveResetTest( - t, - sdkapi.HistogramInstrumentKind, - func(desc *sdkapi.Descriptor) export.Aggregator { - return &New(1, desc)[0] - }, - ) -} diff --git a/sdk/metric/benchmark_test.go b/sdk/metric/benchmark_test.go index 62ff0ac99..585bf4360 100644 --- a/sdk/metric/benchmark_test.go +++ b/sdk/metric/benchmark_test.go @@ -303,16 +303,6 @@ func BenchmarkGaugeObserverObservationFloat64(b *testing.B) { fix.accumulator.Collect(ctx) } -// MaxSumCount - -func BenchmarkInt64MaxSumCountAdd(b *testing.B) { - benchmarkInt64HistogramAdd(b, "int64.minmaxsumcount") -} - -func BenchmarkFloat64MaxSumCountAdd(b *testing.B) { - benchmarkFloat64HistogramAdd(b, "float64.minmaxsumcount") -} - // Exact func BenchmarkInt64ExactAdd(b *testing.B) { diff --git a/sdk/metric/go.mod b/sdk/metric/go.mod index 6c3fc885a..27f1e53c6 100644 --- a/sdk/metric/go.mod +++ b/sdk/metric/go.mod @@ -75,3 +75,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../../bridge/opencens replace go.opentelemetry.io/otel/example/fib => ../../example/fib replace go.opentelemetry.io/otel/schema => ../../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../exporters/otlp/internal/retry diff --git a/sdk/metric/go.sum b/sdk/metric/go.sum index e9663039d..a4acc8a9b 100644 --- a/sdk/metric/go.sum +++ b/sdk/metric/go.sum @@ -2,6 +2,11 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/sdk/metric/minmaxsumcount_stress_test.go b/sdk/metric/minmaxsumcount_stress_test.go deleted file mode 100644 index 32a8652ed..000000000 --- a/sdk/metric/minmaxsumcount_stress_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// 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 metric_test - -import ( - "context" - "math/rand" - "testing" - "time" - - "go.opentelemetry.io/otel/metric/metrictest" - "go.opentelemetry.io/otel/metric/number" - "go.opentelemetry.io/otel/metric/sdkapi" - "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" -) - -func TestStressInt64MinMaxSumCount(t *testing.T) { - desc := metrictest.NewDescriptor("some_metric", sdkapi.HistogramInstrumentKind, number.Int64Kind) - alloc := minmaxsumcount.New(2, &desc) - mmsc, ckpt := &alloc[0], &alloc[1] - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go func() { - rnd := rand.New(rand.NewSource(time.Now().Unix())) - v := rnd.Int63() % 103 - for { - select { - case <-ctx.Done(): - return - default: - _ = mmsc.Update(ctx, number.NewInt64Number(v), &desc) - } - v++ - } - }() - - startTime := time.Now() - for time.Since(startTime) < time.Second { - _ = mmsc.SynchronizedMove(ckpt, &desc) - - s, _ := ckpt.Sum() - c, _ := ckpt.Count() - min, e1 := ckpt.Min() - max, e2 := ckpt.Max() - if c == 0 && (e1 == nil || e2 == nil || s.AsInt64() != 0) { - t.Fail() - } - if c != 0 { - if e1 != nil || e2 != nil { - t.Fail() - } - lo, hi, sum := min.AsInt64(), max.AsInt64(), s.AsInt64() - - if uint64(hi-lo)+1 != c { - t.Fail() - } - if c == 1 { - if lo != hi || lo != sum { - t.Fail() - } - } else { - if hi*(hi+1)/2-(lo-1)*lo/2 != sum { - t.Fail() - } - } - } - } -} diff --git a/sdk/metric/processor/basic/basic_test.go b/sdk/metric/processor/basic/basic_test.go index ced1b3217..a465db2c1 100644 --- a/sdk/metric/processor/basic/basic_test.go +++ b/sdk/metric/processor/basic/basic_test.go @@ -80,7 +80,6 @@ func TestProcessor(t *testing.T) { t.Run(nc.kind.String(), func(t *testing.T) { for _, ac := range []aggregatorCase{ {kind: aggregation.SumKind}, - {kind: aggregation.MinMaxSumCountKind}, {kind: aggregation.HistogramKind}, {kind: aggregation.LastValueKind}, } { diff --git a/sdk/metric/processor/processortest/test.go b/sdk/metric/processor/processortest/test.go index 6f7e56ef8..49e9b4566 100644 --- a/sdk/metric/processor/processortest/test.go +++ b/sdk/metric/processor/processortest/test.go @@ -28,7 +28,6 @@ import ( "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" - "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/resource" ) @@ -191,11 +190,6 @@ func (testAggregatorSelector) AggregatorFor(desc *sdkapi.Descriptor, aggPtrs ... for i := range aggPtrs { *aggPtrs[i] = &aggs[i] } - case strings.HasSuffix(desc.Name(), ".minmaxsumcount"): - aggs := minmaxsumcount.New(len(aggPtrs), desc) - for i := range aggPtrs { - *aggPtrs[i] = &aggs[i] - } case strings.HasSuffix(desc.Name(), ".lastvalue"): aggs := lastvalue.New(len(aggPtrs)) for i := range aggPtrs { diff --git a/sdk/metric/selector/simple/simple.go b/sdk/metric/selector/simple/simple.go index 03cd9d731..3a23be1d7 100644 --- a/sdk/metric/selector/simple/simple.go +++ b/sdk/metric/selector/simple/simple.go @@ -19,7 +19,6 @@ import ( export "go.opentelemetry.io/otel/sdk/export/metric" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" - "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" ) @@ -70,7 +69,7 @@ func (selectorInexpensive) AggregatorFor(descriptor *sdkapi.Descriptor, aggPtrs case sdkapi.GaugeObserverInstrumentKind: lastValueAggs(aggPtrs) case sdkapi.HistogramInstrumentKind: - aggs := minmaxsumcount.New(len(aggPtrs), descriptor) + aggs := sum.New(len(aggPtrs)) for i := range aggPtrs { *aggPtrs[i] = &aggs[i] } diff --git a/sdk/metric/selector/simple/simple_test.go b/sdk/metric/selector/simple/simple_test.go index 4f3746bb0..bd7325518 100644 --- a/sdk/metric/selector/simple/simple_test.go +++ b/sdk/metric/selector/simple/simple_test.go @@ -25,7 +25,6 @@ import ( export "go.opentelemetry.io/otel/sdk/export/metric" "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" - "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/metric/selector/simple" ) @@ -55,7 +54,7 @@ func testFixedSelectors(t *testing.T, sel export.AggregatorSelector) { func TestInexpensiveDistribution(t *testing.T) { inex := simple.NewWithInexpensiveDistribution() - require.IsType(t, (*minmaxsumcount.Aggregator)(nil), oneAgg(inex, &testHistogramDesc)) + require.IsType(t, (*sum.Aggregator)(nil), oneAgg(inex, &testHistogramDesc)) testFixedSelectors(t, inex) } diff --git a/sdk/trace/batch_span_processor.go b/sdk/trace/batch_span_processor.go index 46f66049e..75f519a67 100644 --- a/sdk/trace/batch_span_processor.go +++ b/sdk/trace/batch_span_processor.go @@ -22,6 +22,7 @@ import ( "time" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/trace" ) @@ -224,6 +225,7 @@ func WithBlocking() BatchSpanProcessorOption { // exportSpans is a subroutine of processing and draining the queue. func (bsp *batchSpanProcessor) exportSpans(ctx context.Context) error { + bsp.timer.Reset(bsp.o.BatchTimeout) bsp.batchMutex.Lock() @@ -236,6 +238,7 @@ func (bsp *batchSpanProcessor) exportSpans(ctx context.Context) error { } if l := len(bsp.batch); l > 0 { + global.Debug("exporting spans", "count", len(bsp.batch)) err := bsp.e.ExportSpans(ctx, bsp.batch) // A new batch is always created after exporting, even if the batch failed to be exported. diff --git a/trace/go.mod b/trace/go.mod index 38d8478e0..0f42605c0 100644 --- a/trace/go.mod +++ b/trace/go.mod @@ -71,3 +71,5 @@ replace go.opentelemetry.io/otel/bridge/opencensus/test => ../bridge/opencensus/ replace go.opentelemetry.io/otel/example/fib => ../example/fib replace go.opentelemetry.io/otel/schema => ../schema + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../exporters/otlp/internal/retry diff --git a/trace/go.sum b/trace/go.sum index f212493d5..5615ed1b5 100644 --- a/trace/go.sum +++ b/trace/go.sum @@ -1,5 +1,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=