mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-16 02:47:20 +02:00
Revert the usage of go.opentelemetry.io/proto/slim
(#5253)
* Revert "otlpmetrichttp: Use go.opentelemetry.io/proto/slim/otlp (#5222)" This reverts commit6e92163d6a
. * Revert "otlploghttp: Use go.opentelemetry.io/proto/slim/otlp (#5216)" This reverts commitfe3de7059e
. * Remove slim dep * Fix CI
This commit is contained in:
parent
b34cfc47c4
commit
bf37c5a3a4
@ -29,7 +29,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
|
||||
- Update `go.opentelemetry.io/proto/otlp` from v1.1.0 to v1.2.0. (#5177)
|
||||
- Improve performance of baggage member character validation in `go.opentelemetry.io/otel/baggage`. (#5214)
|
||||
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` no longer depends on `google.golang.org/grpc`. (#5222)
|
||||
|
||||
## [1.25.0/0.47.0/0.0.8/0.1.0-alpha] 2024-04-05
|
||||
|
||||
|
@ -20,8 +20,8 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
collogpb "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"
|
||||
logpb "go.opentelemetry.io/proto/slim/otlp/logs/v1"
|
||||
collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||
logpb "go.opentelemetry.io/proto/otlp/logs/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry"
|
||||
)
|
||||
|
@ -32,10 +32,10 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
collogpb "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
lpb "go.opentelemetry.io/proto/slim/otlp/logs/v1"
|
||||
rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1"
|
||||
collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/log"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/log"
|
||||
logpb "go.opentelemetry.io/proto/slim/otlp/logs/v1"
|
||||
logpb "go.opentelemetry.io/proto/otlp/logs/v1"
|
||||
)
|
||||
|
||||
func TestExporterExportErrors(t *testing.T) {
|
||||
|
@ -11,7 +11,7 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.24.0
|
||||
go.opentelemetry.io/otel/sdk/log v0.0.0-20240403115316-6c6e1e7416e9
|
||||
go.opentelemetry.io/otel/trace v1.25.0
|
||||
go.opentelemetry.io/proto/slim/otlp v1.2.0
|
||||
go.opentelemetry.io/proto/otlp v1.2.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
)
|
||||
|
||||
@ -19,9 +19,17 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.25.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/grpc v1.63.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -9,17 +10,38 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.opentelemetry.io/proto/slim/otlp v1.2.0 h1:90eMxPHyObsdi/dB1ZP8FP3s3txzxVXjArYqLxPuLZg=
|
||||
go.opentelemetry.io/proto/slim/otlp v1.2.0/go.mod h1:DeSHUkdUaCemrUs/Nmnsdo8BtM+XmdTEVjYWYFiLQhU=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
|
||||
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -8,9 +8,9 @@ package transform // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otl
|
||||
import (
|
||||
"time"
|
||||
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
lpb "go.opentelemetry.io/proto/slim/otlp/logs/v1"
|
||||
rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
api "go.opentelemetry.io/otel/log"
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
lpb "go.opentelemetry.io/proto/slim/otlp/logs/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
|
||||
|
||||
api "go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/sdk/log"
|
||||
|
@ -12,15 +12,20 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig.go.tmpl "--data={}" --out=envconfig/envconfig.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig_test.go.tmpl "--data={}" --out=envconfig/envconfig_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig\"}" --out=oconf/envconfig.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl "--data={}" --out=oconf/envconfig_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options.go.tmpl "--data={\"retryImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/retry\"}" --out=oconf/options.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig\"}" --out=oconf/options_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/optiontypes.go.tmpl "--data={}" --out=oconf/optiontypes.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/tls.go.tmpl "--data={}" --out=oconf/tls.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\", \"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal\"}" --out=otest/client_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal\"}" --out=otest/client_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/collector.go.tmpl "--data={\"oconfImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf\"}" --out=otest/collector.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/attribute.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/attribute_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/error.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={}" --out=transform/attribute.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={}" --out=transform/attribute_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={}" --out=transform/error.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error_test.go.tmpl "--data={}" --out=transform/error_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/metricdata.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/metricdata_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={}" --out=transform/metricdata.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={}" --out=transform/metricdata_test.go
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -34,18 +37,46 @@ func ApplyGRPCEnvConfigs(cfg Config) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getOptionsFromEnv() []GRPCOption {
|
||||
opts := []GRPCOption{}
|
||||
// ApplyHTTPEnvConfigs applies the env configurations for HTTP.
|
||||
func ApplyHTTPEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getOptionsFromEnv() []GenericOption {
|
||||
opts := []GenericOption{}
|
||||
|
||||
tlsConf := &tls.Config{}
|
||||
DefaultEnvOptionsReader.Apply(
|
||||
envconfig.WithURL("ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, NewGRPCOption(withEndpointForGRPC(u)))
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
// For OTLP/HTTP endpoint URLs without a per-signal
|
||||
// configuration, the passed endpoint is used as a base URL
|
||||
// and the signals are sent to these paths relative to that.
|
||||
cfg.Metrics.URLPath = path.Join(u.Path, DefaultMetricsPath)
|
||||
return cfg
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithURL("METRICS_ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, NewGRPCOption(withEndpointForGRPC(u)))
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
// For endpoint URLs for OTLP/HTTP per-signal variables, the
|
||||
// URL MUST be used as-is without any modification. The only
|
||||
// exception is that if an URL contains no path part, the root
|
||||
// path / MUST be used.
|
||||
path := u.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
cfg.Metrics.URLPath = path
|
||||
return cfg
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
@ -90,7 +121,7 @@ func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOpt
|
||||
}
|
||||
}
|
||||
|
||||
func withEndpointScheme(u *url.URL) GRPCOption {
|
||||
func withEndpointScheme(u *url.URL) GenericOption {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http", "unix":
|
||||
return WithInsecure()
|
||||
@ -100,7 +131,7 @@ func withEndpointScheme(u *url.URL) GRPCOption {
|
||||
}
|
||||
|
||||
// revive:disable-next-line:flag-parameter
|
||||
func withInsecure(b bool) GRPCOption {
|
||||
func withInsecure(b bool) GenericOption {
|
||||
if b {
|
||||
return WithInsecure()
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/options.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -44,7 +47,6 @@ type (
|
||||
// This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client.
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
// SignalConfig represents signal specific configuration.
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
@ -63,8 +65,8 @@ type (
|
||||
Proxy HTTPTransportProxyFunc
|
||||
}
|
||||
|
||||
// Config represents exporter configuration.
|
||||
Config struct {
|
||||
// Signal specific configurations
|
||||
Metrics SignalConfig
|
||||
|
||||
RetryConfig retry.Config
|
||||
@ -77,6 +79,29 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// NewHTTPConfig returns a new Config with all settings applied from opts and
|
||||
// any unset setting using the default HTTP config values.
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
cfg = ApplyHTTPEnvConfigs(cfg)
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
cfg.Metrics.URLPath = cleanPath(cfg.Metrics.URLPath, DefaultMetricsPath)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// cleanPath returns a path with all spaces trimmed and all redundancies
|
||||
// removed. If urlPath is empty or cleaning it results in an empty string,
|
||||
// defaultPath is returned instead.
|
||||
@ -139,8 +164,10 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// GRPCOption applies an option to the gRPC driver.
|
||||
type GRPCOption interface {
|
||||
type (
|
||||
// GenericOption applies an option to the HTTP or gRPC driver.
|
||||
GenericOption interface {
|
||||
ApplyHTTPOption(Config) Config
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
@ -149,6 +176,83 @@ type GRPCOption interface {
|
||||
private()
|
||||
}
|
||||
|
||||
// HTTPOption applies an option to the HTTP driver.
|
||||
HTTPOption interface {
|
||||
ApplyHTTPOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// GRPCOption applies an option to the gRPC driver.
|
||||
GRPCOption interface {
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
)
|
||||
|
||||
// genericOption is an option that applies the same logic
|
||||
// for both gRPC and HTTP.
|
||||
type genericOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (genericOption) private() {}
|
||||
|
||||
func newGenericOption(fn func(cfg Config) Config) GenericOption {
|
||||
return &genericOption{fn: fn}
|
||||
}
|
||||
|
||||
// splitOption is an option that applies different logics
|
||||
// for gRPC and HTTP.
|
||||
type splitOption struct {
|
||||
httpFn func(Config) Config
|
||||
grpcFn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.grpcFn(cfg)
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.httpFn(cfg)
|
||||
}
|
||||
|
||||
func (splitOption) private() {}
|
||||
|
||||
func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption {
|
||||
return &splitOption{httpFn: httpFn, grpcFn: grpcFn}
|
||||
}
|
||||
|
||||
// httpOption is an option that is only applied to the HTTP driver.
|
||||
type httpOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (h *httpOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return h.fn(cfg)
|
||||
}
|
||||
|
||||
func (httpOption) private() {}
|
||||
|
||||
func NewHTTPOption(fn func(cfg Config) Config) HTTPOption {
|
||||
return &httpOption{fn: fn}
|
||||
}
|
||||
|
||||
// grpcOption is an option that is only applied to the gRPC driver.
|
||||
type grpcOption struct {
|
||||
fn func(Config) Config
|
||||
@ -166,15 +270,15 @@ func NewGRPCOption(fn func(cfg Config) Config) GRPCOption {
|
||||
|
||||
// Generic Options
|
||||
|
||||
func WithEndpoint(endpoint string) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithEndpoint(endpoint string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = endpoint
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithEndpointURL(v string) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithEndpointURL(v string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
global.Error(err, "otlpmetric: parse endpoint url", "url", v)
|
||||
@ -191,78 +295,81 @@ func WithEndpointURL(v string) GRPCOption {
|
||||
})
|
||||
}
|
||||
|
||||
func WithCompression(compression Compression) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithCompression(compression Compression) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Compression = compression
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithURLPath(urlPath string) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithURLPath(urlPath string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.URLPath = urlPath
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithRetry(rc retry.Config) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithRetry(rc retry.Config) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.RetryConfig = rc
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption {
|
||||
return newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TLSCfg = tlsCfg.Clone()
|
||||
return cfg
|
||||
}, func(cfg Config) Config {
|
||||
cfg.Metrics.GRPCCredentials = credentials.NewTLS(tlsCfg)
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithInsecure() GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithInsecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Insecure = true
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithSecure() GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithSecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Insecure = false
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithHeaders(headers map[string]string) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithHeaders(headers map[string]string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Headers = headers
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTimeout(duration time.Duration) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithTimeout(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Timeout = duration
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TemporalitySelector = selector
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithAggregationSelector(selector metric.AggregationSelector) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithAggregationSelector(selector metric.AggregationSelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.AggregationSelector = selector
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GRPCOption {
|
||||
return NewGRPCOption(func(cfg Config) Config {
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Proxy = pf
|
||||
return cfg
|
||||
})
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -61,15 +64,19 @@ func TestConfigs(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []GRPCOption
|
||||
opts []GenericOption
|
||||
env env
|
||||
fileReader fileReader
|
||||
asserts func(t *testing.T, c *Config)
|
||||
asserts func(t *testing.T, c *Config, grpcOption bool)
|
||||
}{
|
||||
{
|
||||
name: "Test default configs",
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.Equal(t, "localhost:4317", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "localhost:4318", c.Metrics.Endpoint)
|
||||
}
|
||||
assert.Equal(t, NoCompression, c.Metrics.Compression)
|
||||
assert.Equal(t, map[string]string(nil), c.Metrics.Headers)
|
||||
assert.Equal(t, 10*time.Second, c.Metrics.Timeout)
|
||||
@ -79,19 +86,19 @@ func TestConfigs(t *testing.T) {
|
||||
// Endpoint Tests
|
||||
{
|
||||
name: "Test With Endpoint",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpoint("someendpoint"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Endpoint URL",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("http://someendpoint/somepath"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/somepath", c.Metrics.URLPath)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
@ -99,10 +106,10 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Test With Secure Endpoint URL",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("https://someendpoint/somepath"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/somepath", c.Metrics.URLPath)
|
||||
assert.Equal(t, false, c.Metrics.Insecure)
|
||||
@ -110,11 +117,15 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Test With Invalid Endpoint URL",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("%invalid"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.Equal(t, "localhost:4317", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "localhost:4318", c.Metrics.Endpoint)
|
||||
}
|
||||
assert.Equal(t, "/v1/metrics", c.Metrics.URLPath)
|
||||
},
|
||||
},
|
||||
@ -123,9 +134,14 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env.endpoint/prefix",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.False(t, c.Metrics.Insecure)
|
||||
if grpcOption {
|
||||
assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "env.endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -134,20 +150,23 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://overrode.by.signal.specific/env/var",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://env.metrics.endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.True(t, c.Metrics.Insecure)
|
||||
assert.Equal(t, "env.metrics.endpoint", c.Metrics.Endpoint)
|
||||
if !grpcOption {
|
||||
assert.Equal(t, "/", c.Metrics.URLPath)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Endpoint",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpoint("metrics_endpoint"),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "metrics_endpoint", c.Metrics.Endpoint)
|
||||
},
|
||||
},
|
||||
@ -156,7 +175,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
@ -166,7 +185,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
@ -176,7 +195,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, false, c.Metrics.Insecure)
|
||||
},
|
||||
@ -187,7 +206,7 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "HTTPS://overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "HtTp://env_metrics_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_metrics_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
@ -196,18 +215,27 @@ func TestConfigs(t *testing.T) {
|
||||
// Certificate tests
|
||||
{
|
||||
name: "Test Default Certificate",
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
assert.Nil(t, c.Metrics.TLSCfg)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Certificate",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithTLSClientConfig(tlsCert),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
// TODO: make sure gRPC's credentials actually works
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -218,8 +246,13 @@ func TestConfigs(t *testing.T) {
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -232,38 +265,48 @@ func TestConfigs(t *testing.T) {
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
"invalid_cert": []byte("invalid certificate file."),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Certificate",
|
||||
opts: []GRPCOption{},
|
||||
opts: []GenericOption{},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects()))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Headers tests
|
||||
{
|
||||
name: "Test With Headers",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithHeaders(map[string]string{"h1": "v1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
@ -273,17 +316,17 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_HEADERS": "h1=v1,h2=v2",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithHeaders(map[string]string{"m1": "mv1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"m1": "mv1"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
@ -291,10 +334,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Compression Tests
|
||||
{
|
||||
name: "Test With Compression",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithCompression(GzipCompression),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
@ -303,7 +346,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
@ -312,19 +355,19 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Compression",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithCompression(NoCompression),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, NoCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
@ -332,10 +375,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Timeout Tests
|
||||
{
|
||||
name: "Test With Timeout",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithTimeout(time.Duration(5 * time.Second)),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, 5*time.Second, c.Metrics.Timeout)
|
||||
},
|
||||
},
|
||||
@ -344,7 +387,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 15*time.Second)
|
||||
},
|
||||
},
|
||||
@ -354,7 +397,7 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 28*time.Second)
|
||||
},
|
||||
},
|
||||
@ -364,10 +407,10 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000",
|
||||
},
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithTimeout(5 * time.Second),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 5*time.Second)
|
||||
},
|
||||
},
|
||||
@ -375,10 +418,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Temporality Selector Tests
|
||||
{
|
||||
name: "WithTemporalitySelector",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithTemporalitySelector(deltaSelector),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
// Function value comparisons are disallowed, test non-default
|
||||
// behavior of a TemporalitySelector here to ensure our "catch
|
||||
// all" was set.
|
||||
@ -391,10 +434,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Aggregation Selector Tests
|
||||
{
|
||||
name: "WithAggregationSelector",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithAggregationSelector(dropSelector),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
// Function value comparisons are disallowed, test non-default
|
||||
// behavior of a AggregationSelector here to ensure our "catch
|
||||
// all" was set.
|
||||
@ -407,12 +450,12 @@ func TestConfigs(t *testing.T) {
|
||||
// Proxy Tests
|
||||
{
|
||||
name: "Test With Proxy",
|
||||
opts: []GRPCOption{
|
||||
opts: []GenericOption{
|
||||
WithProxy(func(r *http.Request) (*url.URL, error) {
|
||||
return url.Parse("http://proxy.com")
|
||||
}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.NotNil(t, c.Metrics.Proxy)
|
||||
proxyURL, err := c.Metrics.Proxy(&http.Request{})
|
||||
assert.NoError(t, err)
|
||||
@ -421,8 +464,8 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Test Without Proxy",
|
||||
opts: []GRPCOption{},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
opts: []GenericOption{},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Nil(t, c.Metrics.Proxy)
|
||||
},
|
||||
},
|
||||
@ -438,9 +481,13 @@ func TestConfigs(t *testing.T) {
|
||||
}
|
||||
t.Cleanup(func() { DefaultEnvOptionsReader = origEOR })
|
||||
|
||||
// Tests Generic options as HTTP Options
|
||||
cfg := NewHTTPConfig(asHTTPOptions(tt.opts)...)
|
||||
tt.asserts(t, &cfg, false)
|
||||
|
||||
// Tests Generic options as gRPC Options
|
||||
cfg := NewGRPCConfig(tt.opts...)
|
||||
tt.asserts(t, &cfg)
|
||||
cfg = NewGRPCConfig(asGRPCOptions(tt.opts)...)
|
||||
tt.asserts(t, &cfg, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -453,6 +500,22 @@ func deltaSelector(metric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.DeltaTemporality
|
||||
}
|
||||
|
||||
func asHTTPOptions(opts []GenericOption) []HTTPOption {
|
||||
converted := make([]HTTPOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = NewHTTPOption(o.ApplyHTTPOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func asGRPCOptions(opts []GenericOption) []GRPCOption {
|
||||
converted := make([]GRPCOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = NewGRPCOption(o.ApplyGRPCOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
type args struct {
|
||||
urlPath string
|
||||
|
@ -1,16 +1,37 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/otest/collector.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otest // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest"
|
||||
|
||||
import (
|
||||
"context" // nolint:depguard // This is for testing.
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix" // nolint:depguard // This is for testing.
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf"
|
||||
collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
@ -20,7 +41,6 @@ type Collector interface {
|
||||
Collect() *Storage
|
||||
}
|
||||
|
||||
// ExportResult represents an export response.
|
||||
type ExportResult struct {
|
||||
Response *collpb.ExportMetricsServiceResponse
|
||||
Err error
|
||||
@ -140,3 +160,292 @@ func (c *GRPCCollector) Export(ctx context.Context, req *collpb.ExportMetricsSer
|
||||
}
|
||||
return &collpb.ExportMetricsServiceResponse{}, nil
|
||||
}
|
||||
|
||||
var emptyExportMetricsServiceResponse = func() []byte {
|
||||
body := collpb.ExportMetricsServiceResponse{}
|
||||
r, err := proto.Marshal(&body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}()
|
||||
|
||||
type HTTPResponseError struct {
|
||||
Err error
|
||||
Status int
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (e *HTTPResponseError) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.Status, e.Err)
|
||||
}
|
||||
|
||||
func (e *HTTPResponseError) Unwrap() error { return e.Err }
|
||||
|
||||
// HTTPCollector is an OTLP HTTP server that collects all requests it receives.
|
||||
type HTTPCollector struct {
|
||||
plainTextResponse bool
|
||||
|
||||
headersMu sync.Mutex
|
||||
headers http.Header
|
||||
storage *Storage
|
||||
|
||||
resultCh <-chan ExportResult
|
||||
listener net.Listener
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
// NewHTTPCollector returns a *HTTPCollector that is listening at the provided
|
||||
// endpoint.
|
||||
//
|
||||
// If endpoint is an empty string, the returned collector will be listening on
|
||||
// the localhost interface at an OS chosen port, not use TLS, and listen at the
|
||||
// default OTLP metric endpoint path ("/v1/metrics"). If the endpoint contains
|
||||
// a prefix of "https" the server will generate weak self-signed TLS
|
||||
// certificates and use them to server data. If the endpoint contains a path,
|
||||
// that path will be used instead of the default OTLP metric endpoint path.
|
||||
//
|
||||
// If errCh is not nil, the collector will respond to HTTP requests with errors
|
||||
// sent on that channel. This means that if errCh is not nil Export calls will
|
||||
// block until an error is received.
|
||||
func NewHTTPCollector(endpoint string, resultCh <-chan ExportResult, opts ...func(*HTTPCollector)) (*HTTPCollector, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Host == "" {
|
||||
u.Host = "localhost:0"
|
||||
}
|
||||
if u.Path == "" {
|
||||
u.Path = oconf.DefaultMetricsPath
|
||||
}
|
||||
|
||||
c := &HTTPCollector{
|
||||
headers: http.Header{},
|
||||
storage: NewStorage(),
|
||||
resultCh: resultCh,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
c.listener, err = net.Listen("tcp", u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(u.Path, http.HandlerFunc(c.handler))
|
||||
c.srv = &http.Server{
|
||||
Handler: mux,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
if u.Scheme == "https" {
|
||||
cert, err := weakCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.srv.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
go func() { _ = c.srv.ServeTLS(c.listener, "", "") }()
|
||||
} else {
|
||||
go func() { _ = c.srv.Serve(c.listener) }()
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// WithHTTPCollectorRespondingPlainText makes the HTTPCollector return
|
||||
// a plaintext, instead of protobuf, response.
|
||||
func WithHTTPCollectorRespondingPlainText() func(*HTTPCollector) {
|
||||
return func(s *HTTPCollector) {
|
||||
s.plainTextResponse = true
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown shuts down the HTTP server closing all open connections and
|
||||
// listeners.
|
||||
func (c *HTTPCollector) Shutdown(ctx context.Context) error {
|
||||
return c.srv.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// Addr returns the net.Addr c is listening at.
|
||||
func (c *HTTPCollector) Addr() net.Addr {
|
||||
return c.listener.Addr()
|
||||
}
|
||||
|
||||
// Collect returns the Storage holding all collected requests.
|
||||
func (c *HTTPCollector) Collect() *Storage {
|
||||
return c.storage
|
||||
}
|
||||
|
||||
// Headers returns the headers received for all requests.
|
||||
func (c *HTTPCollector) Headers() map[string][]string {
|
||||
// Makes a copy.
|
||||
c.headersMu.Lock()
|
||||
defer c.headersMu.Unlock()
|
||||
return c.headers.Clone()
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) handler(w http.ResponseWriter, r *http.Request) {
|
||||
c.respond(w, c.record(r))
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) record(r *http.Request) ExportResult {
|
||||
// Currently only supports protobuf.
|
||||
if v := r.Header.Get("Content-Type"); v != "application/x-protobuf" {
|
||||
err := fmt.Errorf("content-type not supported: %s", v)
|
||||
return ExportResult{Err: err}
|
||||
}
|
||||
|
||||
body, err := c.readBody(r)
|
||||
if err != nil {
|
||||
return ExportResult{Err: err}
|
||||
}
|
||||
pbRequest := &collpb.ExportMetricsServiceRequest{}
|
||||
err = proto.Unmarshal(body, pbRequest)
|
||||
if err != nil {
|
||||
return ExportResult{
|
||||
Err: &HTTPResponseError{
|
||||
Err: err,
|
||||
Status: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
}
|
||||
c.storage.Add(pbRequest)
|
||||
|
||||
c.headersMu.Lock()
|
||||
for k, vals := range r.Header {
|
||||
for _, v := range vals {
|
||||
c.headers.Add(k, v)
|
||||
}
|
||||
}
|
||||
c.headersMu.Unlock()
|
||||
|
||||
if c.resultCh != nil {
|
||||
return <-c.resultCh
|
||||
}
|
||||
return ExportResult{Err: err}
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) readBody(r *http.Request) (body []byte, err error) {
|
||||
var reader io.ReadCloser
|
||||
switch r.Header.Get("Content-Encoding") {
|
||||
case "gzip":
|
||||
reader, err = gzip.NewReader(r.Body)
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
return nil, &HTTPResponseError{
|
||||
Err: err,
|
||||
Status: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
default:
|
||||
reader = r.Body
|
||||
}
|
||||
|
||||
defer func() {
|
||||
cErr := reader.Close()
|
||||
if err == nil && cErr != nil {
|
||||
err = &HTTPResponseError{
|
||||
Err: cErr,
|
||||
Status: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
}()
|
||||
body, err = io.ReadAll(reader)
|
||||
if err != nil {
|
||||
err = &HTTPResponseError{
|
||||
Err: err,
|
||||
Status: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) respond(w http.ResponseWriter, resp ExportResult) {
|
||||
if resp.Err != nil {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
var e *HTTPResponseError
|
||||
if errors.As(resp.Err, &e) {
|
||||
for k, vals := range e.Header {
|
||||
for _, v := range vals {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(e.Status)
|
||||
fmt.Fprintln(w, e.Error())
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintln(w, resp.Err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if c.plainTextResponse {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-protobuf")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if resp.Response == nil {
|
||||
_, _ = w.Write(emptyExportMetricsServiceResponse)
|
||||
} else {
|
||||
r, err := proto.Marshal(resp.Response)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, _ = w.Write(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://golang.org/src/crypto/tls/generate_cert.go,
|
||||
// simplified and weakened.
|
||||
func weakCertificate() (tls.Certificate, error) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Hour)
|
||||
max := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: sn,
|
||||
Subject: pkix.Name{Organization: []string{"otel-go"}},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{net.IPv6loopback, net.IPv4(127, 0, 0, 1)},
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
var certBuf bytes.Buffer
|
||||
err = pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
var privBuf bytes.Buffer
|
||||
err = pem.Encode(&privBuf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.X509KeyPair(certBuf.Bytes(), privBuf.Bytes())
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry"
|
||||
colmetricpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
|
||||
metricpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
type clientShim struct {
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
metricpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
// Exporter is a OpenTelemetry metric Exporter using protobufs over HTTP.
|
||||
|
@ -11,7 +11,8 @@ require (
|
||||
go.opentelemetry.io/otel v1.25.0
|
||||
go.opentelemetry.io/otel/sdk v1.25.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.25.0
|
||||
go.opentelemetry.io/proto/slim/otlp v1.2.0
|
||||
go.opentelemetry.io/proto/otlp v1.2.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
)
|
||||
|
||||
@ -19,13 +20,17 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.25.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
@ -10,25 +10,34 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.opentelemetry.io/proto/slim/otlp v1.2.0 h1:90eMxPHyObsdi/dB1ZP8FP3s3txzxVXjArYqLxPuLZg=
|
||||
go.opentelemetry.io/proto/slim/otlp v1.2.0/go.mod h1:DeSHUkdUaCemrUs/Nmnsdo8BtM+XmdTEVjYWYFiLQhU=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -12,15 +12,20 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig.go.tmpl "--data={}" --out=envconfig/envconfig.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig_test.go.tmpl "--data={}" --out=envconfig/envconfig_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig\"}" --out=oconf/envconfig.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl "--data={}" --out=oconf/envconfig_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options.go.tmpl "--data={\"retryImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry\"}" --out=oconf/options.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig\"}" --out=oconf/options_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/optiontypes.go.tmpl "--data={}" --out=oconf/optiontypes.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/tls.go.tmpl "--data={}" --out=oconf/tls.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\", \"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal\"}" --out=otest/client_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal\"}" --out=otest/client_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/collector.go.tmpl "--data={\"oconfImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf\"}" --out=otest/collector.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/attribute.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/attribute_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/error.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={}" --out=transform/attribute.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={}" --out=transform/attribute_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={}" --out=transform/error.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error_test.go.tmpl "--data={}" --out=transform/error_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/metricdata.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/metricdata_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={}" --out=transform/metricdata.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={}" --out=transform/metricdata_test.go
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -25,6 +28,15 @@ var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{
|
||||
Namespace: "OTEL_EXPORTER_OTLP",
|
||||
}
|
||||
|
||||
// ApplyGRPCEnvConfigs applies the env configurations for gRPC.
|
||||
func ApplyGRPCEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// ApplyHTTPEnvConfigs applies the env configurations for HTTP.
|
||||
func ApplyHTTPEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
@ -34,25 +46,25 @@ func ApplyHTTPEnvConfigs(cfg Config) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getOptionsFromEnv() []HTTPOption {
|
||||
opts := []HTTPOption{}
|
||||
func getOptionsFromEnv() []GenericOption {
|
||||
opts := []GenericOption{}
|
||||
|
||||
tlsConf := &tls.Config{}
|
||||
DefaultEnvOptionsReader.Apply(
|
||||
envconfig.WithURL("ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, NewHTTPOption(func(cfg Config) Config {
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
// For OTLP/HTTP endpoint URLs without a per-signal
|
||||
// configuration, the passed endpoint is used as a base URL
|
||||
// and the signals are sent to these paths relative to that.
|
||||
cfg.Metrics.URLPath = path.Join(u.Path, DefaultMetricsPath)
|
||||
return cfg
|
||||
}))
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithURL("METRICS_ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, NewHTTPOption(func(cfg Config) Config {
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
// For endpoint URLs for OTLP/HTTP per-signal variables, the
|
||||
// URL MUST be used as-is without any modification. The only
|
||||
@ -64,7 +76,7 @@ func getOptionsFromEnv() []HTTPOption {
|
||||
}
|
||||
cfg.Metrics.URLPath = path
|
||||
return cfg
|
||||
}))
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
@ -86,6 +98,15 @@ func getOptionsFromEnv() []HTTPOption {
|
||||
return opts
|
||||
}
|
||||
|
||||
func withEndpointForGRPC(u *url.URL) func(cfg Config) Config {
|
||||
return func(cfg Config) Config {
|
||||
// For OTLP/gRPC endpoints, this is the target to which the
|
||||
// exporter is going to send telemetry.
|
||||
cfg.Metrics.Endpoint = path.Join(u.Host, u.Path)
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression.
|
||||
func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
@ -100,7 +121,7 @@ func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOpt
|
||||
}
|
||||
}
|
||||
|
||||
func withEndpointScheme(u *url.URL) HTTPOption {
|
||||
func withEndpointScheme(u *url.URL) GenericOption {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http", "unix":
|
||||
return WithInsecure()
|
||||
@ -110,7 +131,7 @@ func withEndpointScheme(u *url.URL) HTTPOption {
|
||||
}
|
||||
|
||||
// revive:disable-next-line:flag-parameter
|
||||
func withInsecure(b bool) HTTPOption {
|
||||
func withInsecure(b bool) GenericOption {
|
||||
if b {
|
||||
return WithInsecure()
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -74,7 +77,7 @@ func TestWithEnvTemporalityPreference(t *testing.T) {
|
||||
return origReader(key)
|
||||
}
|
||||
cfg := Config{}
|
||||
cfg = ApplyHTTPEnvConfigs(cfg)
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
|
||||
if tt.want == nil {
|
||||
// There is no function set, the SDK's default is used.
|
||||
@ -144,7 +147,7 @@ func TestWithEnvAggPreference(t *testing.T) {
|
||||
return origReader(key)
|
||||
}
|
||||
cfg := Config{}
|
||||
cfg = ApplyHTTPEnvConfigs(cfg)
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
|
||||
if tt.want == nil {
|
||||
// There is no function set, the SDK's default is used.
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/options.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -12,6 +15,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
@ -38,7 +47,6 @@ type (
|
||||
// This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client.
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
// SignalConfig represents signal specific configuration.
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
@ -48,17 +56,26 @@ type (
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
// gRPC configurations
|
||||
GRPCCredentials credentials.TransportCredentials
|
||||
|
||||
TemporalitySelector metric.TemporalitySelector
|
||||
AggregationSelector metric.AggregationSelector
|
||||
|
||||
Proxy HTTPTransportProxyFunc
|
||||
}
|
||||
|
||||
// Config represents exporter configuration.
|
||||
Config struct {
|
||||
// Signal specific configurations
|
||||
Metrics SignalConfig
|
||||
|
||||
RetryConfig retry.Config
|
||||
|
||||
// gRPC configurations
|
||||
ReconnectionPeriod time.Duration
|
||||
ServiceConfig string
|
||||
DialOptions []grpc.DialOption
|
||||
GRPCConn *grpc.ClientConn
|
||||
}
|
||||
)
|
||||
|
||||
@ -99,8 +116,68 @@ func cleanPath(urlPath string, defaultPath string) string {
|
||||
return tmp
|
||||
}
|
||||
|
||||
// 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 := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
|
||||
if cfg.ServiceConfig != "" {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultServiceConfig(cfg.ServiceConfig))
|
||||
}
|
||||
// Priroritize GRPCCredentials over Insecure (passing both is an error).
|
||||
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.WithTransportCredentials(insecure.NewCredentials()))
|
||||
} else {
|
||||
// Default to using the host's root CA.
|
||||
creds := credentials.NewTLS(nil)
|
||||
cfg.Metrics.GRPCCredentials = creds
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
if cfg.Metrics.Compression == GzipCompression {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)))
|
||||
}
|
||||
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 {
|
||||
ApplyHTTPOption(Config) Config
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// HTTPOption applies an option to the HTTP driver.
|
||||
type HTTPOption interface {
|
||||
HTTPOption interface {
|
||||
ApplyHTTPOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
@ -109,6 +186,58 @@ type HTTPOption interface {
|
||||
private()
|
||||
}
|
||||
|
||||
// GRPCOption applies an option to the gRPC driver.
|
||||
GRPCOption interface {
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
)
|
||||
|
||||
// genericOption is an option that applies the same logic
|
||||
// for both gRPC and HTTP.
|
||||
type genericOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (genericOption) private() {}
|
||||
|
||||
func newGenericOption(fn func(cfg Config) Config) GenericOption {
|
||||
return &genericOption{fn: fn}
|
||||
}
|
||||
|
||||
// splitOption is an option that applies different logics
|
||||
// for gRPC and HTTP.
|
||||
type splitOption struct {
|
||||
httpFn func(Config) Config
|
||||
grpcFn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.grpcFn(cfg)
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.httpFn(cfg)
|
||||
}
|
||||
|
||||
func (splitOption) private() {}
|
||||
|
||||
func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption {
|
||||
return &splitOption{httpFn: httpFn, grpcFn: grpcFn}
|
||||
}
|
||||
|
||||
// httpOption is an option that is only applied to the HTTP driver.
|
||||
type httpOption struct {
|
||||
fn func(Config) Config
|
||||
@ -124,17 +253,32 @@ func NewHTTPOption(fn func(cfg Config) Config) HTTPOption {
|
||||
return &httpOption{fn: fn}
|
||||
}
|
||||
|
||||
// grpcOption is an option that is only applied to the gRPC driver.
|
||||
type grpcOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (h *grpcOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return h.fn(cfg)
|
||||
}
|
||||
|
||||
func (grpcOption) private() {}
|
||||
|
||||
func NewGRPCOption(fn func(cfg Config) Config) GRPCOption {
|
||||
return &grpcOption{fn: fn}
|
||||
}
|
||||
|
||||
// Generic Options
|
||||
|
||||
func WithEndpoint(endpoint string) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithEndpoint(endpoint string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = endpoint
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithEndpointURL(v string) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithEndpointURL(v string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
global.Error(err, "otlpmetric: parse endpoint url", "url", v)
|
||||
@ -151,78 +295,81 @@ func WithEndpointURL(v string) HTTPOption {
|
||||
})
|
||||
}
|
||||
|
||||
func WithCompression(compression Compression) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithCompression(compression Compression) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Compression = compression
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithURLPath(urlPath string) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithURLPath(urlPath string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.URLPath = urlPath
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithRetry(rc retry.Config) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithRetry(rc retry.Config) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.RetryConfig = rc
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption {
|
||||
return newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TLSCfg = tlsCfg.Clone()
|
||||
return cfg
|
||||
}, func(cfg Config) Config {
|
||||
cfg.Metrics.GRPCCredentials = credentials.NewTLS(tlsCfg)
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithInsecure() HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithInsecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Insecure = true
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithSecure() HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithSecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Insecure = false
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithHeaders(headers map[string]string) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithHeaders(headers map[string]string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Headers = headers
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTimeout(duration time.Duration) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithTimeout(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Timeout = duration
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TemporalitySelector = selector
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithAggregationSelector(selector metric.AggregationSelector) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithAggregationSelector(selector metric.AggregationSelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.AggregationSelector = selector
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithProxy(pf HTTPTransportProxyFunc) HTTPOption {
|
||||
return NewHTTPOption(func(cfg Config) Config {
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Proxy = pf
|
||||
return cfg
|
||||
})
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -61,15 +64,19 @@ func TestConfigs(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []HTTPOption
|
||||
opts []GenericOption
|
||||
env env
|
||||
fileReader fileReader
|
||||
asserts func(t *testing.T, c *Config)
|
||||
asserts func(t *testing.T, c *Config, grpcOption bool)
|
||||
}{
|
||||
{
|
||||
name: "Test default configs",
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.Equal(t, "localhost:4317", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "localhost:4318", c.Metrics.Endpoint)
|
||||
}
|
||||
assert.Equal(t, NoCompression, c.Metrics.Compression)
|
||||
assert.Equal(t, map[string]string(nil), c.Metrics.Headers)
|
||||
assert.Equal(t, 10*time.Second, c.Metrics.Timeout)
|
||||
@ -79,19 +86,19 @@ func TestConfigs(t *testing.T) {
|
||||
// Endpoint Tests
|
||||
{
|
||||
name: "Test With Endpoint",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpoint("someendpoint"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Endpoint URL",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("http://someendpoint/somepath"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/somepath", c.Metrics.URLPath)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
@ -99,10 +106,10 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Test With Secure Endpoint URL",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("https://someendpoint/somepath"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/somepath", c.Metrics.URLPath)
|
||||
assert.Equal(t, false, c.Metrics.Insecure)
|
||||
@ -110,11 +117,15 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Test With Invalid Endpoint URL",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("%invalid"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.Equal(t, "localhost:4317", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "localhost:4318", c.Metrics.Endpoint)
|
||||
}
|
||||
assert.Equal(t, "/v1/metrics", c.Metrics.URLPath)
|
||||
},
|
||||
},
|
||||
@ -123,10 +134,14 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env.endpoint/prefix",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.False(t, c.Metrics.Insecure)
|
||||
if grpcOption {
|
||||
assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "env.endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -135,21 +150,23 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://overrode.by.signal.specific/env/var",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://env.metrics.endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.True(t, c.Metrics.Insecure)
|
||||
assert.Equal(t, "env.metrics.endpoint", c.Metrics.Endpoint)
|
||||
if !grpcOption {
|
||||
assert.Equal(t, "/", c.Metrics.URLPath)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Endpoint",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithEndpoint("metrics_endpoint"),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "metrics_endpoint", c.Metrics.Endpoint)
|
||||
},
|
||||
},
|
||||
@ -158,7 +175,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
@ -168,7 +185,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
@ -178,7 +195,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, false, c.Metrics.Insecure)
|
||||
},
|
||||
@ -189,7 +206,7 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "HTTPS://overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "HtTp://env_metrics_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_metrics_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
@ -198,18 +215,27 @@ func TestConfigs(t *testing.T) {
|
||||
// Certificate tests
|
||||
{
|
||||
name: "Test Default Certificate",
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
assert.Nil(t, c.Metrics.TLSCfg)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Certificate",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithTLSClientConfig(tlsCert),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
// TODO: make sure gRPC's credentials actually works
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -220,9 +246,13 @@ func TestConfigs(t *testing.T) {
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -235,40 +265,48 @@ func TestConfigs(t *testing.T) {
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
"invalid_cert": []byte("invalid certificate file."),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Certificate",
|
||||
opts: []HTTPOption{},
|
||||
opts: []GenericOption{},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects()))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Headers tests
|
||||
{
|
||||
name: "Test With Headers",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithHeaders(map[string]string{"h1": "v1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
@ -278,17 +316,17 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_HEADERS": "h1=v1,h2=v2",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithHeaders(map[string]string{"m1": "mv1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"m1": "mv1"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
@ -296,10 +334,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Compression Tests
|
||||
{
|
||||
name: "Test With Compression",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithCompression(GzipCompression),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
@ -308,7 +346,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
@ -317,19 +355,19 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Compression",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithCompression(NoCompression),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, NoCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
@ -337,10 +375,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Timeout Tests
|
||||
{
|
||||
name: "Test With Timeout",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithTimeout(time.Duration(5 * time.Second)),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, 5*time.Second, c.Metrics.Timeout)
|
||||
},
|
||||
},
|
||||
@ -349,7 +387,7 @@ func TestConfigs(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 15*time.Second)
|
||||
},
|
||||
},
|
||||
@ -359,7 +397,7 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 28*time.Second)
|
||||
},
|
||||
},
|
||||
@ -369,10 +407,10 @@ func TestConfigs(t *testing.T) {
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000",
|
||||
},
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithTimeout(5 * time.Second),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 5*time.Second)
|
||||
},
|
||||
},
|
||||
@ -380,10 +418,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Temporality Selector Tests
|
||||
{
|
||||
name: "WithTemporalitySelector",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithTemporalitySelector(deltaSelector),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
// Function value comparisons are disallowed, test non-default
|
||||
// behavior of a TemporalitySelector here to ensure our "catch
|
||||
// all" was set.
|
||||
@ -396,10 +434,10 @@ func TestConfigs(t *testing.T) {
|
||||
// Aggregation Selector Tests
|
||||
{
|
||||
name: "WithAggregationSelector",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithAggregationSelector(dropSelector),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
// Function value comparisons are disallowed, test non-default
|
||||
// behavior of a AggregationSelector here to ensure our "catch
|
||||
// all" was set.
|
||||
@ -412,12 +450,12 @@ func TestConfigs(t *testing.T) {
|
||||
// Proxy Tests
|
||||
{
|
||||
name: "Test With Proxy",
|
||||
opts: []HTTPOption{
|
||||
opts: []GenericOption{
|
||||
WithProxy(func(r *http.Request) (*url.URL, error) {
|
||||
return url.Parse("http://proxy.com")
|
||||
}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.NotNil(t, c.Metrics.Proxy)
|
||||
proxyURL, err := c.Metrics.Proxy(&http.Request{})
|
||||
assert.NoError(t, err)
|
||||
@ -426,8 +464,8 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Test Without Proxy",
|
||||
opts: []HTTPOption{},
|
||||
asserts: func(t *testing.T, c *Config) {
|
||||
opts: []GenericOption{},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Nil(t, c.Metrics.Proxy)
|
||||
},
|
||||
},
|
||||
@ -444,8 +482,12 @@ func TestConfigs(t *testing.T) {
|
||||
t.Cleanup(func() { DefaultEnvOptionsReader = origEOR })
|
||||
|
||||
// Tests Generic options as HTTP Options
|
||||
cfg := NewHTTPConfig(tt.opts...)
|
||||
tt.asserts(t, &cfg)
|
||||
cfg := NewHTTPConfig(asHTTPOptions(tt.opts)...)
|
||||
tt.asserts(t, &cfg, false)
|
||||
|
||||
// Tests Generic options as gRPC Options
|
||||
cfg = NewGRPCConfig(asGRPCOptions(tt.opts)...)
|
||||
tt.asserts(t, &cfg, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -458,6 +500,22 @@ func deltaSelector(metric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.DeltaTemporality
|
||||
}
|
||||
|
||||
func asHTTPOptions(opts []GenericOption) []HTTPOption {
|
||||
converted := make([]HTTPOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = NewHTTPOption(o.ApplyHTTPOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func asGRPCOptions(opts []GenericOption) []GRPCOption {
|
||||
converted := make([]GRPCOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = NewGRPCOption(o.ApplyGRPCOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
type args struct {
|
||||
urlPath string
|
||||
|
@ -19,10 +19,10 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
collpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1"
|
||||
collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -14,8 +14,8 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/otest/collector.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@ -24,11 +27,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf"
|
||||
collpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
// Collector is the collection target a Client sends metric uploads to.
|
||||
@ -36,7 +41,6 @@ type Collector interface {
|
||||
Collect() *Storage
|
||||
}
|
||||
|
||||
// ExportResult represents an export response.
|
||||
type ExportResult struct {
|
||||
Response *collpb.ExportMetricsServiceResponse
|
||||
Err error
|
||||
@ -70,6 +74,93 @@ func (s *Storage) Dump() []*mpb.ResourceMetrics {
|
||||
return data
|
||||
}
|
||||
|
||||
// GRPCCollector is an OTLP gRPC server that collects all requests it receives.
|
||||
type GRPCCollector struct {
|
||||
collpb.UnimplementedMetricsServiceServer
|
||||
|
||||
headersMu sync.Mutex
|
||||
headers metadata.MD
|
||||
storage *Storage
|
||||
|
||||
resultCh <-chan ExportResult
|
||||
listener net.Listener
|
||||
srv *grpc.Server
|
||||
}
|
||||
|
||||
// NewGRPCCollector returns a *GRPCCollector that is listening at the provided
|
||||
// endpoint.
|
||||
//
|
||||
// If endpoint is an empty string, the returned collector will be listening on
|
||||
// the localhost interface at an OS chosen port.
|
||||
//
|
||||
// If errCh is not nil, the collector will respond to Export calls with errors
|
||||
// sent on that channel. This means that if errCh is not nil Export calls will
|
||||
// block until an error is received.
|
||||
func NewGRPCCollector(endpoint string, resultCh <-chan ExportResult) (*GRPCCollector, error) {
|
||||
if endpoint == "" {
|
||||
endpoint = "localhost:0"
|
||||
}
|
||||
|
||||
c := &GRPCCollector{
|
||||
storage: NewStorage(),
|
||||
resultCh: resultCh,
|
||||
}
|
||||
|
||||
var err error
|
||||
c.listener, err = net.Listen("tcp", endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.srv = grpc.NewServer()
|
||||
collpb.RegisterMetricsServiceServer(c.srv, c)
|
||||
go func() { _ = c.srv.Serve(c.listener) }()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Shutdown shuts down the gRPC server closing all open connections and
|
||||
// listeners immediately.
|
||||
func (c *GRPCCollector) Shutdown() { c.srv.Stop() }
|
||||
|
||||
// Addr returns the net.Addr c is listening at.
|
||||
func (c *GRPCCollector) Addr() net.Addr {
|
||||
return c.listener.Addr()
|
||||
}
|
||||
|
||||
// Collect returns the Storage holding all collected requests.
|
||||
func (c *GRPCCollector) Collect() *Storage {
|
||||
return c.storage
|
||||
}
|
||||
|
||||
// Headers returns the headers received for all requests.
|
||||
func (c *GRPCCollector) Headers() map[string][]string {
|
||||
// Makes a copy.
|
||||
c.headersMu.Lock()
|
||||
defer c.headersMu.Unlock()
|
||||
return metadata.Join(c.headers)
|
||||
}
|
||||
|
||||
// Export handles the export req.
|
||||
func (c *GRPCCollector) Export(ctx context.Context, req *collpb.ExportMetricsServiceRequest) (*collpb.ExportMetricsServiceResponse, error) {
|
||||
c.storage.Add(req)
|
||||
|
||||
if h, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
c.headersMu.Lock()
|
||||
c.headers = metadata.Join(c.headers, h)
|
||||
c.headersMu.Unlock()
|
||||
}
|
||||
|
||||
if c.resultCh != nil {
|
||||
r := <-c.resultCh
|
||||
if r.Response == nil {
|
||||
return &collpb.ExportMetricsServiceResponse{}, r.Err
|
||||
}
|
||||
return r.Response, r.Err
|
||||
}
|
||||
return &collpb.ExportMetricsServiceResponse{}, nil
|
||||
}
|
||||
|
||||
var emptyExportMetricsServiceResponse = func() []byte {
|
||||
body := collpb.ExportMetricsServiceResponse{}
|
||||
r, err := proto.Marshal(&body)
|
||||
@ -79,7 +170,6 @@ var emptyExportMetricsServiceResponse = func() []byte {
|
||||
return r
|
||||
}()
|
||||
|
||||
// HTTPResponseError is used to mock a HTTP response error.
|
||||
type HTTPResponseError struct {
|
||||
Err error
|
||||
Status int
|
||||
|
@ -8,7 +8,7 @@ package transform // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
// AttrIter transforms an attribute iterator into OTLP key-values.
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -13,9 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
)
|
||||
|
||||
// ResourceMetrics returns an OTLP ResourceMetrics generated from rm. If rm
|
||||
|
@ -18,9 +18,9 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
cpb "go.opentelemetry.io/proto/slim/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
)
|
||||
|
||||
type unknownAggT struct {
|
||||
|
210
internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl
Normal file
210
internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl
Normal file
@ -0,0 +1,210 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oconf
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"{{ .envconfigImportPath }}"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
)
|
||||
|
||||
// DefaultEnvOptionsReader is the default environments reader.
|
||||
var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{
|
||||
GetEnv: os.Getenv,
|
||||
ReadFile: os.ReadFile,
|
||||
Namespace: "OTEL_EXPORTER_OTLP",
|
||||
}
|
||||
|
||||
// ApplyGRPCEnvConfigs applies the env configurations for gRPC.
|
||||
func ApplyGRPCEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// ApplyHTTPEnvConfigs applies the env configurations for HTTP.
|
||||
func ApplyHTTPEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getOptionsFromEnv() []GenericOption {
|
||||
opts := []GenericOption{}
|
||||
|
||||
tlsConf := &tls.Config{}
|
||||
DefaultEnvOptionsReader.Apply(
|
||||
envconfig.WithURL("ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
// For OTLP/HTTP endpoint URLs without a per-signal
|
||||
// configuration, the passed endpoint is used as a base URL
|
||||
// and the signals are sent to these paths relative to that.
|
||||
cfg.Metrics.URLPath = path.Join(u.Path, DefaultMetricsPath)
|
||||
return cfg
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithURL("METRICS_ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
// For endpoint URLs for OTLP/HTTP per-signal variables, the
|
||||
// URL MUST be used as-is without any modification. The only
|
||||
// exception is that if an URL contains no path part, the root
|
||||
// path / MUST be used.
|
||||
path := u.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
cfg.Metrics.URLPath = path
|
||||
return cfg
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
envconfig.WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }),
|
||||
envconfig.WithClientCert("METRICS_CLIENT_CERTIFICATE", "METRICS_CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }),
|
||||
envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
|
||||
envconfig.WithBool("METRICS_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
|
||||
withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
|
||||
envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
|
||||
envconfig.WithHeaders("METRICS_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
|
||||
WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }),
|
||||
WithEnvCompression("METRICS_COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }),
|
||||
envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }),
|
||||
envconfig.WithDuration("METRICS_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }),
|
||||
withEnvTemporalityPreference("METRICS_TEMPORALITY_PREFERENCE", func(t metric.TemporalitySelector) { opts = append(opts, WithTemporalitySelector(t)) }),
|
||||
withEnvAggPreference("METRICS_DEFAULT_HISTOGRAM_AGGREGATION", func(a metric.AggregationSelector) { opts = append(opts, WithAggregationSelector(a)) }),
|
||||
)
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func withEndpointForGRPC(u *url.URL) func(cfg Config) Config {
|
||||
return func(cfg Config) Config {
|
||||
// For OTLP/gRPC endpoints, this is the target to which the
|
||||
// exporter is going to send telemetry.
|
||||
cfg.Metrics.Endpoint = path.Join(u.Host, u.Path)
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression.
|
||||
func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
cp := NoCompression
|
||||
if v == "gzip" {
|
||||
cp = GzipCompression
|
||||
}
|
||||
|
||||
fn(cp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withEndpointScheme(u *url.URL) GenericOption {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http", "unix":
|
||||
return WithInsecure()
|
||||
default:
|
||||
return WithSecure()
|
||||
}
|
||||
}
|
||||
|
||||
// revive:disable-next-line:flag-parameter
|
||||
func withInsecure(b bool) GenericOption {
|
||||
if b {
|
||||
return WithInsecure()
|
||||
}
|
||||
return WithSecure()
|
||||
}
|
||||
|
||||
func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if c.RootCAs != nil || len(c.Certificates) > 0 {
|
||||
fn(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withEnvTemporalityPreference(n string, fn func(metric.TemporalitySelector)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if s, ok := e.GetEnvValue(n); ok {
|
||||
switch strings.ToLower(s) {
|
||||
case "cumulative":
|
||||
fn(cumulativeTemporality)
|
||||
case "delta":
|
||||
fn(deltaTemporality)
|
||||
case "lowmemory":
|
||||
fn(lowMemory)
|
||||
default:
|
||||
global.Warn("OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE is set to an invalid value, ignoring.", "value", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cumulativeTemporality(metric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
|
||||
func deltaTemporality(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
switch ik {
|
||||
case metric.InstrumentKindCounter, metric.InstrumentKindHistogram, metric.InstrumentKindObservableCounter:
|
||||
return metricdata.DeltaTemporality
|
||||
default:
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
}
|
||||
|
||||
func lowMemory(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
switch ik {
|
||||
case metric.InstrumentKindCounter, metric.InstrumentKindHistogram:
|
||||
return metricdata.DeltaTemporality
|
||||
default:
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
}
|
||||
|
||||
func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if s, ok := e.GetEnvValue(n); ok {
|
||||
switch strings.ToLower(s) {
|
||||
case "explicit_bucket_histogram":
|
||||
fn(metric.DefaultAggregationSelector)
|
||||
case "base2_exponential_bucket_histogram":
|
||||
fn(func(kind metric.InstrumentKind) metric.Aggregation {
|
||||
if kind == metric.InstrumentKindHistogram {
|
||||
return metric.AggregationBase2ExponentialHistogram{
|
||||
MaxSize: 160,
|
||||
MaxScale: 20,
|
||||
NoMinMax: false,
|
||||
}
|
||||
}
|
||||
return metric.DefaultAggregationSelector(kind)
|
||||
})
|
||||
default:
|
||||
global.Warn("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION is set to an invalid value, ignoring.", "value", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
165
internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl
Normal file
165
internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl
Normal file
@ -0,0 +1,165 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oconf
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
)
|
||||
|
||||
func TestWithEnvTemporalityPreference(t *testing.T) {
|
||||
origReader := DefaultEnvOptionsReader.GetEnv
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
want map[metric.InstrumentKind]metricdata.Temporality
|
||||
}{
|
||||
{
|
||||
name: "default do not set the selector",
|
||||
envValue: "",
|
||||
},
|
||||
{
|
||||
name: "non-normative do not set the selector",
|
||||
envValue: "non-normative",
|
||||
},
|
||||
{
|
||||
name: "cumulative",
|
||||
envValue: "cumulative",
|
||||
want: map[metric.InstrumentKind]metricdata.Temporality{
|
||||
metric.InstrumentKindCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindHistogram: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindUpDownCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableUpDownCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableGauge: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delta",
|
||||
envValue: "delta",
|
||||
want: map[metric.InstrumentKind]metricdata.Temporality{
|
||||
metric.InstrumentKindCounter: metricdata.DeltaTemporality,
|
||||
metric.InstrumentKindHistogram: metricdata.DeltaTemporality,
|
||||
metric.InstrumentKindUpDownCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableCounter: metricdata.DeltaTemporality,
|
||||
metric.InstrumentKindObservableUpDownCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableGauge: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lowmemory",
|
||||
envValue: "lowmemory",
|
||||
want: map[metric.InstrumentKind]metricdata.Temporality{
|
||||
metric.InstrumentKindCounter: metricdata.DeltaTemporality,
|
||||
metric.InstrumentKindHistogram: metricdata.DeltaTemporality,
|
||||
metric.InstrumentKindUpDownCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableUpDownCounter: metricdata.CumulativeTemporality,
|
||||
metric.InstrumentKindObservableGauge: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
DefaultEnvOptionsReader.GetEnv = func(key string) string {
|
||||
if key == "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" {
|
||||
return tt.envValue
|
||||
}
|
||||
return origReader(key)
|
||||
}
|
||||
cfg := Config{}
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
|
||||
if tt.want == nil {
|
||||
// There is no function set, the SDK's default is used.
|
||||
assert.Nil(t, cfg.Metrics.TemporalitySelector)
|
||||
return
|
||||
}
|
||||
|
||||
require.NotNil(t, cfg.Metrics.TemporalitySelector)
|
||||
for ik, want := range tt.want {
|
||||
assert.Equal(t, want, cfg.Metrics.TemporalitySelector(ik))
|
||||
}
|
||||
})
|
||||
}
|
||||
DefaultEnvOptionsReader.GetEnv = origReader
|
||||
}
|
||||
|
||||
func TestWithEnvAggPreference(t *testing.T) {
|
||||
origReader := DefaultEnvOptionsReader.GetEnv
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
want map[metric.InstrumentKind]metric.Aggregation
|
||||
}{
|
||||
{
|
||||
name: "default do not set the selector",
|
||||
envValue: "",
|
||||
},
|
||||
{
|
||||
name: "non-normative do not set the selector",
|
||||
envValue: "non-normative",
|
||||
},
|
||||
{
|
||||
name: "explicit_bucket_histogram",
|
||||
envValue: "explicit_bucket_histogram",
|
||||
want: map[metric.InstrumentKind]metric.Aggregation{
|
||||
metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter),
|
||||
metric.InstrumentKindHistogram: metric.DefaultAggregationSelector(metric.InstrumentKindHistogram),
|
||||
metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter),
|
||||
metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter),
|
||||
metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter),
|
||||
metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "base2_exponential_bucket_histogram",
|
||||
envValue: "base2_exponential_bucket_histogram",
|
||||
want: map[metric.InstrumentKind]metric.Aggregation{
|
||||
metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter),
|
||||
metric.InstrumentKindHistogram: metric.AggregationBase2ExponentialHistogram{
|
||||
MaxSize: 160,
|
||||
MaxScale: 20,
|
||||
NoMinMax: false,
|
||||
},
|
||||
metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter),
|
||||
metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter),
|
||||
metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter),
|
||||
metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
DefaultEnvOptionsReader.GetEnv = func(key string) string {
|
||||
if key == "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION" {
|
||||
return tt.envValue
|
||||
}
|
||||
return origReader(key)
|
||||
}
|
||||
cfg := Config{}
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
|
||||
if tt.want == nil {
|
||||
// There is no function set, the SDK's default is used.
|
||||
assert.Nil(t, cfg.Metrics.AggregationSelector)
|
||||
return
|
||||
}
|
||||
|
||||
require.NotNil(t, cfg.Metrics.AggregationSelector)
|
||||
for ik, want := range tt.want {
|
||||
assert.Equal(t, want, cfg.Metrics.AggregationSelector(ik))
|
||||
}
|
||||
})
|
||||
}
|
||||
DefaultEnvOptionsReader.GetEnv = origReader
|
||||
}
|
376
internal/shared/otlp/otlpmetric/oconf/options.go.tmpl
Normal file
376
internal/shared/otlp/otlpmetric/oconf/options.go.tmpl
Normal file
@ -0,0 +1,376 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/options.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oconf
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
"{{ .retryImportPath }}"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
)
|
||||
|
||||
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
|
||||
// DefaultTimeout is a default max waiting time for the backend to process
|
||||
// each span or metrics batch.
|
||||
DefaultTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
type (
|
||||
// HTTPTransportProxyFunc is a function that resolves which URL to use as proxy for a given request.
|
||||
// This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client.
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
// gRPC configurations
|
||||
GRPCCredentials credentials.TransportCredentials
|
||||
|
||||
TemporalitySelector metric.TemporalitySelector
|
||||
AggregationSelector metric.AggregationSelector
|
||||
|
||||
Proxy HTTPTransportProxyFunc
|
||||
}
|
||||
|
||||
Config struct {
|
||||
// Signal specific configurations
|
||||
Metrics SignalConfig
|
||||
|
||||
RetryConfig retry.Config
|
||||
|
||||
// gRPC configurations
|
||||
ReconnectionPeriod time.Duration
|
||||
ServiceConfig string
|
||||
DialOptions []grpc.DialOption
|
||||
GRPCConn *grpc.ClientConn
|
||||
}
|
||||
)
|
||||
|
||||
// NewHTTPConfig returns a new Config with all settings applied from opts and
|
||||
// any unset setting using the default HTTP config values.
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
cfg = ApplyHTTPEnvConfigs(cfg)
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
cfg.Metrics.URLPath = cleanPath(cfg.Metrics.URLPath, DefaultMetricsPath)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// cleanPath returns a path with all spaces trimmed and all redundancies
|
||||
// removed. If urlPath is empty or cleaning it results in an empty string,
|
||||
// defaultPath is returned instead.
|
||||
func cleanPath(urlPath string, defaultPath string) string {
|
||||
tmp := path.Clean(strings.TrimSpace(urlPath))
|
||||
if tmp == "." {
|
||||
return defaultPath
|
||||
}
|
||||
if !path.IsAbs(tmp) {
|
||||
tmp = fmt.Sprintf("/%s", tmp)
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
// 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 := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
|
||||
if cfg.ServiceConfig != "" {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultServiceConfig(cfg.ServiceConfig))
|
||||
}
|
||||
// Priroritize GRPCCredentials over Insecure (passing both is an error).
|
||||
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.WithTransportCredentials(insecure.NewCredentials()))
|
||||
} else {
|
||||
// Default to using the host's root CA.
|
||||
creds := credentials.NewTLS(nil)
|
||||
cfg.Metrics.GRPCCredentials = creds
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
if cfg.Metrics.Compression == GzipCompression {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)))
|
||||
}
|
||||
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 {
|
||||
ApplyHTTPOption(Config) Config
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// HTTPOption applies an option to the HTTP driver.
|
||||
HTTPOption interface {
|
||||
ApplyHTTPOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// GRPCOption applies an option to the gRPC driver.
|
||||
GRPCOption interface {
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
)
|
||||
|
||||
// genericOption is an option that applies the same logic
|
||||
// for both gRPC and HTTP.
|
||||
type genericOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (genericOption) private() {}
|
||||
|
||||
func newGenericOption(fn func(cfg Config) Config) GenericOption {
|
||||
return &genericOption{fn: fn}
|
||||
}
|
||||
|
||||
// splitOption is an option that applies different logics
|
||||
// for gRPC and HTTP.
|
||||
type splitOption struct {
|
||||
httpFn func(Config) Config
|
||||
grpcFn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.grpcFn(cfg)
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.httpFn(cfg)
|
||||
}
|
||||
|
||||
func (splitOption) private() {}
|
||||
|
||||
func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption {
|
||||
return &splitOption{httpFn: httpFn, grpcFn: grpcFn}
|
||||
}
|
||||
|
||||
// httpOption is an option that is only applied to the HTTP driver.
|
||||
type httpOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (h *httpOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return h.fn(cfg)
|
||||
}
|
||||
|
||||
func (httpOption) private() {}
|
||||
|
||||
func NewHTTPOption(fn func(cfg Config) Config) HTTPOption {
|
||||
return &httpOption{fn: fn}
|
||||
}
|
||||
|
||||
// grpcOption is an option that is only applied to the gRPC driver.
|
||||
type grpcOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (h *grpcOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return h.fn(cfg)
|
||||
}
|
||||
|
||||
func (grpcOption) private() {}
|
||||
|
||||
func NewGRPCOption(fn func(cfg Config) Config) GRPCOption {
|
||||
return &grpcOption{fn: fn}
|
||||
}
|
||||
|
||||
// Generic Options
|
||||
|
||||
func WithEndpoint(endpoint string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Endpoint = endpoint
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithEndpointURL(v string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
global.Error(err, "otlpmetric: parse endpoint url", "url", v)
|
||||
return cfg
|
||||
}
|
||||
|
||||
cfg.Metrics.Endpoint = u.Host
|
||||
cfg.Metrics.URLPath = u.Path
|
||||
if u.Scheme != "https" {
|
||||
cfg.Metrics.Insecure = true
|
||||
}
|
||||
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithCompression(compression Compression) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Compression = compression
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithURLPath(urlPath string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.URLPath = urlPath
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithRetry(rc retry.Config) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.RetryConfig = rc
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption {
|
||||
return newSplitOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TLSCfg = tlsCfg.Clone()
|
||||
return cfg
|
||||
}, func(cfg Config) Config {
|
||||
cfg.Metrics.GRPCCredentials = credentials.NewTLS(tlsCfg)
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithInsecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Insecure = true
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithSecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Insecure = false
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithHeaders(headers map[string]string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Headers = headers
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTimeout(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Timeout = duration
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TemporalitySelector = selector
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithAggregationSelector(selector metric.AggregationSelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.AggregationSelector = selector
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.Proxy = pf
|
||||
return cfg
|
||||
})
|
||||
}
|
583
internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl
Normal file
583
internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl
Normal file
@ -0,0 +1,583 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oconf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"{{ .envconfigImportPath }}"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
)
|
||||
|
||||
const (
|
||||
WeakCertificate = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ
|
||||
MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa
|
||||
MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9
|
||||
nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z
|
||||
sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
|
||||
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA
|
||||
AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/
|
||||
1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH
|
||||
Lhnm4N/QDk5rek0=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
WeakPrivateKey = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN8HEXiXhvByrJ1zK
|
||||
SFT6Y2l2KqDWwWzKf+t4CyWrNKehRANCAAS9nWSkmPCxShxnp43F+PrOtbGV7sNf
|
||||
kbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0ZsJCLHGogQsYnWJBXUZOV
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
type env map[string]string
|
||||
|
||||
func (e *env) getEnv(env string) string {
|
||||
return (*e)[env]
|
||||
}
|
||||
|
||||
type fileReader map[string][]byte
|
||||
|
||||
func (f *fileReader) readFile(filename string) ([]byte, error) {
|
||||
if b, ok := (*f)[filename]; ok {
|
||||
return b, nil
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
}
|
||||
|
||||
func TestConfigs(t *testing.T) {
|
||||
tlsCert, err := CreateTLSConfig([]byte(WeakCertificate))
|
||||
assert.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []GenericOption
|
||||
env env
|
||||
fileReader fileReader
|
||||
asserts func(t *testing.T, c *Config, grpcOption bool)
|
||||
}{
|
||||
{
|
||||
name: "Test default configs",
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.Equal(t, "localhost:4317", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "localhost:4318", c.Metrics.Endpoint)
|
||||
}
|
||||
assert.Equal(t, NoCompression, c.Metrics.Compression)
|
||||
assert.Equal(t, map[string]string(nil), c.Metrics.Headers)
|
||||
assert.Equal(t, 10*time.Second, c.Metrics.Timeout)
|
||||
},
|
||||
},
|
||||
|
||||
// Endpoint Tests
|
||||
{
|
||||
name: "Test With Endpoint",
|
||||
opts: []GenericOption{
|
||||
WithEndpoint("someendpoint"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Endpoint URL",
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("http://someendpoint/somepath"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/somepath", c.Metrics.URLPath)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Secure Endpoint URL",
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("https://someendpoint/somepath"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "someendpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/somepath", c.Metrics.URLPath)
|
||||
assert.Equal(t, false, c.Metrics.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Invalid Endpoint URL",
|
||||
opts: []GenericOption{
|
||||
WithEndpointURL("%invalid"),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.Equal(t, "localhost:4317", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "localhost:4318", c.Metrics.Endpoint)
|
||||
}
|
||||
assert.Equal(t, "/v1/metrics", c.Metrics.URLPath)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env.endpoint/prefix",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.False(t, c.Metrics.Insecure)
|
||||
if grpcOption {
|
||||
assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint)
|
||||
} else {
|
||||
assert.Equal(t, "env.endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://overrode.by.signal.specific/env/var",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://env.metrics.endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.True(t, c.Metrics.Insecure)
|
||||
assert.Equal(t, "env.metrics.endpoint", c.Metrics.Endpoint)
|
||||
if !grpcOption {
|
||||
assert.Equal(t, "/", c.Metrics.URLPath)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Endpoint",
|
||||
opts: []GenericOption{
|
||||
WithEndpoint("metrics_endpoint"),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "metrics_endpoint", c.Metrics.Endpoint)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint with HTTP scheme",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint with HTTP scheme and leading & trailingspaces",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint with HTTPS scheme",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, false, c.Metrics.Insecure)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint with uppercase scheme",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "HTTPS://overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "HtTp://env_metrics_endpoint",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, "env_metrics_endpoint", c.Metrics.Endpoint)
|
||||
assert.Equal(t, true, c.Metrics.Insecure)
|
||||
},
|
||||
},
|
||||
|
||||
// Certificate tests
|
||||
{
|
||||
name: "Test Default Certificate",
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
assert.Nil(t, c.Metrics.TLSCfg)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Certificate",
|
||||
opts: []GenericOption{
|
||||
WithTLSClientConfig(tlsCert),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
// TODO: make sure gRPC's credentials actually works
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Certificate",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Certificate",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
"invalid_cert": []byte("invalid certificate file."),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Certificate",
|
||||
opts: []GenericOption{},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
if grpcOption {
|
||||
assert.NotNil(t, c.Metrics.GRPCCredentials)
|
||||
} else {
|
||||
// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
|
||||
assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects()))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Headers tests
|
||||
{
|
||||
name: "Test With Headers",
|
||||
opts: []GenericOption{
|
||||
WithHeaders(map[string]string{"h1": "v1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Headers",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_HEADERS": "h1=v1,h2=v2",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Headers",
|
||||
env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"},
|
||||
opts: []GenericOption{
|
||||
WithHeaders(map[string]string{"m1": "mv1"}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, map[string]string{"m1": "mv1"}, c.Metrics.Headers)
|
||||
},
|
||||
},
|
||||
|
||||
// Compression Tests
|
||||
{
|
||||
name: "Test With Compression",
|
||||
opts: []GenericOption{
|
||||
WithCompression(GzipCompression),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Compression",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Compression",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, GzipCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Compression",
|
||||
opts: []GenericOption{
|
||||
WithCompression(NoCompression),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, NoCompression, c.Metrics.Compression)
|
||||
},
|
||||
},
|
||||
|
||||
// Timeout Tests
|
||||
{
|
||||
name: "Test With Timeout",
|
||||
opts: []GenericOption{
|
||||
WithTimeout(time.Duration(5 * time.Second)),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, 5*time.Second, c.Metrics.Timeout)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Timeout",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 15*time.Second)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Timeout",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000",
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 28*time.Second)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Timeout",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000",
|
||||
},
|
||||
opts: []GenericOption{
|
||||
WithTimeout(5 * time.Second),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Equal(t, c.Metrics.Timeout, 5*time.Second)
|
||||
},
|
||||
},
|
||||
|
||||
// Temporality Selector Tests
|
||||
{
|
||||
name: "WithTemporalitySelector",
|
||||
opts: []GenericOption{
|
||||
WithTemporalitySelector(deltaSelector),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
// Function value comparisons are disallowed, test non-default
|
||||
// behavior of a TemporalitySelector here to ensure our "catch
|
||||
// all" was set.
|
||||
var undefinedKind metric.InstrumentKind
|
||||
got := c.Metrics.TemporalitySelector
|
||||
assert.Equal(t, metricdata.DeltaTemporality, got(undefinedKind))
|
||||
},
|
||||
},
|
||||
|
||||
// Aggregation Selector Tests
|
||||
{
|
||||
name: "WithAggregationSelector",
|
||||
opts: []GenericOption{
|
||||
WithAggregationSelector(dropSelector),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
// Function value comparisons are disallowed, test non-default
|
||||
// behavior of a AggregationSelector here to ensure our "catch
|
||||
// all" was set.
|
||||
var undefinedKind metric.InstrumentKind
|
||||
got := c.Metrics.AggregationSelector
|
||||
assert.Equal(t, metric.AggregationDrop{}, got(undefinedKind))
|
||||
},
|
||||
},
|
||||
|
||||
// Proxy Tests
|
||||
{
|
||||
name: "Test With Proxy",
|
||||
opts: []GenericOption{
|
||||
WithProxy(func(r *http.Request) (*url.URL, error) {
|
||||
return url.Parse("http://proxy.com")
|
||||
}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.NotNil(t, c.Metrics.Proxy)
|
||||
proxyURL, err := c.Metrics.Proxy(&http.Request{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "http://proxy.com", proxyURL.String())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Without Proxy",
|
||||
opts: []GenericOption{},
|
||||
asserts: func(t *testing.T, c *Config, grpcOption bool) {
|
||||
assert.Nil(t, c.Metrics.Proxy)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
origEOR := DefaultEnvOptionsReader
|
||||
DefaultEnvOptionsReader = envconfig.EnvOptionsReader{
|
||||
GetEnv: tt.env.getEnv,
|
||||
ReadFile: tt.fileReader.readFile,
|
||||
Namespace: "OTEL_EXPORTER_OTLP",
|
||||
}
|
||||
t.Cleanup(func() { DefaultEnvOptionsReader = origEOR })
|
||||
|
||||
// Tests Generic options as HTTP Options
|
||||
cfg := NewHTTPConfig(asHTTPOptions(tt.opts)...)
|
||||
tt.asserts(t, &cfg, false)
|
||||
|
||||
// Tests Generic options as gRPC Options
|
||||
cfg = NewGRPCConfig(asGRPCOptions(tt.opts)...)
|
||||
tt.asserts(t, &cfg, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func dropSelector(metric.InstrumentKind) metric.Aggregation {
|
||||
return metric.AggregationDrop{}
|
||||
}
|
||||
|
||||
func deltaSelector(metric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.DeltaTemporality
|
||||
}
|
||||
|
||||
func asHTTPOptions(opts []GenericOption) []HTTPOption {
|
||||
converted := make([]HTTPOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = NewHTTPOption(o.ApplyHTTPOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func asGRPCOptions(opts []GenericOption) []GRPCOption {
|
||||
converted := make([]GRPCOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = NewGRPCOption(o.ApplyGRPCOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
type args struct {
|
||||
urlPath string
|
||||
defaultPath string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "clean empty path",
|
||||
args: args{
|
||||
urlPath: "",
|
||||
defaultPath: "DefaultPath",
|
||||
},
|
||||
want: "DefaultPath",
|
||||
},
|
||||
{
|
||||
name: "clean metrics path",
|
||||
args: args{
|
||||
urlPath: "/prefix/v1/metrics",
|
||||
defaultPath: "DefaultMetricsPath",
|
||||
},
|
||||
want: "/prefix/v1/metrics",
|
||||
},
|
||||
{
|
||||
name: "clean traces path",
|
||||
args: args{
|
||||
urlPath: "https://env_endpoint",
|
||||
defaultPath: "DefaultTracesPath",
|
||||
},
|
||||
want: "/https:/env_endpoint",
|
||||
},
|
||||
{
|
||||
name: "spaces trimmed",
|
||||
args: args{
|
||||
urlPath: " /dir",
|
||||
},
|
||||
want: "/dir",
|
||||
},
|
||||
{
|
||||
name: "clean path empty",
|
||||
args: args{
|
||||
urlPath: "dir/..",
|
||||
defaultPath: "DefaultTracesPath",
|
||||
},
|
||||
want: "DefaultTracesPath",
|
||||
},
|
||||
{
|
||||
name: "make absolute",
|
||||
args: args{
|
||||
urlPath: "dir/a",
|
||||
},
|
||||
want: "/dir/a",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := cleanPath(tt.args.urlPath, tt.args.defaultPath); got != tt.want {
|
||||
t.Errorf("CleanPath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -19,10 +19,10 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
collpb "{{ .protoImportPrefix }}/otlp/collector/metrics/v1"
|
||||
cpb "{{ .protoImportPrefix }}/otlp/common/v1"
|
||||
mpb "{{ .protoImportPrefix }}/otlp/metrics/v1"
|
||||
rpb "{{ .protoImportPrefix }}/otlp/resource/v1"
|
||||
collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -14,8 +14,8 @@ import (
|
||||
"{{ .internalImportPath }}"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
cpb "{{ .protoImportPrefix }}/otlp/collector/metrics/v1"
|
||||
mpb "{{ .protoImportPrefix }}/otlp/metrics/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
|
451
internal/shared/otlp/otlpmetric/otest/collector.go.tmpl
Normal file
451
internal/shared/otlp/otlpmetric/otest/collector.go.tmpl
Normal file
@ -0,0 +1,451 @@
|
||||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlpmetric/otest/collector.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix" // nolint:depguard // This is for testing.
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"{{ .oconfImportPath }}"
|
||||
collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
// Collector is the collection target a Client sends metric uploads to.
|
||||
type Collector interface {
|
||||
Collect() *Storage
|
||||
}
|
||||
|
||||
type ExportResult struct {
|
||||
Response *collpb.ExportMetricsServiceResponse
|
||||
Err error
|
||||
}
|
||||
|
||||
// Storage stores uploaded OTLP metric data in their proto form.
|
||||
type Storage struct {
|
||||
dataMu sync.Mutex
|
||||
data []*mpb.ResourceMetrics
|
||||
}
|
||||
|
||||
// NewStorage returns a configure storage ready to store received requests.
|
||||
func NewStorage() *Storage {
|
||||
return &Storage{}
|
||||
}
|
||||
|
||||
// Add adds the request to the Storage.
|
||||
func (s *Storage) Add(request *collpb.ExportMetricsServiceRequest) {
|
||||
s.dataMu.Lock()
|
||||
defer s.dataMu.Unlock()
|
||||
s.data = append(s.data, request.ResourceMetrics...)
|
||||
}
|
||||
|
||||
// Dump returns all added ResourceMetrics and clears the storage.
|
||||
func (s *Storage) Dump() []*mpb.ResourceMetrics {
|
||||
s.dataMu.Lock()
|
||||
defer s.dataMu.Unlock()
|
||||
|
||||
var data []*mpb.ResourceMetrics
|
||||
data, s.data = s.data, []*mpb.ResourceMetrics{}
|
||||
return data
|
||||
}
|
||||
|
||||
// GRPCCollector is an OTLP gRPC server that collects all requests it receives.
|
||||
type GRPCCollector struct {
|
||||
collpb.UnimplementedMetricsServiceServer
|
||||
|
||||
headersMu sync.Mutex
|
||||
headers metadata.MD
|
||||
storage *Storage
|
||||
|
||||
resultCh <-chan ExportResult
|
||||
listener net.Listener
|
||||
srv *grpc.Server
|
||||
}
|
||||
|
||||
// NewGRPCCollector returns a *GRPCCollector that is listening at the provided
|
||||
// endpoint.
|
||||
//
|
||||
// If endpoint is an empty string, the returned collector will be listening on
|
||||
// the localhost interface at an OS chosen port.
|
||||
//
|
||||
// If errCh is not nil, the collector will respond to Export calls with errors
|
||||
// sent on that channel. This means that if errCh is not nil Export calls will
|
||||
// block until an error is received.
|
||||
func NewGRPCCollector(endpoint string, resultCh <-chan ExportResult) (*GRPCCollector, error) {
|
||||
if endpoint == "" {
|
||||
endpoint = "localhost:0"
|
||||
}
|
||||
|
||||
c := &GRPCCollector{
|
||||
storage: NewStorage(),
|
||||
resultCh: resultCh,
|
||||
}
|
||||
|
||||
var err error
|
||||
c.listener, err = net.Listen("tcp", endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.srv = grpc.NewServer()
|
||||
collpb.RegisterMetricsServiceServer(c.srv, c)
|
||||
go func() { _ = c.srv.Serve(c.listener) }()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Shutdown shuts down the gRPC server closing all open connections and
|
||||
// listeners immediately.
|
||||
func (c *GRPCCollector) Shutdown() { c.srv.Stop() }
|
||||
|
||||
// Addr returns the net.Addr c is listening at.
|
||||
func (c *GRPCCollector) Addr() net.Addr {
|
||||
return c.listener.Addr()
|
||||
}
|
||||
|
||||
// Collect returns the Storage holding all collected requests.
|
||||
func (c *GRPCCollector) Collect() *Storage {
|
||||
return c.storage
|
||||
}
|
||||
|
||||
// Headers returns the headers received for all requests.
|
||||
func (c *GRPCCollector) Headers() map[string][]string {
|
||||
// Makes a copy.
|
||||
c.headersMu.Lock()
|
||||
defer c.headersMu.Unlock()
|
||||
return metadata.Join(c.headers)
|
||||
}
|
||||
|
||||
// Export handles the export req.
|
||||
func (c *GRPCCollector) Export(ctx context.Context, req *collpb.ExportMetricsServiceRequest) (*collpb.ExportMetricsServiceResponse, error) {
|
||||
c.storage.Add(req)
|
||||
|
||||
if h, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
c.headersMu.Lock()
|
||||
c.headers = metadata.Join(c.headers, h)
|
||||
c.headersMu.Unlock()
|
||||
}
|
||||
|
||||
if c.resultCh != nil {
|
||||
r := <-c.resultCh
|
||||
if r.Response == nil {
|
||||
return &collpb.ExportMetricsServiceResponse{}, r.Err
|
||||
}
|
||||
return r.Response, r.Err
|
||||
}
|
||||
return &collpb.ExportMetricsServiceResponse{}, nil
|
||||
}
|
||||
|
||||
var emptyExportMetricsServiceResponse = func() []byte {
|
||||
body := collpb.ExportMetricsServiceResponse{}
|
||||
r, err := proto.Marshal(&body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}()
|
||||
|
||||
type HTTPResponseError struct {
|
||||
Err error
|
||||
Status int
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (e *HTTPResponseError) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.Status, e.Err)
|
||||
}
|
||||
|
||||
func (e *HTTPResponseError) Unwrap() error { return e.Err }
|
||||
|
||||
// HTTPCollector is an OTLP HTTP server that collects all requests it receives.
|
||||
type HTTPCollector struct {
|
||||
plainTextResponse bool
|
||||
|
||||
headersMu sync.Mutex
|
||||
headers http.Header
|
||||
storage *Storage
|
||||
|
||||
resultCh <-chan ExportResult
|
||||
listener net.Listener
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
// NewHTTPCollector returns a *HTTPCollector that is listening at the provided
|
||||
// endpoint.
|
||||
//
|
||||
// If endpoint is an empty string, the returned collector will be listening on
|
||||
// the localhost interface at an OS chosen port, not use TLS, and listen at the
|
||||
// default OTLP metric endpoint path ("/v1/metrics"). If the endpoint contains
|
||||
// a prefix of "https" the server will generate weak self-signed TLS
|
||||
// certificates and use them to server data. If the endpoint contains a path,
|
||||
// that path will be used instead of the default OTLP metric endpoint path.
|
||||
//
|
||||
// If errCh is not nil, the collector will respond to HTTP requests with errors
|
||||
// sent on that channel. This means that if errCh is not nil Export calls will
|
||||
// block until an error is received.
|
||||
func NewHTTPCollector(endpoint string, resultCh <-chan ExportResult, opts ...func(*HTTPCollector)) (*HTTPCollector, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Host == "" {
|
||||
u.Host = "localhost:0"
|
||||
}
|
||||
if u.Path == "" {
|
||||
u.Path = oconf.DefaultMetricsPath
|
||||
}
|
||||
|
||||
c := &HTTPCollector{
|
||||
headers: http.Header{},
|
||||
storage: NewStorage(),
|
||||
resultCh: resultCh,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
c.listener, err = net.Listen("tcp", u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(u.Path, http.HandlerFunc(c.handler))
|
||||
c.srv = &http.Server{
|
||||
Handler: mux,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
if u.Scheme == "https" {
|
||||
cert, err := weakCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.srv.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
go func() { _ = c.srv.ServeTLS(c.listener, "", "") }()
|
||||
} else {
|
||||
go func() { _ = c.srv.Serve(c.listener) }()
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// WithHTTPCollectorRespondingPlainText makes the HTTPCollector return
|
||||
// a plaintext, instead of protobuf, response.
|
||||
func WithHTTPCollectorRespondingPlainText() func(*HTTPCollector) {
|
||||
return func(s *HTTPCollector) {
|
||||
s.plainTextResponse = true
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown shuts down the HTTP server closing all open connections and
|
||||
// listeners.
|
||||
func (c *HTTPCollector) Shutdown(ctx context.Context) error {
|
||||
return c.srv.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// Addr returns the net.Addr c is listening at.
|
||||
func (c *HTTPCollector) Addr() net.Addr {
|
||||
return c.listener.Addr()
|
||||
}
|
||||
|
||||
// Collect returns the Storage holding all collected requests.
|
||||
func (c *HTTPCollector) Collect() *Storage {
|
||||
return c.storage
|
||||
}
|
||||
|
||||
// Headers returns the headers received for all requests.
|
||||
func (c *HTTPCollector) Headers() map[string][]string {
|
||||
// Makes a copy.
|
||||
c.headersMu.Lock()
|
||||
defer c.headersMu.Unlock()
|
||||
return c.headers.Clone()
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) handler(w http.ResponseWriter, r *http.Request) {
|
||||
c.respond(w, c.record(r))
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) record(r *http.Request) ExportResult {
|
||||
// Currently only supports protobuf.
|
||||
if v := r.Header.Get("Content-Type"); v != "application/x-protobuf" {
|
||||
err := fmt.Errorf("content-type not supported: %s", v)
|
||||
return ExportResult{Err: err}
|
||||
}
|
||||
|
||||
body, err := c.readBody(r)
|
||||
if err != nil {
|
||||
return ExportResult{Err: err}
|
||||
}
|
||||
pbRequest := &collpb.ExportMetricsServiceRequest{}
|
||||
err = proto.Unmarshal(body, pbRequest)
|
||||
if err != nil {
|
||||
return ExportResult{
|
||||
Err: &HTTPResponseError{
|
||||
Err: err,
|
||||
Status: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
}
|
||||
c.storage.Add(pbRequest)
|
||||
|
||||
c.headersMu.Lock()
|
||||
for k, vals := range r.Header {
|
||||
for _, v := range vals {
|
||||
c.headers.Add(k, v)
|
||||
}
|
||||
}
|
||||
c.headersMu.Unlock()
|
||||
|
||||
if c.resultCh != nil {
|
||||
return <-c.resultCh
|
||||
}
|
||||
return ExportResult{Err: err}
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) readBody(r *http.Request) (body []byte, err error) {
|
||||
var reader io.ReadCloser
|
||||
switch r.Header.Get("Content-Encoding") {
|
||||
case "gzip":
|
||||
reader, err = gzip.NewReader(r.Body)
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
return nil, &HTTPResponseError{
|
||||
Err: err,
|
||||
Status: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
default:
|
||||
reader = r.Body
|
||||
}
|
||||
|
||||
defer func() {
|
||||
cErr := reader.Close()
|
||||
if err == nil && cErr != nil {
|
||||
err = &HTTPResponseError{
|
||||
Err: cErr,
|
||||
Status: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
}()
|
||||
body, err = io.ReadAll(reader)
|
||||
if err != nil {
|
||||
err = &HTTPResponseError{
|
||||
Err: err,
|
||||
Status: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
func (c *HTTPCollector) respond(w http.ResponseWriter, resp ExportResult) {
|
||||
if resp.Err != nil {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
var e *HTTPResponseError
|
||||
if errors.As(resp.Err, &e) {
|
||||
for k, vals := range e.Header {
|
||||
for _, v := range vals {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(e.Status)
|
||||
fmt.Fprintln(w, e.Error())
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintln(w, resp.Err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if c.plainTextResponse {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-protobuf")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if resp.Response == nil {
|
||||
_, _ = w.Write(emptyExportMetricsServiceResponse)
|
||||
} else {
|
||||
r, err := proto.Marshal(resp.Response)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, _ = w.Write(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://golang.org/src/crypto/tls/generate_cert.go,
|
||||
// simplified and weakened.
|
||||
func weakCertificate() (tls.Certificate, error) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Hour)
|
||||
max := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: sn,
|
||||
Subject: pkix.Name{Organization: []string{"otel-go"}},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{net.IPv6loopback, net.IPv4(127, 0, 0, 1)},
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
var certBuf bytes.Buffer
|
||||
err = pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
var privBuf bytes.Buffer
|
||||
err = pem.Encode(&privBuf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.X509KeyPair(certBuf.Bytes(), privBuf.Bytes())
|
||||
}
|
@ -8,7 +8,7 @@ package transform
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "{{ .protoImportPrefix }}/otlp/common/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
// AttrIter transforms an attribute iterator into OTLP key-values.
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
cpb "{{ .protoImportPrefix }}/otlp/common/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
mpb "{{ .protoImportPrefix }}/otlp/metrics/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -13,9 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
cpb "{{ .protoImportPrefix }}/otlp/common/v1"
|
||||
mpb "{{ .protoImportPrefix }}/otlp/metrics/v1"
|
||||
rpb "{{ .protoImportPrefix }}/otlp/resource/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
)
|
||||
|
||||
// ResourceMetrics returns an OTLP ResourceMetrics generated from rm. If rm
|
||||
|
@ -18,9 +18,9 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
cpb "{{ .protoImportPrefix }}/otlp/common/v1"
|
||||
mpb "{{ .protoImportPrefix }}/otlp/metrics/v1"
|
||||
rpb "{{ .protoImportPrefix }}/otlp/resource/v1"
|
||||
cpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
)
|
||||
|
||||
type unknownAggT struct {
|
||||
|
Loading…
Reference in New Issue
Block a user