trace proxie middlewares (#6313)

* trace proxie middlewares

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* Update ocis-pkg/service/grpc/client.go

Co-authored-by: Christian Richter <1058116+dragonchaser@users.noreply.github.com>

* default tls is off

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Co-authored-by: Christian Richter <1058116+dragonchaser@users.noreply.github.com>
This commit is contained in:
Jörn Friedrich Dreyer
2023-05-27 10:18:24 +02:00
committed by GitHub
parent 12dff34442
commit 632b206675
95 changed files with 2409 additions and 3195 deletions
@@ -0,0 +1,5 @@
Bugfix: trace proxy middlewares
We moved trace initialization to an early middleware to also trace requests made by other proxy middlewares.
https://github.com/owncloud/ocis/pull/6313
+11 -10
View File
@@ -37,7 +37,7 @@ require (
github.com/go-micro/plugins/v4/server/http v1.2.1
github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker v1.2.0
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
github.com/go-micro/plugins/v4/wrapper/trace/opencensus v1.1.0
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
@@ -80,12 +80,12 @@ require (
github.com/xhit/go-simple-mail/v2 v2.13.0
go-micro.dev/v4 v4.9.0
go.etcd.io/bbolt v1.3.7
go.opencensus.io v0.24.0
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
go.opentelemetry.io/otel/sdk v1.14.0
go.opentelemetry.io/otel/trace v1.14.0
go.opentelemetry.io/contrib/zpages v0.41.1
go.opentelemetry.io/otel v1.15.1
go.opentelemetry.io/otel/exporters/jaeger v1.15.1
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1
go.opentelemetry.io/otel/sdk v1.15.1
go.opentelemetry.io/otel/trace v1.15.1
golang.org/x/crypto v0.9.0
golang.org/x/exp v0.0.0-20221026004748-78e5e7837ae6
golang.org/x/image v0.6.0
@@ -146,7 +146,7 @@ require (
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f // indirect
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/ceph/go-ceph v0.18.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect
@@ -306,9 +306,10 @@ require (
go.etcd.io/etcd/api/v3 v3.5.7 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
go.etcd.io/etcd/client/v3 v3.5.7 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
+20 -18
View File
@@ -568,8 +568,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -803,8 +803,8 @@ github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker v1.2.0 h1:EQj4l7fuOSz8u
github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker v1.2.0/go.mod h1:JR9Ox/iJIrcXm8nCWdAEBsyG7Q7lyMLzsTZPfXrqvwo=
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0 h1:UWBUYtMXCxQ9bIGOYcbLOjtPv8ovvCRjWWM6tHhB4S8=
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0/go.mod h1:8BYxs/wEE4ZJayHZQffw4A8s9rcPumyoNms0hYoNocM=
github.com/go-micro/plugins/v4/wrapper/trace/opencensus v1.1.0 h1:ITm1vEP8BPEccWFAu6/tMFHrxHfwYzE4GdkCy6PlF6A=
github.com/go-micro/plugins/v4/wrapper/trace/opencensus v1.1.0/go.mod h1:4izlDcwSo9tu8v2TcaBgpO3EmNqUkB4oMFSwxvSt438=
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0 h1:e2hgtWMNqJ3DmbMt9ZxzmH/BkVAw9Xg23l6CHrXQfKw=
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0/go.mod h1:BBqL7ckGNb7rFfk3vU2Yj/CILVsz/WF19CkAyveQl8A=
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
@@ -1675,20 +1675,22 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 h1:PRXhsszxTt5bbPriTjmaweWUsAnJYeWBhUMLRetUgBU=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4/go.mod h1:05eWWy6ZWzmpeImD3UowLTB3VjDMU1yxQ+ENuVWDM3c=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
go.opentelemetry.io/otel/exporters/jaeger v1.14.0 h1:CjbUNd4iN2hHmWekmOqZ+zSCU+dzZppG8XsV+A3oc8Q=
go.opentelemetry.io/otel/exporters/jaeger v1.14.0/go.mod h1:4Ay9kk5vELRrbg5z4cpP9EtmQRFap2Wb0woPG4lujZA=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.opentelemetry.io/contrib/zpages v0.41.1 h1:FReY8OWFNtYm4mWleTRxTUyD3r02uGcwS6ZeElahs00=
go.opentelemetry.io/contrib/zpages v0.41.1/go.mod h1:C3iy146ccMyv1+gEaxVDDHuoT7yXAKKmbg+twudDpeg=
go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8=
go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc=
go.opentelemetry.io/otel/exporters/jaeger v1.15.1 h1:x3SLvwli0OyAJapNcOIzf1xXBRBA+HD3elrMQmFfmXo=
go.opentelemetry.io/otel/exporters/jaeger v1.15.1/go.mod h1:0Ck9b5oLL/bFZvfAEEqtrb1U0jZXjm5fWXMCOCG3vvM=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 h1:XYDQtNzdb2T4uM1pku2m76eSMDJgqhJ+6KzkqgQBALc=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1/go.mod h1:uOTV75+LOzV+ODmL8ahRLWkFA3eQcSC2aAsbxIu4duk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 h1:tyoeaUh8REKay72DVYsSEBYV18+fGONe+YYPaOxgLoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1/go.mod h1:HUSnrjQQ19KX9ECjpQxufsF+3ioD3zISPMlauTPZu2g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 h1:pIfoG5IAZFzp9EUlJzdSkpUwpaUAAnD+Ru1nBLTACIQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1/go.mod h1:poNKBqF5+nR/6ke2oGTDjHfksrsHDOHXAl2g4+9ONsY=
go.opentelemetry.io/otel/sdk v1.15.1 h1:5FKR+skgpzvhPQHIEfcwMYjCBr14LWzs3uSqKiQzETI=
go.opentelemetry.io/otel/sdk v1.15.1/go.mod h1:8rVtxQfrbmbHKfqzpQkT5EzZMcbMBwTzNAggbEAM0KA=
go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY=
go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+3 -2
View File
@@ -10,7 +10,7 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
graphMiddleware "github.com/owncloud/ocis/v2/services/graph/pkg/middleware"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opencensus.io/zpages"
"go.opentelemetry.io/contrib/zpages"
)
// NewService initializes a new debug service.
@@ -42,7 +42,8 @@ func NewService(opts ...Option) *http.Server {
}
if dopts.Zpages {
zpages.Handle(mux, "/debug")
h := zpages.NewTracezHandler(zpages.NewSpanProcessor())
mux.Handle("/debug", h)
}
return &http.Server{
+59 -2
View File
@@ -9,9 +9,11 @@ import (
mgrpcc "github.com/go-micro/plugins/v4/client/grpc"
mbreaker "github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker"
mtracer "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
"go-micro.dev/v4/client"
"go.opentelemetry.io/otel/trace"
)
var (
@@ -23,6 +25,7 @@ var (
type ClientOptions struct {
tlsMode string
caCert string
tp trace.TracerProvider
}
// Option is used to pass client options
@@ -42,13 +45,19 @@ func WithTLSCACert(v string) ClientOption {
}
}
// WithTraceProvider allows to set the trace Provider for grpc clients
func WithTraceProvider(tp trace.TracerProvider) ClientOption {
return func(o *ClientOptions) {
o.tp = tp
}
}
// Configure configures the default oOCIS grpc client (e.g. TLS settings)
func Configure(opts ...ClientOption) error {
var options ClientOptions
for _, opt := range opts {
opt(&options)
}
var outerr error
once.Do(func() {
reg := registry.GetRegistry()
@@ -56,6 +65,9 @@ func Configure(opts ...ClientOption) error {
cOpts := []client.Option{
client.Registry(reg),
client.Wrap(mbreaker.NewClientWrapper()),
client.Wrap(mtracer.NewClientWrapper(
mtracer.WithTraceProvider(options.tp),
)),
}
switch options.tlsMode {
case "insecure":
@@ -74,12 +86,14 @@ func Configure(opts ...ClientOption) error {
return
}
if !certs.AppendCertsFromPEM(pemData) {
outerr = errors.New("Error initializing LDAP Backend. Adding CA cert failed")
outerr = errors.New("could not initialize default client, adding CA cert failed")
return
}
tlsConfig.RootCAs = certs
}
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
//case "off":
//default:
}
defaultClient = mgrpcc.NewClient(cOpts...)
@@ -99,3 +113,46 @@ func GetClientOptions(t *shared.GRPCClientTLS) []ClientOption {
}
return opts
}
func NewClient(opts ...ClientOption) (client.Client, error) {
var options ClientOptions
for _, opt := range opts {
opt(&options)
}
reg := registry.GetRegistry()
var tlsConfig *tls.Config
cOpts := []client.Option{
client.Registry(reg),
client.Wrap(mbreaker.NewClientWrapper()),
client.Wrap(mtracer.NewClientWrapper(
mtracer.WithTraceProvider(options.tp),
)),
}
switch options.tlsMode {
case "insecure":
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
case "on":
tlsConfig = &tls.Config{}
// Note: If caCert is empty we use the system's default set of trusted CAs
if options.caCert != "" {
certs := x509.NewCertPool()
pemData, err := os.ReadFile(options.caCert)
if err != nil {
return nil, err
}
if !certs.AppendCertsFromPEM(pemData) {
return nil, errors.New("could not initialize client, adding CA cert failed")
}
tlsConfig.RootCAs = certs
}
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
//case "off":
//default:
}
return mgrpcc.NewClient(cOpts...), nil
}
+4 -4
View File
@@ -8,7 +8,7 @@ import (
mgrpcs "github.com/go-micro/plugins/v4/server/grpc"
"github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus"
"github.com/go-micro/plugins/v4/wrapper/trace/opencensus"
mtracer "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
ociscrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"go-micro.dev/v4"
@@ -62,9 +62,9 @@ func NewService(opts ...Option) (Service, error) {
micro.RegisterTTL(time.Second * 30),
micro.RegisterInterval(time.Second * 10),
micro.WrapHandler(prometheus.NewHandlerWrapper()),
micro.WrapClient(opencensus.NewClientWrapper()),
micro.WrapHandler(opencensus.NewHandlerWrapper()),
micro.WrapSubscriber(opencensus.NewSubscriberWrapper()),
micro.WrapClient(mtracer.NewClientWrapper()),
micro.WrapHandler(mtracer.NewHandlerWrapper()),
micro.WrapSubscriber(mtracer.NewSubscriberWrapper()),
}
return Service{micro.NewService(mopts...)}, nil
+8 -4
View File
@@ -18,7 +18,6 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/keycloak"
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/v2/ocis-pkg/service/http"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
@@ -26,6 +25,7 @@ import (
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
graphMiddleware "github.com/owncloud/ocis/v2/services/graph/pkg/middleware"
svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
"github.com/owncloud/ocis/v2/services/graph/pkg/tracing"
"github.com/pkg/errors"
"go-micro.dev/v4"
"go-micro.dev/v4/events"
@@ -115,13 +115,17 @@ func Server(opts ...Option) (http.Service, error) {
var requireAdminMiddleware func(stdhttp.Handler) stdhttp.Handler
var roleService svc.RoleService
var gatewayClient gateway.GatewayAPIClient
grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(options.Config.GRPCClientTLS), grpc.WithTraceProvider(tracing.TraceProvider))...)
if err != nil {
return http.Service{}, err
}
if options.Config.HTTP.APIToken == "" {
middlewares = append(middlewares,
graphMiddleware.Auth(
account.Logger(options.Logger),
account.JWTSecret(options.Config.TokenManager.JWTSecret),
))
roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient())
roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpcClient)
gatewayClient, err = pool.GetGatewayServiceClient(options.Config.Reva.Address, options.Config.Reva.GetRevaOptions()...)
if err != nil {
return http.Service{}, errors.Wrap(err, "could not initialize gateway client")
@@ -145,7 +149,7 @@ func Server(opts ...Option) (http.Service, error) {
keyCloakClient = keycloak.New(kcc.BasePath, kcc.ClientID, kcc.ClientSecret, kcc.ClientRealm, kcc.InsecureSkipVerify)
}
hClient := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", ogrpc.DefaultClient())
hClient := ehsvc.NewEventHistoryService("com.owncloud.api.eventhistory", grpcClient)
var handle svc.Service
handle, err = svc.NewService(
@@ -156,7 +160,7 @@ func Server(opts ...Option) (http.Service, error) {
svc.WithRoleService(roleService),
svc.WithRequireAdminMiddleware(requireAdminMiddleware),
svc.WithGatewayClient(gatewayClient),
svc.WithSearchService(searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient())),
svc.WithSearchService(searchsvc.NewSearchProviderService("com.owncloud.api.search", grpcClient)),
svc.KeycloakClient(keyCloakClient),
svc.EventHistoryClient(hClient),
)
+8 -3
View File
@@ -31,6 +31,7 @@ import (
v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
gtracing "github.com/owncloud/ocis/v2/services/graph/pkg/tracing"
settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
"github.com/pkg/errors"
merrors "go-micro.dev/v4/errors"
@@ -582,13 +583,17 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces
func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*storageprovider.ListStorageSpacesRequest_Filter, unrestricted bool) (*storageprovider.ListStorageSpacesResponse, error) {
client := g.GetGatewayClient()
permissions := make(map[string]struct{}, 1)
s := settingssvc.NewPermissionService("com.owncloud.api.settings", grpc.DefaultClient())
grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(g.config.GRPCClientTLS), grpc.WithTraceProvider(gtracing.TraceProvider))...)
if err != nil {
return nil, err
}
s := settingssvc.NewPermissionService("com.owncloud.api.settings", grpcClient)
_, err := s.GetPermissionByID(ctx, &settingssvc.GetPermissionByIDRequest{
_, err = s.GetPermissionByID(ctx, &settingssvc.GetPermissionByIDRequest{
PermissionId: settingsServiceExt.ListAllSpacesPermissionID,
})
permissions := make(map[string]struct{}, 1)
// No error means the user has the permission
if err == nil {
permissions[settingsServiceExt.ListAllSpacesPermissionName] = struct{}{}
@@ -18,6 +18,7 @@ import (
. "github.com/onsi/gomega"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
"github.com/owncloud/ocis/v2/services/graph/mocks"
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
@@ -44,6 +45,7 @@ var _ = Describe("Users changing their own password", func() {
ctx = context.Background()
cfg = defaults.FullDefaultConfig()
cfg.TokenManager.JWTSecret = "loremipsum"
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
gatewayClient = &cs3mocks.GatewayAPIClient{}
ldapClient = mockedLDAPClient()
+6 -1
View File
@@ -23,6 +23,7 @@ import (
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
"github.com/owncloud/ocis/v2/services/graph/pkg/identity/ldap"
graphm "github.com/owncloud/ocis/v2/services/graph/pkg/middleware"
gtracing "github.com/owncloud/ocis/v2/services/graph/pkg/tracing"
microstore "go-micro.dev/v4/store"
)
@@ -155,7 +156,11 @@ func NewService(opts ...Option) (Graph, error) {
}
if options.PermissionService == nil {
svc.permissionsService = settingssvc.NewPermissionService("com.owncloud.api.settings", grpc.DefaultClient())
grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(options.Config.GRPCClientTLS), grpc.WithTraceProvider(gtracing.TraceProvider))...)
if err != nil {
return svc, err
}
svc.permissionsService = settingssvc.NewPermissionService("com.owncloud.api.settings", grpcClient)
} else {
svc.permissionsService = options.PermissionService
}
+7 -2
View File
@@ -271,7 +271,11 @@ func (h *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
}
func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userInfoCache microstore.Store) alice.Chain {
rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient())
grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(cfg.GRPCClientTLS), grpc.WithTraceProvider(tracing.TraceProvider))...)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to get gateway client")
}
rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpcClient)
revaClient, err := pool.GetGatewayServiceClient(cfg.Reva.Address, cfg.Reva.GetRevaOptions()...)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to get gateway client")
@@ -318,7 +322,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config,
logger.Fatal().Msgf("Invalid role assignment driver '%s'", cfg.RoleAssignment.Driver)
}
storeClient := storesvc.NewStoreService("com.owncloud.api.store", grpc.DefaultClient())
storeClient := storesvc.NewStoreService("com.owncloud.api.store", grpcClient)
if err != nil {
logger.Error().Err(err).
Str("gateway", cfg.Reva.Address).
@@ -375,6 +379,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config,
return alice.New(
// first make sure we log all requests and redirect to https if necessary
middleware.Tracer(),
pkgmiddleware.TraceContext,
chimiddleware.RealIP,
chimiddleware.RequestID,
+50
View File
@@ -0,0 +1,50 @@
package middleware
import (
"fmt"
"net/http"
chimiddleware "github.com/go-chi/chi/v5/middleware"
pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing"
proxytracing "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
// Tracer provides a middleware to start traces
func Tracer() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return &tracer{
next: next,
}
}
}
type tracer struct {
next http.Handler
}
func (m tracer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
span trace.Span
)
tracer := proxytracing.TraceProvider.Tracer("proxy")
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindServer),
}
ctx, span = tracer.Start(ctx, fmt.Sprintf("%s %v", r.Method, r.URL.Path), spanOpts...)
defer span.End()
span.SetAttributes(
attribute.KeyValue{
Key: "x-request-id",
Value: attribute.StringValue(chimiddleware.GetReqID(r.Context())),
})
pkgtrace.Propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
m.next.ServeHTTP(w, r.WithContext(ctx))
}
+1 -30
View File
@@ -4,25 +4,16 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
"net/http/httputil"
"os"
"time"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"go.opentelemetry.io/otel/attribute"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing"
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
"github.com/owncloud/ocis/v2/services/proxy/pkg/proxy/policy"
"github.com/owncloud/ocis/v2/services/proxy/pkg/router"
proxytracing "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
// MultiHostReverseProxy extends "httputil" to support multiple hosts with different policies
@@ -84,25 +75,5 @@ func NewMultiHostReverseProxy(opts ...Option) (*MultiHostReverseProxy, error) {
}
func (p *MultiHostReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
span trace.Span
)
tracer := proxytracing.TraceProvider.Tracer("proxy")
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindServer),
}
ctx, span = tracer.Start(ctx, fmt.Sprintf("%s %v", r.Method, r.URL.Path), spanOpts...)
defer span.End()
span.SetAttributes(
attribute.KeyValue{
Key: "x-request-id",
Value: attribute.StringValue(chimiddleware.GetReqID(r.Context())),
})
pkgtrace.Propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
p.ReverseProxy.ServeHTTP(w, r.WithContext(ctx))
p.ReverseProxy.ServeHTTP(w, r)
}
@@ -1,31 +0,0 @@
# OpenCensus wrappers
OpenCensus wrappers propagate traces (spans) accross services.
## Usage
```go
service := micro.NewService(
micro.Name("go.micro.srv.greeter"),
micro.WrapClient(opencensus.NewClientWrapper()),
micro.WrapHandler(opencensus.NewHandlerWrapper()),
micro.WrapSubscriber(opencensus.NewSubscriberWrapper()),
)
```
### Views
The OpenCensus package exposes some convenience views.
Don't forget to register these views:
```go
// Register to all RPC server views.
if err := view.Register(opencensus.DefaultServerViews...); err != nil {
log.Fatal(err)
}
// Register to all RPC client views.
if err := view.Register(opencensus.DefaultClientViews...); err != nil {
log.Fatal(err)
}
```
@@ -1,150 +0,0 @@
// Package opencensus provides wrappers for OpenCensus tracing.
package opencensus
import (
"context"
"encoding/base64"
"fmt"
"go-micro.dev/v4/client"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/metadata"
"go-micro.dev/v4/server"
"go.opencensus.io/trace"
"go.opencensus.io/trace/propagation"
)
const (
// TracePropagationField is the key for the tracing context
// that will be injected in go-micro's metadata.
TracePropagationField = "X-Trace-Context"
)
// clientWrapper wraps an RPC client and adds tracing.
type clientWrapper struct {
client.Client
}
func injectTraceIntoCtx(ctx context.Context, span *trace.Span) context.Context {
spanCtx := propagation.Binary(span.SpanContext())
return metadata.Set(ctx, TracePropagationField, base64.RawStdEncoding.EncodeToString(spanCtx))
}
// Call implements client.Client.Call.
func (w *clientWrapper) Call(
ctx context.Context,
req client.Request,
rsp interface{},
opts ...client.CallOption) (err error) {
t := newRequestTracker(req, ClientProfile)
ctx = t.start(ctx, true)
defer func() { t.end(ctx, err) }()
ctx = injectTraceIntoCtx(ctx, t.span)
err = w.Client.Call(ctx, req, rsp, opts...)
return
}
// Publish implements client.Client.Publish.
func (w *clientWrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) (err error) {
t := newEventTracker(p, ClientProfile)
ctx = t.start(ctx, true)
defer func() { t.end(ctx, err) }()
ctx = injectTraceIntoCtx(ctx, t.span)
err = w.Client.Publish(ctx, p, opts...)
return
}
// NewClientWrapper returns a client.Wrapper
// that adds monitoring to outgoing requests.
func NewClientWrapper() client.Wrapper {
return func(c client.Client) client.Client {
return &clientWrapper{c}
}
}
func getTraceFromCtx(ctx context.Context) *trace.SpanContext {
encodedTraceCtx, ok := metadata.Get(ctx, TracePropagationField)
if !ok {
return nil
}
traceCtxBytes, err := base64.RawStdEncoding.DecodeString(encodedTraceCtx)
if err != nil {
log.Errorf("Could not decode trace context: %s", err.Error())
return nil
}
spanCtx, ok := propagation.FromBinary(traceCtxBytes)
if !ok {
log.Errorf("Could not decode trace context from binary")
return nil
}
return &spanCtx
}
// NewHandlerWrapper returns a server.HandlerWrapper
// that adds tracing to incoming requests.
func NewHandlerWrapper() server.HandlerWrapper {
return func(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) (err error) {
t := newRequestTracker(req, ServerProfile)
ctx = t.start(ctx, false)
defer func() { t.end(ctx, err) }()
spanCtx := getTraceFromCtx(ctx)
if spanCtx != nil {
ctx, t.span = trace.StartSpanWithRemoteParent(
ctx,
fmt.Sprintf("rpc/%s/%s/%s", ServerProfile.Role, req.Service(), req.Endpoint()),
*spanCtx,
)
} else {
ctx, t.span = trace.StartSpan(
ctx,
fmt.Sprintf("rpc/%s/%s/%s", ServerProfile.Role, req.Service(), req.Endpoint()),
)
}
err = fn(ctx, req, rsp)
return
}
}
}
// NewSubscriberWrapper returns a server.SubscriberWrapper
// that adds tracing to subscription requests.
func NewSubscriberWrapper() server.SubscriberWrapper {
return func(fn server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, p server.Message) (err error) {
t := newEventTracker(p, ServerProfile)
ctx = t.start(ctx, false)
defer func() { t.end(ctx, err) }()
spanCtx := getTraceFromCtx(ctx)
if spanCtx != nil {
ctx, t.span = trace.StartSpanWithRemoteParent(
ctx,
fmt.Sprintf("rpc/%s/pubsub/%s", ServerProfile.Role, p.Topic()),
*spanCtx,
)
} else {
ctx, t.span = trace.StartSpan(
ctx,
fmt.Sprintf("rpc/%s/pubsub/%s", ServerProfile.Role, p.Topic()),
)
}
err = fn(ctx, p)
return
}
}
}
-142
View File
@@ -1,142 +0,0 @@
package opencensus
import (
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
// The following client RPC measures are supported for use in custom views.
var (
ClientRequestCount = stats.Int64("opencensus.io/rpc/client/request_count", "Number of RPC requests started", stats.UnitNone)
ClientLatency = stats.Float64("opencensus.io/rpc/client/latency", "End-to-end latency", stats.UnitMilliseconds)
)
// The following server RPC measures are supported for use in custom views.
var (
ServerRequestCount = stats.Int64("opencensus.io/rpc/server/request_count", "Number of RPC requests received", stats.UnitNone)
ServerLatency = stats.Float64("opencensus.io/rpc/server/latency", "End-to-end latency", stats.UnitMilliseconds)
)
// The following tags are applied to stats recorded by this package.
// Service and Method are applied to all measures.
// StatusCode is not applied to ClientRequestCount or ServerRequestCount,
// since it is recorded before the status is known.
var (
// StatusCode is the RPC status code.
StatusCode, _ = tag.NewKey("rpc.status")
// Service is the name of the micro-service.
Service, _ = tag.NewKey("rpc.service")
// Method is the service method called.
Endpoint, _ = tag.NewKey("rpc.endpoint")
)
// Default distributions used by views in this package.
var (
DefaultLatencyDistribution = view.Distribution(0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
)
// This package provides some convenience views.
// You need to subscribe to the views for data to actually be collected.
var (
ClientRequestCountView = &view.View{
Name: "opencensus.io/rpc/client/request_count",
Description: "Count of RPC requests started",
Measure: ClientRequestCount,
Aggregation: view.Count(),
}
ClientLatencyView = &view.View{
Name: "opencensus.io/rpc/client/latency",
Description: "Latency distribution of RPC requests",
Measure: ClientLatency,
Aggregation: DefaultLatencyDistribution,
}
ClientRequestCountByMethod = &view.View{
Name: "opencensus.io/rpc/client/request_count_by_method",
Description: "Client request count by RPC method",
TagKeys: []tag.Key{Endpoint},
Measure: ClientRequestCount,
Aggregation: view.Count(),
}
ClientResponseCountByStatusCode = &view.View{
Name: "opencensus.io/rpc/client/response_count_by_status_code",
Description: "Client response count by RPC status code",
TagKeys: []tag.Key{StatusCode},
Measure: ClientLatency,
Aggregation: view.Count(),
}
ServerRequestCountView = &view.View{
Name: "opencensus.io/rpc/server/request_count",
Description: "Count of RPC requests received",
Measure: ServerRequestCount,
Aggregation: view.Count(),
}
ServerLatencyView = &view.View{
Name: "opencensus.io/rpc/server/latency",
Description: "Latency distribution of RPC requests",
Measure: ServerLatency,
Aggregation: DefaultLatencyDistribution,
}
ServerRequestCountByMethod = &view.View{
Name: "opencensus.io/rpc/server/request_count_by_method",
Description: "Server request count by RPC method",
TagKeys: []tag.Key{Endpoint},
Measure: ServerRequestCount,
Aggregation: view.Count(),
}
ServerResponseCountByStatusCode = &view.View{
Name: "opencensus.io/rpc/server/response_count_by_status_code",
Description: "Server response count by RPC status code",
TagKeys: []tag.Key{StatusCode},
Measure: ServerLatency,
Aggregation: view.Count(),
}
)
// DefaultClientViews are the default client views provided by this package.
var DefaultClientViews = []*view.View{
ClientRequestCountView,
ClientLatencyView,
ClientRequestCountByMethod,
ClientResponseCountByStatusCode,
}
// DefaultServerViews are the default server views provided by this package.
var DefaultServerViews = []*view.View{
ServerRequestCountView,
ServerLatencyView,
ServerRequestCountByMethod,
ServerResponseCountByStatusCode,
}
// StatsProfile groups metrics-related data.
type StatsProfile struct {
Role string
CountMeasure *stats.Int64Measure
LatencyMeasure *stats.Float64Measure
}
var (
// ClientProfile is used for RPC clients.
ClientProfile = &StatsProfile{
Role: "client",
CountMeasure: ClientRequestCount,
LatencyMeasure: ClientLatency,
}
// ServerProfile is used for RPC servers.
ServerProfile = &StatsProfile{
Role: "server",
CountMeasure: ServerRequestCount,
LatencyMeasure: ServerLatency,
}
)
@@ -1,45 +0,0 @@
package opencensus
import (
"fmt"
microerr "go-micro.dev/v4/errors"
"go.opencensus.io/trace"
"google.golang.org/genproto/googleapis/rpc/code"
)
var microCodeToStatusCode = map[int32]code.Code{
400: code.Code_INVALID_ARGUMENT,
401: code.Code_UNAUTHENTICATED,
403: code.Code_PERMISSION_DENIED,
404: code.Code_NOT_FOUND,
409: code.Code_ABORTED,
500: code.Code_INTERNAL,
}
func getResponseStatus(err error) trace.Status {
if err != nil {
microErr, ok := err.(*microerr.Error)
if ok {
statusCode := microErr.Code
code, ok := microCodeToStatusCode[microErr.Code]
if ok {
statusCode = int32(code)
}
return trace.Status{
Code: statusCode,
Message: fmt.Sprintf("%s: %s", microErr.Id, microErr.Detail),
}
}
return trace.Status{
Code: int32(code.Code_UNKNOWN),
Message: err.Error(),
}
}
return trace.Status{}
}
@@ -1,81 +0,0 @@
package opencensus
import (
"context"
"fmt"
"strconv"
"time"
"go.opencensus.io/stats"
"go.opencensus.io/tag"
"go.opencensus.io/trace"
)
type tracker struct {
startedAt time.Time
profile *StatsProfile
span *trace.Span
method string
service string
}
type requestDescriptor interface {
Service() string
Endpoint() string
}
type publicationDescriptor interface {
Topic() string
}
// newRequestTracker creates a new tracker for an RPC request (client or server).
func newRequestTracker(req requestDescriptor, profile *StatsProfile) *tracker {
return &tracker{
profile: profile,
method: req.Endpoint(),
service: req.Service(),
}
}
// newEventTracker creates a new tracker for a publication (client or server).
func newEventTracker(pub publicationDescriptor, profile *StatsProfile) *tracker {
return &tracker{
profile: profile,
method: pub.Topic(),
service: "pubsub",
}
}
// start monitoring a request. You can choose to let this method
// start a span for the request or attach one later.
func (t *tracker) start(ctx context.Context, startSpan bool) context.Context {
t.startedAt = time.Now()
ctx, _ = tag.New(ctx, tag.Upsert(Service, t.service), tag.Upsert(Endpoint, t.method))
stats.Record(ctx, t.profile.CountMeasure.M(1))
if startSpan {
ctx, t.span = trace.StartSpan(
ctx,
fmt.Sprintf("rpc/%s/%s/%s", t.profile.Role, t.service, t.method),
)
}
return ctx
}
// end a request's monitoring session. If there is a span ongoing, it will
// be ended and metrics will be recorded.
func (t *tracker) end(ctx context.Context, err error) {
status := getResponseStatus(err)
ctx, _ = tag.New(ctx, tag.Upsert(StatusCode, strconv.Itoa(int(status.Code))))
stats.Record(ctx, t.profile.LatencyMeasure.M(float64(time.Since(t.startedAt))/float64(time.Millisecond)))
if t.span != nil {
t.span.SetStatus(status)
t.span.End()
}
}
@@ -0,0 +1,14 @@
# OpenTelemetry wrappers
OpenTelemetry wrappers propagate traces (spans) accross services.
## Usage
```go
service := micro.NewService(
micro.Name("go.micro.srv.greeter"),
micro.WrapClient(opentelemetry.NewClientWrapper()),
micro.WrapHandler(open.NewHandlerWrapper()),
micro.WrapSubscriber(opentelemetry.NewSubscriberWrapper()),
)
```
@@ -0,0 +1,55 @@
package opentelemetry
import (
"context"
"strings"
"go-micro.dev/v4/metadata"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
const (
instrumentationName = "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
)
// StartSpanFromContext returns a new span with the given operation name and options. If a span
// is found in the context, it will be used as the parent of the resulting span.
func StartSpanFromContext(ctx context.Context, tp trace.TracerProvider, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(metadata.Metadata)
}
propagator, carrier := otel.GetTextMapPropagator(), make(propagation.MapCarrier)
for k, v := range md {
for _, f := range propagator.Fields() {
if strings.EqualFold(k, f) {
carrier[f] = v
}
}
}
ctx = propagator.Extract(ctx, carrier)
spanCtx := trace.SpanContextFromContext(ctx)
ctx = baggage.ContextWithBaggage(ctx, baggage.FromContext(ctx))
var tracer trace.Tracer
var span trace.Span
if tp != nil {
tracer = tp.Tracer(instrumentationName)
} else {
tracer = otel.Tracer(instrumentationName)
}
ctx, span = tracer.Start(trace.ContextWithRemoteSpanContext(ctx, spanCtx), name, opts...)
carrier = make(propagation.MapCarrier)
propagator.Inject(ctx, carrier)
for k, v := range carrier {
//lint:ignore SA1019 no unicode punctution handle needed
md.Set(strings.Title(k), v)
}
ctx = metadata.NewContext(ctx, md)
return ctx, span
}
@@ -0,0 +1,72 @@
package opentelemetry
import (
"context"
"go-micro.dev/v4/client"
"go-micro.dev/v4/server"
"go.opentelemetry.io/otel/trace"
)
type Options struct {
TraceProvider trace.TracerProvider
CallFilter CallFilter
StreamFilter StreamFilter
PublishFilter PublishFilter
SubscriberFilter SubscriberFilter
HandlerFilter HandlerFilter
}
// CallFilter used to filter client.Call, return true to skip call trace.
type CallFilter func(context.Context, client.Request) bool
// StreamFilter used to filter client.Stream, return true to skip stream trace.
type StreamFilter func(context.Context, client.Request) bool
// PublishFilter used to filter client.Publish, return true to skip publish trace.
type PublishFilter func(context.Context, client.Message) bool
// SubscriberFilter used to filter server.Subscribe, return true to skip subcribe trace.
type SubscriberFilter func(context.Context, server.Message) bool
// HandlerFilter used to filter server.Handle, return true to skip handle trace.
type HandlerFilter func(context.Context, server.Request) bool
type Option func(*Options)
func WithTraceProvider(tp trace.TracerProvider) Option {
return func(o *Options) {
o.TraceProvider = tp
}
}
func WithCallFilter(filter CallFilter) Option {
return func(o *Options) {
o.CallFilter = filter
}
}
func WithStreamFilter(filter StreamFilter) Option {
return func(o *Options) {
o.StreamFilter = filter
}
}
func WithPublishFilter(filter PublishFilter) Option {
return func(o *Options) {
o.PublishFilter = filter
}
}
func WithSubscribeFilter(filter SubscriberFilter) Option {
return func(o *Options) {
o.SubscriberFilter = filter
}
}
func WithHandleFilter(filter HandlerFilter) Option {
return func(o *Options) {
o.HandlerFilter = filter
}
}
@@ -0,0 +1,175 @@
package opentelemetry
import (
"context"
"fmt"
"go-micro.dev/v4/client"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/server"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
// NewCallWrapper accepts an opentracing Tracer and returns a Call Wrapper.
func NewCallWrapper(opts ...Option) client.CallWrapper {
options := Options{}
for _, o := range opts {
o(&options)
}
return func(cf client.CallFunc) client.CallFunc {
return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
if options.CallFilter != nil && options.CallFilter(ctx, req) {
return cf(ctx, node, req, rsp, opts)
}
name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindClient),
}
ctx, span := StartSpanFromContext(ctx, options.TraceProvider, name, spanOpts...)
defer span.End()
if err := cf(ctx, node, req, rsp, opts); err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return err
}
return nil
}
}
}
// NewHandlerWrapper accepts an opentracing Tracer and returns a Handler Wrapper.
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
options := Options{}
for _, o := range opts {
o(&options)
}
return func(h server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
if options.HandlerFilter != nil && options.HandlerFilter(ctx, req) {
return h(ctx, req, rsp)
}
name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindServer),
}
ctx, span := StartSpanFromContext(ctx, options.TraceProvider, name, spanOpts...)
defer span.End()
if err := h(ctx, req, rsp); err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return err
}
return nil
}
}
}
// NewSubscriberWrapper accepts an opentracing Tracer and returns a Subscriber Wrapper.
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
options := Options{}
for _, o := range opts {
o(&options)
}
return func(next server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
if options.SubscriberFilter != nil && options.SubscriberFilter(ctx, msg) {
return next(ctx, msg)
}
name := "Sub from " + msg.Topic()
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindServer),
}
ctx, span := StartSpanFromContext(ctx, options.TraceProvider, name, spanOpts...)
defer span.End()
if err := next(ctx, msg); err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return err
}
return nil
}
}
}
// NewClientWrapper returns a client.Wrapper
// that adds monitoring to outgoing requests.
func NewClientWrapper(opts ...Option) client.Wrapper {
options := Options{}
for _, o := range opts {
o(&options)
}
return func(c client.Client) client.Client {
w := &clientWrapper{
Client: c,
tp: options.TraceProvider,
callFilter: options.CallFilter,
streamFilter: options.StreamFilter,
publishFilter: options.PublishFilter,
}
return w
}
}
type clientWrapper struct {
client.Client
tp trace.TracerProvider
callFilter CallFilter
streamFilter StreamFilter
publishFilter PublishFilter
}
func (w *clientWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
if w.callFilter != nil && w.callFilter(ctx, req) {
return w.Client.Call(ctx, req, rsp, opts...)
}
name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindClient),
}
ctx, span := StartSpanFromContext(ctx, w.tp, name, spanOpts...)
defer span.End()
if err := w.Client.Call(ctx, req, rsp, opts...); err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return err
}
return nil
}
func (w *clientWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
if w.streamFilter != nil && w.streamFilter(ctx, req) {
return w.Client.Stream(ctx, req, opts...)
}
name := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindClient),
}
ctx, span := StartSpanFromContext(ctx, w.tp, name, spanOpts...)
defer span.End()
stream, err := w.Client.Stream(ctx, req, opts...)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
}
return stream, err
}
func (w *clientWrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
if w.publishFilter != nil && w.publishFilter(ctx, p) {
return w.Client.Publish(ctx, p, opts...)
}
name := fmt.Sprintf("Pub to %s", p.Topic())
spanOpts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindClient),
}
ctx, span := StartSpanFromContext(ctx, w.tp, name, spanOpts...)
defer span.End()
if err := w.Client.Publish(ctx, p, opts...); err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return err
}
return nil
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2018, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ocgrpc
import (
"context"
"go.opencensus.io/trace"
"google.golang.org/grpc/stats"
)
// ClientHandler implements a gRPC stats.Handler for recording OpenCensus stats and
// traces. Use with gRPC clients only.
type ClientHandler struct {
// StartOptions allows configuring the StartOptions used to create new spans.
//
// StartOptions.SpanKind will always be set to trace.SpanKindClient
// for spans started by this handler.
StartOptions trace.StartOptions
}
// HandleConn exists to satisfy gRPC stats.Handler.
func (c *ClientHandler) HandleConn(ctx context.Context, cs stats.ConnStats) {
// no-op
}
// TagConn exists to satisfy gRPC stats.Handler.
func (c *ClientHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context {
// no-op
return ctx
}
// HandleRPC implements per-RPC tracing and stats instrumentation.
func (c *ClientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
traceHandleRPC(ctx, rs)
statsHandleRPC(ctx, rs)
}
// TagRPC implements per-RPC context management.
func (c *ClientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
ctx = c.traceTagRPC(ctx, rti)
ctx = c.statsTagRPC(ctx, rti)
return ctx
}
-118
View File
@@ -1,118 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package ocgrpc
import (
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
// The following variables are measures are recorded by ClientHandler:
var (
ClientSentMessagesPerRPC = stats.Int64("grpc.io/client/sent_messages_per_rpc", "Number of messages sent in the RPC (always 1 for non-streaming RPCs).", stats.UnitDimensionless)
ClientSentBytesPerRPC = stats.Int64("grpc.io/client/sent_bytes_per_rpc", "Total bytes sent across all request messages per RPC.", stats.UnitBytes)
ClientReceivedMessagesPerRPC = stats.Int64("grpc.io/client/received_messages_per_rpc", "Number of response messages received per RPC (always 1 for non-streaming RPCs).", stats.UnitDimensionless)
ClientReceivedBytesPerRPC = stats.Int64("grpc.io/client/received_bytes_per_rpc", "Total bytes received across all response messages per RPC.", stats.UnitBytes)
ClientRoundtripLatency = stats.Float64("grpc.io/client/roundtrip_latency", "Time between first byte of request sent to last byte of response received, or terminal error.", stats.UnitMilliseconds)
ClientStartedRPCs = stats.Int64("grpc.io/client/started_rpcs", "Number of started client RPCs.", stats.UnitDimensionless)
ClientServerLatency = stats.Float64("grpc.io/client/server_latency", `Propagated from the server and should have the same value as "grpc.io/server/latency".`, stats.UnitMilliseconds)
)
// Predefined views may be registered to collect data for the above measures.
// As always, you may also define your own custom views over measures collected by this
// package. These are declared as a convenience only; none are registered by
// default.
var (
ClientSentBytesPerRPCView = &view.View{
Measure: ClientSentBytesPerRPC,
Name: "grpc.io/client/sent_bytes_per_rpc",
Description: "Distribution of bytes sent per RPC, by method.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: DefaultBytesDistribution,
}
ClientReceivedBytesPerRPCView = &view.View{
Measure: ClientReceivedBytesPerRPC,
Name: "grpc.io/client/received_bytes_per_rpc",
Description: "Distribution of bytes received per RPC, by method.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: DefaultBytesDistribution,
}
ClientRoundtripLatencyView = &view.View{
Measure: ClientRoundtripLatency,
Name: "grpc.io/client/roundtrip_latency",
Description: "Distribution of round-trip latency, by method.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: DefaultMillisecondsDistribution,
}
// Purposely reuses the count from `ClientRoundtripLatency`, tagging
// with method and status to result in ClientCompletedRpcs.
ClientCompletedRPCsView = &view.View{
Measure: ClientRoundtripLatency,
Name: "grpc.io/client/completed_rpcs",
Description: "Count of RPCs by method and status.",
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus},
Aggregation: view.Count(),
}
ClientStartedRPCsView = &view.View{
Measure: ClientStartedRPCs,
Name: "grpc.io/client/started_rpcs",
Description: "Number of started client RPCs.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: view.Count(),
}
ClientSentMessagesPerRPCView = &view.View{
Measure: ClientSentMessagesPerRPC,
Name: "grpc.io/client/sent_messages_per_rpc",
Description: "Distribution of sent messages count per RPC, by method.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: DefaultMessageCountDistribution,
}
ClientReceivedMessagesPerRPCView = &view.View{
Measure: ClientReceivedMessagesPerRPC,
Name: "grpc.io/client/received_messages_per_rpc",
Description: "Distribution of received messages count per RPC, by method.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: DefaultMessageCountDistribution,
}
ClientServerLatencyView = &view.View{
Measure: ClientServerLatency,
Name: "grpc.io/client/server_latency",
Description: "Distribution of server latency as viewed by client, by method.",
TagKeys: []tag.Key{KeyClientMethod},
Aggregation: DefaultMillisecondsDistribution,
}
)
// DefaultClientViews are the default client views provided by this package.
var DefaultClientViews = []*view.View{
ClientSentBytesPerRPCView,
ClientReceivedBytesPerRPCView,
ClientRoundtripLatencyView,
ClientCompletedRPCsView,
}
// TODO(jbd): Add roundtrip_latency, uncompressed_request_bytes, uncompressed_response_bytes, request_count, response_count.
// TODO(acetechnologist): This is temporary and will need to be replaced by a
// mechanism to load these defaults from a common repository/config shared by
// all supported languages. Likely a serialized protobuf of these defaults.
-49
View File
@@ -1,49 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package ocgrpc
import (
"context"
"time"
"go.opencensus.io/tag"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/stats"
)
// statsTagRPC gets the tag.Map populated by the application code, serializes
// its tags into the GRPC metadata in order to be sent to the server.
func (h *ClientHandler) statsTagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
startTime := time.Now()
if info == nil {
if grpclog.V(2) {
grpclog.Info("clientHandler.TagRPC called with nil info.")
}
return ctx
}
d := &rpcData{
startTime: startTime,
method: info.FullMethodName,
}
ts := tag.FromContext(ctx)
if ts != nil {
encoded := tag.Encode(ts)
ctx = stats.SetTags(ctx, encoded)
}
return context.WithValue(ctx, rpcDataKey, d)
}
-81
View File
@@ -1,81 +0,0 @@
// Copyright 2018, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ocgrpc
import (
"context"
"google.golang.org/grpc/stats"
"go.opencensus.io/trace"
)
// ServerHandler implements gRPC stats.Handler recording OpenCensus stats and
// traces. Use with gRPC servers.
//
// When installed (see Example), tracing metadata is read from inbound RPCs
// by default. If no tracing metadata is present, or if the tracing metadata is
// present but the SpanContext isn't sampled, then a new trace may be started
// (as determined by Sampler).
type ServerHandler struct {
// IsPublicEndpoint may be set to true to always start a new trace around
// each RPC. Any SpanContext in the RPC metadata will be added as a linked
// span instead of making it the parent of the span created around the
// server RPC.
//
// Be aware that if you leave this false (the default) on a public-facing
// server, callers will be able to send tracing metadata in gRPC headers
// and trigger traces in your backend.
IsPublicEndpoint bool
// StartOptions to use for to spans started around RPCs handled by this server.
//
// These will apply even if there is tracing metadata already
// present on the inbound RPC but the SpanContext is not sampled. This
// ensures that each service has some opportunity to be traced. If you would
// like to not add any additional traces for this gRPC service, set:
//
// StartOptions.Sampler = trace.ProbabilitySampler(0.0)
//
// StartOptions.SpanKind will always be set to trace.SpanKindServer
// for spans started by this handler.
StartOptions trace.StartOptions
}
var _ stats.Handler = (*ServerHandler)(nil)
// HandleConn exists to satisfy gRPC stats.Handler.
func (s *ServerHandler) HandleConn(ctx context.Context, cs stats.ConnStats) {
// no-op
}
// TagConn exists to satisfy gRPC stats.Handler.
func (s *ServerHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context {
// no-op
return ctx
}
// HandleRPC implements per-RPC tracing and stats instrumentation.
func (s *ServerHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
traceHandleRPC(ctx, rs)
statsHandleRPC(ctx, rs)
}
// TagRPC implements per-RPC context management.
func (s *ServerHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
ctx = s.traceTagRPC(ctx, rti)
ctx = s.statsTagRPC(ctx, rti)
return ctx
}
-108
View File
@@ -1,108 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package ocgrpc
import (
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
// The following variables are measures are recorded by ServerHandler:
var (
ServerReceivedMessagesPerRPC = stats.Int64("grpc.io/server/received_messages_per_rpc", "Number of messages received in each RPC. Has value 1 for non-streaming RPCs.", stats.UnitDimensionless)
ServerReceivedBytesPerRPC = stats.Int64("grpc.io/server/received_bytes_per_rpc", "Total bytes received across all messages per RPC.", stats.UnitBytes)
ServerSentMessagesPerRPC = stats.Int64("grpc.io/server/sent_messages_per_rpc", "Number of messages sent in each RPC. Has value 1 for non-streaming RPCs.", stats.UnitDimensionless)
ServerSentBytesPerRPC = stats.Int64("grpc.io/server/sent_bytes_per_rpc", "Total bytes sent in across all response messages per RPC.", stats.UnitBytes)
ServerStartedRPCs = stats.Int64("grpc.io/server/started_rpcs", "Number of started server RPCs.", stats.UnitDimensionless)
ServerLatency = stats.Float64("grpc.io/server/server_latency", "Time between first byte of request received to last byte of response sent, or terminal error.", stats.UnitMilliseconds)
)
// TODO(acetechnologist): This is temporary and will need to be replaced by a
// mechanism to load these defaults from a common repository/config shared by
// all supported languages. Likely a serialized protobuf of these defaults.
// Predefined views may be registered to collect data for the above measures.
// As always, you may also define your own custom views over measures collected by this
// package. These are declared as a convenience only; none are registered by
// default.
var (
ServerReceivedBytesPerRPCView = &view.View{
Name: "grpc.io/server/received_bytes_per_rpc",
Description: "Distribution of received bytes per RPC, by method.",
Measure: ServerReceivedBytesPerRPC,
TagKeys: []tag.Key{KeyServerMethod},
Aggregation: DefaultBytesDistribution,
}
ServerSentBytesPerRPCView = &view.View{
Name: "grpc.io/server/sent_bytes_per_rpc",
Description: "Distribution of total sent bytes per RPC, by method.",
Measure: ServerSentBytesPerRPC,
TagKeys: []tag.Key{KeyServerMethod},
Aggregation: DefaultBytesDistribution,
}
ServerLatencyView = &view.View{
Name: "grpc.io/server/server_latency",
Description: "Distribution of server latency in milliseconds, by method.",
TagKeys: []tag.Key{KeyServerMethod},
Measure: ServerLatency,
Aggregation: DefaultMillisecondsDistribution,
}
// Purposely reuses the count from `ServerLatency`, tagging
// with method and status to result in ServerCompletedRpcs.
ServerCompletedRPCsView = &view.View{
Name: "grpc.io/server/completed_rpcs",
Description: "Count of RPCs by method and status.",
TagKeys: []tag.Key{KeyServerMethod, KeyServerStatus},
Measure: ServerLatency,
Aggregation: view.Count(),
}
ServerStartedRPCsView = &view.View{
Measure: ServerStartedRPCs,
Name: "grpc.io/server/started_rpcs",
Description: "Number of started server RPCs.",
TagKeys: []tag.Key{KeyServerMethod},
Aggregation: view.Count(),
}
ServerReceivedMessagesPerRPCView = &view.View{
Name: "grpc.io/server/received_messages_per_rpc",
Description: "Distribution of messages received count per RPC, by method.",
TagKeys: []tag.Key{KeyServerMethod},
Measure: ServerReceivedMessagesPerRPC,
Aggregation: DefaultMessageCountDistribution,
}
ServerSentMessagesPerRPCView = &view.View{
Name: "grpc.io/server/sent_messages_per_rpc",
Description: "Distribution of messages sent count per RPC, by method.",
TagKeys: []tag.Key{KeyServerMethod},
Measure: ServerSentMessagesPerRPC,
Aggregation: DefaultMessageCountDistribution,
}
)
// DefaultServerViews are the default server views provided by this package.
var DefaultServerViews = []*view.View{
ServerReceivedBytesPerRPCView,
ServerSentBytesPerRPCView,
ServerLatencyView,
ServerCompletedRPCsView,
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package ocgrpc
import (
"time"
"context"
"go.opencensus.io/tag"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/stats"
)
// statsTagRPC gets the metadata from gRPC context, extracts the encoded tags from
// it and creates a new tag.Map and puts them into the returned context.
func (h *ServerHandler) statsTagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
startTime := time.Now()
if info == nil {
if grpclog.V(2) {
grpclog.Infof("opencensus: TagRPC called with nil info.")
}
return ctx
}
d := &rpcData{
startTime: startTime,
method: info.FullMethodName,
}
propagated := h.extractPropagatedTags(ctx)
ctx = tag.NewContext(ctx, propagated)
ctx, _ = tag.New(ctx, tag.Upsert(KeyServerMethod, methodName(info.FullMethodName)))
return context.WithValue(ctx, rpcDataKey, d)
}
// extractPropagatedTags creates a new tag map containing the tags extracted from the
// gRPC metadata.
func (h *ServerHandler) extractPropagatedTags(ctx context.Context) *tag.Map {
buf := stats.Tags(ctx)
if buf == nil {
return nil
}
propagated, err := tag.Decode(buf)
if err != nil {
if grpclog.V(2) {
grpclog.Warningf("opencensus: Failed to decode tags from gRPC metadata failed to decode: %v", err)
}
return nil
}
return propagated
}
-248
View File
@@ -1,248 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package ocgrpc
import (
"context"
"strconv"
"strings"
"sync/atomic"
"time"
"go.opencensus.io/metric/metricdata"
ocstats "go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/stats"
"google.golang.org/grpc/status"
)
type grpcInstrumentationKey string
// rpcData holds the instrumentation RPC data that is needed between the start
// and end of an call. It holds the info that this package needs to keep track
// of between the various GRPC events.
type rpcData struct {
// reqCount and respCount has to be the first words
// in order to be 64-aligned on 32-bit architectures.
sentCount, sentBytes, recvCount, recvBytes int64 // access atomically
// startTime represents the time at which TagRPC was invoked at the
// beginning of an RPC. It is an appoximation of the time when the
// application code invoked GRPC code.
startTime time.Time
method string
}
// The following variables define the default hard-coded auxiliary data used by
// both the default GRPC client and GRPC server metrics.
var (
DefaultBytesDistribution = view.Distribution(1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
DefaultMillisecondsDistribution = view.Distribution(0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
DefaultMessageCountDistribution = view.Distribution(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536)
)
// Server tags are applied to the context used to process each RPC, as well as
// the measures at the end of each RPC.
var (
KeyServerMethod = tag.MustNewKey("grpc_server_method")
KeyServerStatus = tag.MustNewKey("grpc_server_status")
)
// Client tags are applied to measures at the end of each RPC.
var (
KeyClientMethod = tag.MustNewKey("grpc_client_method")
KeyClientStatus = tag.MustNewKey("grpc_client_status")
)
var (
rpcDataKey = grpcInstrumentationKey("opencensus-rpcData")
)
func methodName(fullname string) string {
return strings.TrimLeft(fullname, "/")
}
// statsHandleRPC processes the RPC events.
func statsHandleRPC(ctx context.Context, s stats.RPCStats) {
switch st := s.(type) {
case *stats.OutHeader, *stats.InHeader, *stats.InTrailer, *stats.OutTrailer:
// do nothing for client
case *stats.Begin:
handleRPCBegin(ctx, st)
case *stats.OutPayload:
handleRPCOutPayload(ctx, st)
case *stats.InPayload:
handleRPCInPayload(ctx, st)
case *stats.End:
handleRPCEnd(ctx, st)
default:
grpclog.Infof("unexpected stats: %T", st)
}
}
func handleRPCBegin(ctx context.Context, s *stats.Begin) {
d, ok := ctx.Value(rpcDataKey).(*rpcData)
if !ok {
if grpclog.V(2) {
grpclog.Infoln("Failed to retrieve *rpcData from context.")
}
}
if s.IsClient() {
ocstats.RecordWithOptions(ctx,
ocstats.WithTags(tag.Upsert(KeyClientMethod, methodName(d.method))),
ocstats.WithMeasurements(ClientStartedRPCs.M(1)))
} else {
ocstats.RecordWithOptions(ctx,
ocstats.WithTags(tag.Upsert(KeyClientMethod, methodName(d.method))),
ocstats.WithMeasurements(ServerStartedRPCs.M(1)))
}
}
func handleRPCOutPayload(ctx context.Context, s *stats.OutPayload) {
d, ok := ctx.Value(rpcDataKey).(*rpcData)
if !ok {
if grpclog.V(2) {
grpclog.Infoln("Failed to retrieve *rpcData from context.")
}
return
}
atomic.AddInt64(&d.sentBytes, int64(s.Length))
atomic.AddInt64(&d.sentCount, 1)
}
func handleRPCInPayload(ctx context.Context, s *stats.InPayload) {
d, ok := ctx.Value(rpcDataKey).(*rpcData)
if !ok {
if grpclog.V(2) {
grpclog.Infoln("Failed to retrieve *rpcData from context.")
}
return
}
atomic.AddInt64(&d.recvBytes, int64(s.Length))
atomic.AddInt64(&d.recvCount, 1)
}
func handleRPCEnd(ctx context.Context, s *stats.End) {
d, ok := ctx.Value(rpcDataKey).(*rpcData)
if !ok {
if grpclog.V(2) {
grpclog.Infoln("Failed to retrieve *rpcData from context.")
}
return
}
elapsedTime := time.Since(d.startTime)
var st string
if s.Error != nil {
s, ok := status.FromError(s.Error)
if ok {
st = statusCodeToString(s)
}
} else {
st = "OK"
}
latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
attachments := getSpanCtxAttachment(ctx)
if s.Client {
ocstats.RecordWithOptions(ctx,
ocstats.WithTags(
tag.Upsert(KeyClientMethod, methodName(d.method)),
tag.Upsert(KeyClientStatus, st)),
ocstats.WithAttachments(attachments),
ocstats.WithMeasurements(
ClientSentBytesPerRPC.M(atomic.LoadInt64(&d.sentBytes)),
ClientSentMessagesPerRPC.M(atomic.LoadInt64(&d.sentCount)),
ClientReceivedMessagesPerRPC.M(atomic.LoadInt64(&d.recvCount)),
ClientReceivedBytesPerRPC.M(atomic.LoadInt64(&d.recvBytes)),
ClientRoundtripLatency.M(latencyMillis)))
} else {
ocstats.RecordWithOptions(ctx,
ocstats.WithTags(
tag.Upsert(KeyServerStatus, st),
),
ocstats.WithAttachments(attachments),
ocstats.WithMeasurements(
ServerSentBytesPerRPC.M(atomic.LoadInt64(&d.sentBytes)),
ServerSentMessagesPerRPC.M(atomic.LoadInt64(&d.sentCount)),
ServerReceivedMessagesPerRPC.M(atomic.LoadInt64(&d.recvCount)),
ServerReceivedBytesPerRPC.M(atomic.LoadInt64(&d.recvBytes)),
ServerLatency.M(latencyMillis)))
}
}
func statusCodeToString(s *status.Status) string {
// see https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
switch c := s.Code(); c {
case codes.OK:
return "OK"
case codes.Canceled:
return "CANCELLED"
case codes.Unknown:
return "UNKNOWN"
case codes.InvalidArgument:
return "INVALID_ARGUMENT"
case codes.DeadlineExceeded:
return "DEADLINE_EXCEEDED"
case codes.NotFound:
return "NOT_FOUND"
case codes.AlreadyExists:
return "ALREADY_EXISTS"
case codes.PermissionDenied:
return "PERMISSION_DENIED"
case codes.ResourceExhausted:
return "RESOURCE_EXHAUSTED"
case codes.FailedPrecondition:
return "FAILED_PRECONDITION"
case codes.Aborted:
return "ABORTED"
case codes.OutOfRange:
return "OUT_OF_RANGE"
case codes.Unimplemented:
return "UNIMPLEMENTED"
case codes.Internal:
return "INTERNAL"
case codes.Unavailable:
return "UNAVAILABLE"
case codes.DataLoss:
return "DATA_LOSS"
case codes.Unauthenticated:
return "UNAUTHENTICATED"
default:
return "CODE_" + strconv.FormatInt(int64(c), 10)
}
}
func getSpanCtxAttachment(ctx context.Context) metricdata.Attachments {
attachments := map[string]interface{}{}
span := trace.FromContext(ctx)
if span == nil {
return attachments
}
spanCtx := span.SpanContext()
if spanCtx.IsSampled() {
attachments[metricdata.AttachmentKeySpanContext] = spanCtx
}
return attachments
}
-107
View File
@@ -1,107 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ocgrpc
import (
"context"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/stats"
"google.golang.org/grpc/status"
"go.opencensus.io/trace"
"go.opencensus.io/trace/propagation"
)
const traceContextKey = "grpc-trace-bin"
// TagRPC creates a new trace span for the client side of the RPC.
//
// It returns ctx with the new trace span added and a serialization of the
// SpanContext added to the outgoing gRPC metadata.
func (c *ClientHandler) traceTagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
name := strings.TrimPrefix(rti.FullMethodName, "/")
name = strings.Replace(name, "/", ".", -1)
ctx, span := trace.StartSpan(ctx, name,
trace.WithSampler(c.StartOptions.Sampler),
trace.WithSpanKind(trace.SpanKindClient)) // span is ended by traceHandleRPC
traceContextBinary := propagation.Binary(span.SpanContext())
return metadata.AppendToOutgoingContext(ctx, traceContextKey, string(traceContextBinary))
}
// TagRPC creates a new trace span for the server side of the RPC.
//
// It checks the incoming gRPC metadata in ctx for a SpanContext, and if
// it finds one, uses that SpanContext as the parent context of the new span.
//
// It returns ctx, with the new trace span added.
func (s *ServerHandler) traceTagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
md, _ := metadata.FromIncomingContext(ctx)
name := strings.TrimPrefix(rti.FullMethodName, "/")
name = strings.Replace(name, "/", ".", -1)
traceContext := md[traceContextKey]
var (
parent trace.SpanContext
haveParent bool
)
if len(traceContext) > 0 {
// Metadata with keys ending in -bin are actually binary. They are base64
// encoded before being put on the wire, see:
// https://github.com/grpc/grpc-go/blob/08d6261/Documentation/grpc-metadata.md#storing-binary-data-in-metadata
traceContextBinary := []byte(traceContext[0])
parent, haveParent = propagation.FromBinary(traceContextBinary)
if haveParent && !s.IsPublicEndpoint {
ctx, _ := trace.StartSpanWithRemoteParent(ctx, name, parent,
trace.WithSpanKind(trace.SpanKindServer),
trace.WithSampler(s.StartOptions.Sampler),
)
return ctx
}
}
ctx, span := trace.StartSpan(ctx, name,
trace.WithSpanKind(trace.SpanKindServer),
trace.WithSampler(s.StartOptions.Sampler))
if haveParent {
span.AddLink(trace.Link{TraceID: parent.TraceID, SpanID: parent.SpanID, Type: trace.LinkTypeChild})
}
return ctx
}
func traceHandleRPC(ctx context.Context, rs stats.RPCStats) {
span := trace.FromContext(ctx)
// TODO: compressed and uncompressed sizes are not populated in every message.
switch rs := rs.(type) {
case *stats.Begin:
span.AddAttributes(
trace.BoolAttribute("Client", rs.Client),
trace.BoolAttribute("FailFast", rs.FailFast))
case *stats.InPayload:
span.AddMessageReceiveEvent(0 /* TODO: messageID */, int64(rs.Length), int64(rs.WireLength))
case *stats.OutPayload:
span.AddMessageSendEvent(0, int64(rs.Length), int64(rs.WireLength))
case *stats.End:
if rs.Error != nil {
s, ok := status.FromError(rs.Error)
if ok {
span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()})
} else {
span.SetStatus(trace.Status{Code: int32(codes.Internal), Message: rs.Error.Error()})
}
}
span.End()
}
}
-284
View File
@@ -1,284 +0,0 @@
// Code generated by "esc -pkg resources -o resources.go public/ templates/"; DO NOT EDIT.
package internal
import (
"bytes"
"compress/gzip"
"encoding/base64"
"io/ioutil"
"net/http"
"os"
"path"
"sync"
"time"
)
type _escLocalFS struct{}
var _escLocal _escLocalFS
type _escStaticFS struct{}
var _escStatic _escStaticFS
type _escDirectory struct {
fs http.FileSystem
name string
}
type _escFile struct {
compressed string
size int64
modtime int64
local string
isDir bool
once sync.Once
data []byte
name string
}
func (_escLocalFS) Open(name string) (http.File, error) {
f, present := _escData[path.Clean(name)]
if !present {
return nil, os.ErrNotExist
}
return os.Open(f.local)
}
func (_escStaticFS) prepare(name string) (*_escFile, error) {
f, present := _escData[path.Clean(name)]
if !present {
return nil, os.ErrNotExist
}
var err error
f.once.Do(func() {
f.name = path.Base(name)
if f.size == 0 {
return
}
var gr *gzip.Reader
b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed))
gr, err = gzip.NewReader(b64)
if err != nil {
return
}
f.data, err = ioutil.ReadAll(gr)
})
if err != nil {
return nil, err
}
return f, nil
}
func (fs _escStaticFS) Open(name string) (http.File, error) {
f, err := fs.prepare(name)
if err != nil {
return nil, err
}
return f.File()
}
func (dir _escDirectory) Open(name string) (http.File, error) {
return dir.fs.Open(dir.name + name)
}
func (f *_escFile) File() (http.File, error) {
type httpFile struct {
*bytes.Reader
*_escFile
}
return &httpFile{
Reader: bytes.NewReader(f.data),
_escFile: f,
}, nil
}
func (f *_escFile) Close() error {
return nil
}
func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
func (f *_escFile) Stat() (os.FileInfo, error) {
return f, nil
}
func (f *_escFile) Name() string {
return f.name
}
func (f *_escFile) Size() int64 {
return f.size
}
func (f *_escFile) Mode() os.FileMode {
return 0
}
func (f *_escFile) ModTime() time.Time {
return time.Unix(f.modtime, 0)
}
func (f *_escFile) IsDir() bool {
return f.isDir
}
func (f *_escFile) Sys() interface{} {
return f
}
// FS returns a http.Filesystem for the embedded assets. If useLocal is true,
// the filesystem's contents are instead used.
func FS(useLocal bool) http.FileSystem {
if useLocal {
return _escLocal
}
return _escStatic
}
// Dir returns a http.Filesystem for the embedded assets on a given prefix dir.
// If useLocal is true, the filesystem's contents are instead used.
func Dir(useLocal bool, name string) http.FileSystem {
if useLocal {
return _escDirectory{fs: _escLocal, name: name}
}
return _escDirectory{fs: _escStatic, name: name}
}
// FSByte returns the named file from the embedded assets. If useLocal is
// true, the filesystem's contents are instead used.
func FSByte(useLocal bool, name string) ([]byte, error) {
if useLocal {
f, err := _escLocal.Open(name)
if err != nil {
return nil, err
}
b, err := ioutil.ReadAll(f)
_ = f.Close()
return b, err
}
f, err := _escStatic.prepare(name)
if err != nil {
return nil, err
}
return f.data, nil
}
// FSMustByte is the same as FSByte, but panics if name is not present.
func FSMustByte(useLocal bool, name string) []byte {
b, err := FSByte(useLocal, name)
if err != nil {
panic(err)
}
return b
}
// FSString is the string version of FSByte.
func FSString(useLocal bool, name string) (string, error) {
b, err := FSByte(useLocal, name)
return string(b), err
}
// FSMustString is the string version of FSMustByte.
func FSMustString(useLocal bool, name string) string {
return string(FSMustByte(useLocal, name))
}
var _escData = map[string]*_escFile{
"/public/opencensus.css": {
local: "public/opencensus.css",
size: 0,
modtime: 1519153040,
compressed: `
H4sIAAAAAAAC/wEAAP//AAAAAAAAAAA=
`,
},
"/templates/footer.html": {
local: "templates/footer.html",
size: 16,
modtime: 1519153248,
compressed: `
H4sIAAAAAAAC/7LRT8pPqbTjstHPKMnNseMCBAAA//8ATCBFEAAAAA==
`,
},
"/templates/header.html": {
local: "templates/header.html",
size: 523,
modtime: 1519164535,
compressed: `
H4sIAAAAAAAC/5TRv07rMBQG8D1P4ev1qvat7oKQEwZgYEAwdGF0nZP4UP+JfE6oqqrvjkyKBGIpky0f
+6fP+syfu6fbzcvzvfAcQ9eYuohg09hKSLIzHmzfNUIIYSKwFc7bQsCtnHlYXcnziJEDdMej2tTN6WT0
crJMA6adKBBaST4XdjMLdDlJ4QsMrdR6v9+rPEFykGgmhVkP9q1eUeiy1D8ZPgQgD8CfxjRvAzr9BXFE
F730zBNdaz3kxKTGnMcAdkJSLkddM9wMNmI4tI+WoaANfx9cTiR/QbvcgxqBYx/q39bqv/qn45lTmHoc
82rCtFMR00fwM06u4MSihwGKoOIuJSvzSrIzehG6xuilSLPN/aHWvP7Wll93zXsAAAD//6iqQ1ULAgAA
`,
},
"/templates/rpcz.html": {
local: "templates/rpcz.html",
size: 2626,
modtime: 1519164559,
compressed: `
H4sIAAAAAAAC/+yW3WrbMBTH7/0UwmUjYyxJU3o1W1C6sQ4WNrq+gCwdfzBFMtJx9+Hl3cex3DhNCrOz
XfbGxFZ+5/8D+Ry5bZ0wBbD5VxT4wdmm9tttlNQ8QZFpYFkhrbYuPQMAyHP2vVJYpufL5QueoGNCV4VJ
JRgExxNUPMmtQearX5C+XvG2nb+rHEisrNlukwUt8mRB/1ugowuF8GRR8+ggMD7L8/wSIGa5ExtIM/uD
SdDa10JWpkiX3V0tlKK7FY8ixhgjp6ECAFwqiHm3FJZLCi2DKnnsLzGphfdprM9jJi0lmfSCX9vG4FTo
6r5gWiAY+ZPNNv7VVP5WILCZq+ViOvvR1A2y2bfsBPZzg6fD752zzndU2Aza47H70r9KGnLka8DSql38
S5P5+u3x9Vgr1HBVUSJfV2bel3i8cOOefn5ncf6c+Zz5XzKfaADyGLrlYn9UvlnxB52DERlFw4Q2oval
RRrQDyX3zBVPMhq4oXlo2mZHjXvcyqrXjzv/mAp0A29dmQbht6TfVGscdWMbN5W5syj0I2ik59V98SmM
2F5240elDlynO5kKwjtspO3tl2sa6r2qEwijYnusM50KBdE9aqRqd4DsySqBYnT2Du6UT0OD+AE7Uj6c
YKfaD/R0/YH9F/9wiE5uv4BN7L8A/a0BwxxqWzCKPg37b7bdgz8BAAD//6NjPmJCCgAA
`,
},
"/templates/summary.html": {
local: "templates/summary.html",
size: 1619,
modtime: 1519164559,
compressed: `
H4sIAAAAAAAC/6yVPW/bMBCG9/yKg2p4qu2kW12JRQtkCzok3YoOlHSWBdMngaSc2iz/e8EP+Stqi8Re
DIo63t3zvjwr1TwXCEpvBWZJ3sgS5US1vKipmsNtwm4AAFItwyI8lFA0QrWcsjvgoq4oE7jQLM3ZU8sJ
vvE1prOcpTNdnhxjY8pV+yn8/j5+8KFDiZMCSaNMXPLHjqim6i2pB5v/OFDjgWukYgtPfN0KVFerNcRz
L2Ujhyuls17xv0t/pcbelsYYyalCmEbBvnbFCrVzXlmb6uU/wX8YM7X2Z0ReMmOQSmuviRIENGbEYZ7B
9LvkBap7KtumJm2teyNqWin/9sGt/GaAGsnmuaYSf733Sx/z2DyHkAmMiK/RbzreuFkvADdIh7NOBrkf
LF6sKtl0VM7hHSImjlko9EGBHyZRAUdvTMzdD8b/9IgtRKijVC/k57CUuMgSp421n3dOOgeUGePBrB3v
9LbF7NY1Of1S6HrjG+HsUMr1ft7wIXIfdUb1aoa9Ib0bGy66IH28d07ACxjvxjvV5X5pzCj65rhDpSPs
/o6e0J9Pge+G+dv98tClYlxs6IcDbPDW/wGpE8cGfB2Iiij9kHnIdOY/JezmTwAAAP//Dz6TJ1MGAAA=
`,
},
"/templates/traces.html": {
local: "templates/traces.html",
size: 420,
modtime: 1519164578,
compressed: `
H4sIAAAAAAAC/4yQsU70MBCEez/FKtIv3RW/w6WgOIw7kGgoDiRqO14gwnGM1xEgs++OnKMA5Qq2ssYz
I82nolZW30UT4NaMuIdSZH0wg2qtVm3UQkVd1XlkhgO+zkiZvj8SavHwjAFO35U3kdDBhrDfiv9/PFFK
MuEJQR6mN2IuJaYh5Edo/nXn1MBmCA7fQV4P6B3B2ZYZfnh23dqzO3p+i12tlp85mR4HxyxKweCYVbvs
UjYt25UFyh8eL5t+8lPaWz/jRaPva+zGVUowogkEZMbo0UE6MpKiIlinTf9yMh6mvKpYMH8FAAD//yQs
JUakAQAA
`,
},
"/": {
isDir: true,
local: "",
},
"/public": {
isDir: true,
local: "public",
},
"/templates": {
isDir: true,
local: "templates",
},
}
-333
View File
@@ -1,333 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package zpages
import (
"fmt"
"io"
"log"
"math"
"net/http"
"sort"
"sync"
"text/tabwriter"
"time"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/stats/view"
)
const bytesPerKb = 1024
var (
programStartTime = time.Now()
mu sync.Mutex // protects snaps
snaps = make(map[methodKey]*statSnapshot)
// viewType lists the views we are interested in for RPC stats.
// A view's map value indicates whether that view contains data for received
// RPCs.
viewType = map[*view.View]bool{
ocgrpc.ClientCompletedRPCsView: false,
ocgrpc.ClientSentBytesPerRPCView: false,
ocgrpc.ClientSentMessagesPerRPCView: false,
ocgrpc.ClientReceivedBytesPerRPCView: false,
ocgrpc.ClientReceivedMessagesPerRPCView: false,
ocgrpc.ClientRoundtripLatencyView: false,
ocgrpc.ServerCompletedRPCsView: true,
ocgrpc.ServerReceivedBytesPerRPCView: true,
ocgrpc.ServerReceivedMessagesPerRPCView: true,
ocgrpc.ServerSentBytesPerRPCView: true,
ocgrpc.ServerSentMessagesPerRPCView: true,
ocgrpc.ServerLatencyView: true,
}
)
func registerRPCViews() {
views := make([]*view.View, 0, len(viewType))
for v := range viewType {
views = append(views, v)
}
if err := view.Register(views...); err != nil {
log.Printf("error subscribing to views: %v", err)
}
view.RegisterExporter(snapExporter{})
}
func rpczHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
WriteHTMLRpczPage(w)
}
// WriteHTMLRpczPage writes an HTML document to w containing per-method RPC stats.
func WriteHTMLRpczPage(w io.Writer) {
if err := headerTemplate.Execute(w, headerData{Title: "RPC Stats"}); err != nil {
log.Printf("zpages: executing template: %v", err)
}
WriteHTMLRpczSummary(w)
if err := footerTemplate.Execute(w, nil); err != nil {
log.Printf("zpages: executing template: %v", err)
}
}
// WriteHTMLRpczSummary writes HTML to w containing per-method RPC stats.
//
// It includes neither a header nor footer, so you can embed this data in other pages.
func WriteHTMLRpczSummary(w io.Writer) {
mu.Lock()
if err := statsTemplate.Execute(w, getStatsPage()); err != nil {
log.Printf("zpages: executing template: %v", err)
}
mu.Unlock()
}
// WriteTextRpczPage writes formatted text to w containing per-method RPC stats.
func WriteTextRpczPage(w io.Writer) {
mu.Lock()
defer mu.Unlock()
page := getStatsPage()
for i, sg := range page.StatGroups {
switch i {
case 0:
fmt.Fprint(w, "Sent:\n")
case 1:
fmt.Fprint(w, "\nReceived:\n")
}
tw := tabwriter.NewWriter(w, 6, 8, 1, ' ', 0)
fmt.Fprint(tw, "Method\tCount\t\t\tAvgLat\t\t\tMaxLat\t\t\tRate\t\t\tIn (MiB/s)\t\t\tOut (MiB/s)\t\t\tErrors\t\t\n")
fmt.Fprint(tw, "\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\n")
for _, s := range sg.Snapshots {
fmt.Fprintf(tw, "%s\t%d\t%d\t%d\t%v\t%v\t%v\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%d\t%d\t%d\n",
s.Method,
s.CountMinute,
s.CountHour,
s.CountTotal,
s.AvgLatencyMinute,
s.AvgLatencyHour,
s.AvgLatencyTotal,
s.RPCRateMinute,
s.RPCRateHour,
s.RPCRateTotal,
s.InputRateMinute/bytesPerKb,
s.InputRateHour/bytesPerKb,
s.InputRateTotal/bytesPerKb,
s.OutputRateMinute/bytesPerKb,
s.OutputRateHour/bytesPerKb,
s.OutputRateTotal/bytesPerKb,
s.ErrorsMinute,
s.ErrorsHour,
s.ErrorsTotal)
}
tw.Flush()
}
}
// headerData contains data for the header template.
type headerData struct {
Title string
}
// statsPage aggregates stats on the page for 'sent' and 'received' categories
type statsPage struct {
StatGroups []*statGroup
}
// statGroup aggregates snapshots for a directional category
type statGroup struct {
Direction string
Snapshots []*statSnapshot
}
func (s *statGroup) Len() int {
return len(s.Snapshots)
}
func (s *statGroup) Swap(i, j int) {
s.Snapshots[i], s.Snapshots[j] = s.Snapshots[j], s.Snapshots[i]
}
func (s *statGroup) Less(i, j int) bool {
return s.Snapshots[i].Method < s.Snapshots[j].Method
}
// statSnapshot holds the data items that are presented in a single row of RPC
// stat information.
type statSnapshot struct {
// TODO: compute hour/minute values from cumulative
Method string
Received bool
CountMinute uint64
CountHour uint64
CountTotal uint64
AvgLatencyMinute time.Duration
AvgLatencyHour time.Duration
AvgLatencyTotal time.Duration
RPCRateMinute float64
RPCRateHour float64
RPCRateTotal float64
InputRateMinute float64
InputRateHour float64
InputRateTotal float64
OutputRateMinute float64
OutputRateHour float64
OutputRateTotal float64
ErrorsMinute uint64
ErrorsHour uint64
ErrorsTotal uint64
}
type methodKey struct {
method string
received bool
}
type snapExporter struct{}
func (s snapExporter) ExportView(vd *view.Data) {
received, ok := viewType[vd.View]
if !ok {
return
}
if len(vd.Rows) == 0 {
return
}
ageSec := float64(time.Since(programStartTime)) / float64(time.Second)
computeRate := func(maxSec, x float64) float64 {
dur := ageSec
if maxSec > 0 && dur > maxSec {
dur = maxSec
}
return x / dur
}
convertTime := func(ms float64) time.Duration {
if math.IsInf(ms, 0) || math.IsNaN(ms) {
return 0
}
return time.Duration(float64(time.Millisecond) * ms)
}
haveResetErrors := make(map[string]struct{})
mu.Lock()
defer mu.Unlock()
for _, row := range vd.Rows {
var method string
for _, tag := range row.Tags {
if tag.Key == ocgrpc.KeyClientMethod || tag.Key == ocgrpc.KeyServerMethod {
method = tag.Value
break
}
}
key := methodKey{method: method, received: received}
s := snaps[key]
if s == nil {
s = &statSnapshot{Method: method, Received: received}
snaps[key] = s
}
var (
sum float64
count float64
)
switch v := row.Data.(type) {
case *view.CountData:
sum = float64(v.Value)
count = float64(v.Value)
case *view.DistributionData:
sum = v.Sum()
count = float64(v.Count)
case *view.SumData:
sum = v.Value
count = v.Value
}
// Update field of s corresponding to the view.
switch vd.View {
case ocgrpc.ClientCompletedRPCsView:
if _, ok := haveResetErrors[method]; !ok {
haveResetErrors[method] = struct{}{}
s.ErrorsTotal = 0
}
for _, tag := range row.Tags {
if tag.Key == ocgrpc.KeyClientStatus && tag.Value != "OK" {
s.ErrorsTotal += uint64(count)
}
}
case ocgrpc.ClientRoundtripLatencyView:
s.AvgLatencyTotal = convertTime(sum / count)
case ocgrpc.ClientSentBytesPerRPCView:
s.OutputRateTotal = computeRate(0, sum)
case ocgrpc.ClientReceivedBytesPerRPCView:
s.InputRateTotal = computeRate(0, sum)
case ocgrpc.ClientSentMessagesPerRPCView:
s.CountTotal = uint64(count)
s.RPCRateTotal = computeRate(0, count)
case ocgrpc.ClientReceivedMessagesPerRPCView:
// currently unused
case ocgrpc.ServerCompletedRPCsView:
if _, ok := haveResetErrors[method]; !ok {
haveResetErrors[method] = struct{}{}
s.ErrorsTotal = 0
}
for _, tag := range row.Tags {
if tag.Key == ocgrpc.KeyServerStatus && tag.Value != "OK" {
s.ErrorsTotal += uint64(count)
}
}
case ocgrpc.ServerLatencyView:
s.AvgLatencyTotal = convertTime(sum / count)
case ocgrpc.ServerSentBytesPerRPCView:
s.OutputRateTotal = computeRate(0, sum)
case ocgrpc.ServerReceivedMessagesPerRPCView:
s.CountTotal = uint64(count)
s.RPCRateTotal = computeRate(0, count)
case ocgrpc.ServerSentMessagesPerRPCView:
// currently unused
}
}
}
func getStatsPage() *statsPage {
sentStats := statGroup{Direction: "Sent"}
receivedStats := statGroup{Direction: "Received"}
for key, sg := range snaps {
if key.received {
receivedStats.Snapshots = append(receivedStats.Snapshots, sg)
} else {
sentStats.Snapshots = append(sentStats.Snapshots, sg)
}
}
sort.Sort(&sentStats)
sort.Sort(&receivedStats)
return &statsPage{
StatGroups: []*statGroup{&sentStats, &receivedStats},
}
}
-125
View File
@@ -1,125 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package zpages
import (
"fmt"
"html/template"
"io/ioutil"
"log"
"strconv"
"time"
"go.opencensus.io/trace"
"go.opencensus.io/zpages/internal"
)
var (
fs = internal.FS(false)
templateFunctions = template.FuncMap{
"count": countFormatter,
"ms": msFormatter,
"rate": rateFormatter,
"datarate": dataRateFormatter,
"even": even,
"traceid": traceIDFormatter,
}
headerTemplate = parseTemplate("header")
summaryTableTemplate = parseTemplate("summary")
statsTemplate = parseTemplate("rpcz")
tracesTableTemplate = parseTemplate("traces")
footerTemplate = parseTemplate("footer")
)
func parseTemplate(name string) *template.Template {
f, err := fs.Open("/templates/" + name + ".html")
if err != nil {
log.Panicf("%v: %v", name, err)
}
defer f.Close()
text, err := ioutil.ReadAll(f)
if err != nil {
log.Panicf("%v: %v", name, err)
}
return template.Must(template.New(name).Funcs(templateFunctions).Parse(string(text)))
}
func countFormatter(num uint64) string {
if num <= 0 {
return " "
}
var floatVal float64
var suffix string
if num >= 1e18 {
floatVal = float64(num) / 1e18
suffix = " E "
} else if num >= 1e15 {
floatVal = float64(num) / 1e15
suffix = " P "
} else if num >= 1e12 {
floatVal = float64(num) / 1e12
suffix = " T "
} else if num >= 1e9 {
floatVal = float64(num) / 1e9
suffix = " G "
} else if num >= 1e6 {
floatVal = float64(num) / 1e6
suffix = " M "
}
if floatVal != 0 {
return fmt.Sprintf("%1.3f%s", floatVal, suffix)
}
return fmt.Sprint(num)
}
func msFormatter(d time.Duration) string {
if d == 0 {
return "0"
}
if d < 10*time.Millisecond {
return fmt.Sprintf("%.3f", float64(d)*1e-6)
}
return strconv.Itoa(int(d / time.Millisecond))
}
func rateFormatter(r float64) string {
return fmt.Sprintf("%.3f", r)
}
func dataRateFormatter(b float64) string {
return fmt.Sprintf("%.3f", b/1e6)
}
func traceIDFormatter(r traceRow) template.HTML {
sc := r.SpanContext
if sc == (trace.SpanContext{}) {
return ""
}
col := "black"
if sc.TraceOptions.IsSampled() {
col = "blue"
}
if r.ParentSpanID != (trace.SpanID{}) {
return template.HTML(fmt.Sprintf(`trace_id: <b style="color:%s">%s</b> span_id: %s parent_span_id: %s`, col, sc.TraceID, sc.SpanID, r.ParentSpanID))
}
return template.HTML(fmt.Sprintf(`trace_id: <b style="color:%s">%s</b> span_id: %s`, col, sc.TraceID, sc.SpanID))
}
func even(x int) bool {
return x%2 == 0
}
-442
View File
@@ -1,442 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package zpages
import (
"fmt"
"io"
"log"
"net/http"
"sort"
"strconv"
"strings"
"text/tabwriter"
"time"
"go.opencensus.io/internal"
"go.opencensus.io/trace"
)
const (
// spanNameQueryField is the header for span name.
spanNameQueryField = "zspanname"
// spanTypeQueryField is the header for type (running = 0, latency = 1, error = 2) to display.
spanTypeQueryField = "ztype"
// spanSubtypeQueryField is the header for sub-type:
// * for latency based samples [0, 8] representing the latency buckets, where 0 is the first one;
// * for error based samples, 0 means all, otherwise the error code;
spanSubtypeQueryField = "zsubtype"
// maxTraceMessageLength is the maximum length of a message in tracez output.
maxTraceMessageLength = 1024
)
var (
defaultLatencies = [...]time.Duration{
10 * time.Microsecond,
100 * time.Microsecond,
time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
time.Second,
10 * time.Second,
100 * time.Second,
}
canonicalCodes = [...]string{
"OK",
"CANCELLED",
"UNKNOWN",
"INVALID_ARGUMENT",
"DEADLINE_EXCEEDED",
"NOT_FOUND",
"ALREADY_EXISTS",
"PERMISSION_DENIED",
"RESOURCE_EXHAUSTED",
"FAILED_PRECONDITION",
"ABORTED",
"OUT_OF_RANGE",
"UNIMPLEMENTED",
"INTERNAL",
"UNAVAILABLE",
"DATA_LOSS",
"UNAUTHENTICATED",
}
)
func canonicalCodeString(code int32) string {
if code < 0 || int(code) >= len(canonicalCodes) {
return "error code " + strconv.FormatInt(int64(code), 10)
}
return canonicalCodes[code]
}
func tracezHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
name := r.Form.Get(spanNameQueryField)
t, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField))
st, _ := strconv.Atoi(r.Form.Get(spanSubtypeQueryField))
WriteHTMLTracezPage(w, name, t, st)
}
// WriteHTMLTracezPage writes an HTML document to w containing locally-sampled trace spans.
func WriteHTMLTracezPage(w io.Writer, spanName string, spanType, spanSubtype int) {
if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil {
log.Printf("zpages: executing template: %v", err)
}
WriteHTMLTracezSummary(w)
WriteHTMLTracezSpans(w, spanName, spanType, spanSubtype)
if err := footerTemplate.Execute(w, nil); err != nil {
log.Printf("zpages: executing template: %v", err)
}
}
// WriteHTMLTracezSummary writes HTML to w containing a summary of locally-sampled trace spans.
//
// It includes neither a header nor footer, so you can embed this data in other pages.
func WriteHTMLTracezSummary(w io.Writer) {
if err := summaryTableTemplate.Execute(w, getSummaryPageData()); err != nil {
log.Printf("zpages: executing template: %v", err)
}
}
// WriteHTMLTracezSpans writes HTML to w containing locally-sampled trace spans.
//
// It includes neither a header nor footer, so you can embed this data in other pages.
func WriteHTMLTracezSpans(w io.Writer, spanName string, spanType, spanSubtype int) {
if spanName == "" {
return
}
if err := tracesTableTemplate.Execute(w, traceDataFromSpans(spanName, traceSpans(spanName, spanType, spanSubtype))); err != nil {
log.Printf("zpages: executing template: %v", err)
}
}
// WriteTextTracezSpans writes formatted text to w containing locally-sampled trace spans.
func WriteTextTracezSpans(w io.Writer, spanName string, spanType, spanSubtype int) {
spans := traceSpans(spanName, spanType, spanSubtype)
data := traceDataFromSpans(spanName, spans)
writeTextTraces(w, data)
}
// WriteTextTracezSummary writes formatted text to w containing a summary of locally-sampled trace spans.
func WriteTextTracezSummary(w io.Writer) {
w.Write([]byte("Locally sampled spans summary\n\n"))
data := getSummaryPageData()
if len(data.Rows) == 0 {
return
}
tw := tabwriter.NewWriter(w, 8, 8, 1, ' ', 0)
for i, s := range data.Header {
if i != 0 {
tw.Write([]byte("\t"))
}
tw.Write([]byte(s))
}
tw.Write([]byte("\n"))
put := func(x int) {
if x == 0 {
tw.Write([]byte(".\t"))
return
}
fmt.Fprintf(tw, "%d\t", x)
}
for _, r := range data.Rows {
tw.Write([]byte(r.Name))
tw.Write([]byte("\t"))
put(r.Active)
for _, l := range r.Latency {
put(l)
}
put(r.Errors)
tw.Write([]byte("\n"))
}
tw.Flush()
}
// traceData contains data for the trace data template.
type traceData struct {
Name string
Num int
Rows []traceRow
}
type traceRow struct {
Fields [3]string
trace.SpanContext
ParentSpanID trace.SpanID
}
type events []interface{}
func (e events) Len() int { return len(e) }
func (e events) Less(i, j int) bool {
var ti time.Time
switch x := e[i].(type) {
case *trace.Annotation:
ti = x.Time
case *trace.MessageEvent:
ti = x.Time
}
switch x := e[j].(type) {
case *trace.Annotation:
return ti.Before(x.Time)
case *trace.MessageEvent:
return ti.Before(x.Time)
}
return false
}
func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func traceRows(s *trace.SpanData) []traceRow {
start := s.StartTime
lasty, lastm, lastd := start.Date()
wholeTime := func(t time.Time) string {
return t.Format("2006/01/02-15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
}
formatTime := func(t time.Time) string {
y, m, d := t.Date()
if y == lasty && m == lastm && d == lastd {
return t.Format(" 15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
}
lasty, lastm, lastd = y, m, d
return wholeTime(t)
}
lastTime := start
formatElapsed := func(t time.Time) string {
d := t.Sub(lastTime)
lastTime = t
u := int64(d / 1000)
// There are five cases for duration printing:
// -1234567890s
// -1234.123456
// .123456
// 12345.123456
// 12345678901s
switch {
case u < -9999999999:
return fmt.Sprintf("%11ds", u/1e6)
case u < 0:
sec := u / 1e6
u -= sec * 1e6
return fmt.Sprintf("%5d.%06d", sec, -u)
case u < 1e6:
return fmt.Sprintf(" .%6d", u)
case u <= 99999999999:
sec := u / 1e6
u -= sec * 1e6
return fmt.Sprintf("%5d.%06d", sec, u)
default:
return fmt.Sprintf("%11ds", u/1e6)
}
}
firstRow := traceRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext, ParentSpanID: s.ParentSpanID}
if s.EndTime.IsZero() {
firstRow.Fields[1] = " "
} else {
firstRow.Fields[1] = formatElapsed(s.EndTime)
lastTime = start
}
out := []traceRow{firstRow}
formatAttributes := func(a map[string]interface{}) string {
if len(a) == 0 {
return ""
}
var keys []string
for key := range a {
keys = append(keys, key)
}
sort.Strings(keys)
var s []string
for _, key := range keys {
val := a[key]
switch val.(type) {
case string:
s = append(s, fmt.Sprintf("%s=%q", key, val))
default:
s = append(s, fmt.Sprintf("%s=%v", key, val))
}
}
return "Attributes:{" + strings.Join(s, ", ") + "}"
}
if s.Status != (trace.Status{}) {
msg := fmt.Sprintf("Status{canonicalCode=%s, description=%q}",
canonicalCodeString(s.Status.Code), s.Status.Message)
out = append(out, traceRow{Fields: [3]string{"", "", msg}})
}
if len(s.Attributes) != 0 {
out = append(out, traceRow{Fields: [3]string{"", "", formatAttributes(s.Attributes)}})
}
var es events
for i := range s.Annotations {
es = append(es, &s.Annotations[i])
}
for i := range s.MessageEvents {
es = append(es, &s.MessageEvents[i])
}
sort.Sort(es)
for _, e := range es {
switch e := e.(type) {
case *trace.Annotation:
msg := e.Message
if len(e.Attributes) != 0 {
msg = msg + " " + formatAttributes(e.Attributes)
}
row := traceRow{Fields: [3]string{
formatTime(e.Time),
formatElapsed(e.Time),
msg,
}}
out = append(out, row)
case *trace.MessageEvent:
row := traceRow{Fields: [3]string{formatTime(e.Time), formatElapsed(e.Time)}}
switch e.EventType {
case trace.MessageEventTypeSent:
row.Fields[2] = fmt.Sprintf("sent message [%d bytes, %d compressed bytes]", e.UncompressedByteSize, e.CompressedByteSize)
case trace.MessageEventTypeRecv:
row.Fields[2] = fmt.Sprintf("received message [%d bytes, %d compressed bytes]", e.UncompressedByteSize, e.CompressedByteSize)
}
out = append(out, row)
}
}
for i := range out {
if len(out[i].Fields[2]) > maxTraceMessageLength {
out[i].Fields[2] = out[i].Fields[2][:maxTraceMessageLength]
}
}
return out
}
func traceSpans(spanName string, spanType, spanSubtype int) []*trace.SpanData {
internalTrace := internal.Trace.(interface {
ReportActiveSpans(name string) []*trace.SpanData
ReportSpansByError(name string, code int32) []*trace.SpanData
ReportSpansByLatency(name string, minLatency, maxLatency time.Duration) []*trace.SpanData
})
var spans []*trace.SpanData
switch spanType {
case 0: // active
spans = internalTrace.ReportActiveSpans(spanName)
case 1: // latency
var min, max time.Duration
n := len(defaultLatencies)
if spanSubtype == 0 {
max = defaultLatencies[0]
} else if spanSubtype == n {
min, max = defaultLatencies[spanSubtype-1], (1<<63)-1
} else if 0 < spanSubtype && spanSubtype < n {
min, max = defaultLatencies[spanSubtype-1], defaultLatencies[spanSubtype]
}
spans = internalTrace.ReportSpansByLatency(spanName, min, max)
case 2: // error
spans = internalTrace.ReportSpansByError(spanName, 0)
}
return spans
}
func traceDataFromSpans(name string, spans []*trace.SpanData) traceData {
data := traceData{
Name: name,
Num: len(spans),
}
for _, s := range spans {
data.Rows = append(data.Rows, traceRows(s)...)
}
return data
}
func writeTextTraces(w io.Writer, data traceData) {
tw := tabwriter.NewWriter(w, 1, 8, 1, ' ', 0)
fmt.Fprint(tw, "When\tElapsed(s)\tType\n")
for _, r := range data.Rows {
tw.Write([]byte(r.Fields[0]))
tw.Write([]byte("\t"))
tw.Write([]byte(r.Fields[1]))
tw.Write([]byte("\t"))
tw.Write([]byte(r.Fields[2]))
if sc := r.SpanContext; sc != (trace.SpanContext{}) {
fmt.Fprintf(tw, "trace_id: %s span_id: %s", sc.TraceID, sc.SpanID)
if r.ParentSpanID != (trace.SpanID{}) {
fmt.Fprintf(tw, " parent_span_id: %s", r.ParentSpanID)
}
}
tw.Write([]byte("\n"))
}
tw.Flush()
}
type summaryPageData struct {
Header []string
LatencyBucketNames []string
Links bool
TracesEndpoint string
Rows []summaryPageRow
}
type summaryPageRow struct {
Name string
Active int
Latency []int
Errors int
}
func getSummaryPageData() summaryPageData {
data := summaryPageData{
Links: true,
TracesEndpoint: "tracez",
}
internalTrace := internal.Trace.(interface {
ReportSpansPerMethod() map[string]internal.PerMethodSummary
})
for name, s := range internalTrace.ReportSpansPerMethod() {
if len(data.Header) == 0 {
data.Header = []string{"Name", "Active"}
for _, b := range s.LatencyBuckets {
l := b.MinLatency
s := fmt.Sprintf(">%v", l)
if l == 100*time.Second {
s = ">100s"
}
data.Header = append(data.Header, s)
data.LatencyBucketNames = append(data.LatencyBucketNames, s)
}
data.Header = append(data.Header, "Errors")
}
row := summaryPageRow{Name: name, Active: s.Active}
for _, l := range s.LatencyBuckets {
row.Latency = append(row.Latency, l.Size)
}
for _, e := range s.ErrorBuckets {
row.Errors += e.Size
}
data.Rows = append(data.Rows, row)
}
sort.Slice(data.Rows, func(i, j int) bool {
return data.Rows[i].Name < data.Rows[j].Name
})
return data
}
-70
View File
@@ -1,70 +0,0 @@
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Package zpages implements a collection of HTML pages that display RPC stats
// and trace data, and also functions to write that same data in plain text to
// an io.Writer.
//
// Users can also embed the HTML for stats and traces in custom status pages.
//
// zpages are currrently work-in-process and cannot display minutely and
// hourly stats correctly.
//
// # Performance
//
// Installing the zpages has a performance overhead because additional traces
// and stats will be collected in-process. In most cases, we expect this
// overhead will not be significant but it depends on many factors, including
// how many spans your process creates and how richly annotated they are.
package zpages // import "go.opencensus.io/zpages"
import (
"net/http"
"path"
"sync"
"go.opencensus.io/internal"
)
// TODO(ramonza): Remove Handler to make initialization lazy.
// Handler is deprecated: Use Handle.
var Handler http.Handler
func init() {
mux := http.NewServeMux()
Handle(mux, "/")
Handler = mux
}
// Handle adds the z-pages to the given ServeMux rooted at pathPrefix.
func Handle(mux *http.ServeMux, pathPrefix string) {
enable()
if mux == nil {
mux = http.DefaultServeMux
}
mux.HandleFunc(path.Join(pathPrefix, "rpcz"), rpczHandler)
mux.HandleFunc(path.Join(pathPrefix, "tracez"), tracezHandler)
mux.Handle(path.Join(pathPrefix, "public/"), http.FileServer(fs))
}
var enableOnce sync.Once
func enable() {
enableOnce.Do(func() {
internal.LocalSpanStoreEnabled = true
registerRPCViews()
})
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+61
View File
@@ -0,0 +1,61 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package zpages // import "go.opentelemetry.io/contrib/zpages"
import (
"sort"
"time"
)
const zeroDuration = time.Duration(0)
const maxDuration = time.Duration(1<<63 - 1)
var defaultBoundaries = newBoundaries([]time.Duration{
10 * time.Microsecond,
100 * time.Microsecond,
time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
time.Second,
10 * time.Second,
100 * time.Second,
})
// boundaries represents the interval bounds for the latency based samples.
type boundaries struct {
durations []time.Duration
}
// newBoundaries returns a new boundaries.
func newBoundaries(durations []time.Duration) *boundaries {
sort.Slice(durations, func(i, j int) bool {
return durations[i] < durations[j]
})
return &boundaries{durations: durations}
}
// numBuckets returns the number of buckets needed for these boundaries.
func (lb boundaries) numBuckets() int {
return len(lb.durations) + 1
}
// getBucketIndex returns the appropriate bucket index for a given latency.
func (lb boundaries) getBucketIndex(latency time.Duration) int {
i := 0
for i < len(lb.durations) && latency >= lb.durations[i] {
i++
}
return i
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright The OpenTelemetry Authors
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package zpages // import "go.opentelemetry.io/contrib/zpages"
import (
"time"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
const (
// defaultBucketCapacity is the default capacity for every bucket (latency or error based).
defaultBucketCapacity = 10
// samplePeriod is the minimum time between accepting spans in a single bucket.
samplePeriod = time.Second
)
// bucket is a container for a set of spans for latency buckets or errored spans.
type bucket struct {
nextTime time.Time // next time we can accept a span
buffer []sdktrace.ReadOnlySpan // circular buffer of spans
nextIndex int // location next ReadOnlySpan should be placed in buffer
overflow bool // whether the circular buffer has wrapped around
}
// newBucket returns a new bucket with the given capacity.
func newBucket(capacity uint) *bucket {
return &bucket{
buffer: make([]sdktrace.ReadOnlySpan, capacity),
}
}
// add adds a span to the bucket, if nextTime has been reached.
func (b *bucket) add(s sdktrace.ReadOnlySpan) {
if s.EndTime().Before(b.nextTime) {
return
}
if len(b.buffer) == 0 {
return
}
b.nextTime = s.EndTime().Add(samplePeriod)
b.buffer[b.nextIndex] = s
b.nextIndex++
if b.nextIndex == len(b.buffer) {
b.nextIndex = 0
b.overflow = true
}
}
// len returns the number of spans in the bucket.
func (b *bucket) len() int {
if b.overflow {
return len(b.buffer)
}
return b.nextIndex
}
// spans returns the spans in this bucket.
func (b *bucket) spans() []sdktrace.ReadOnlySpan {
return append([]sdktrace.ReadOnlySpan(nil), b.buffer[0:b.len()]...)
}
@@ -1,4 +1,4 @@
// Copyright 2018, OpenCensus Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,9 +11,10 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package internal // import "go.opencensus.io/zpages/internal"
package internal // import "go.opentelemetry.io/contrib/zpages/internal"
// go get https://github.com/mjibson/esc.git
//go:generate esc -pkg internal -o resources.go public/ templates/
import "embed"
//go:embed templates/*
var Templates embed.FS
@@ -0,0 +1,2 @@
</body>
</html>
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<title>{{.Title}}</title>
<link rel="shortcut icon" href="https://opentelemetry.io/favicons/favicon.ico"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
</head>
<body>
<h1>{{.Title}}</h1>
@@ -0,0 +1,43 @@
<table style="border-spacing: 0">
<tr>
<td colspan=1 align=left><b>Span Name</b></td>
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td><td colspan=1 align="center"><b>Running</b></td>
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
<td colspan=9 align="center"><b>Latency Samples</b></td>
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
<td colspan=1 align="center"><b>Error Samples</b></td>
</tr>
<tr>
<td colspan=1></td>
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
<td colspan=1></td>
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
{{range .LatencyBucketNames}}<th colspan=1 align="center"><b>[{{.}}]</b></th>{{end}}
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
<td colspan=1></td>
</tr>
{{$a := .TracesEndpoint}}
{{$links := .Links}}
{{range $rowindex, $row := .Rows}}
{{- $name := .Name}}
{{- if even $rowindex}}<tr style="background: #eee">{{else}}<tr>{{end -}}
<td>{{.Name}}</td><td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
{{- if $links -}}
<td align="center"><a href="{{$a}}?zspanname={{$name}}&ztype=0">{{.Active}}</a></td>
{{- else -}}
<td>{{.Active}}</td>
{{- end -}}
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
{{- if $links -}}
{{range $index, $value := .Latency}}<td align="center"><a href="{{$a}}?zspanname={{$name}}&ztype=1&zlatencybucket={{$index}}">{{$value}}</a></td>{{end}}
{{- else -}}
{{range .Latency}}<td>{{.}}</td>{{end}}
{{- end -}}
<td>&nbsp;&nbsp;|&nbsp;&nbsp;</td>
{{- if $links -}}
<td align="center"><a href="{{$a}}?zspanname={{$name}}&ztype=2&zlatencybucket=0">{{.Errors}}</td>
{{- else -}}
<td>{{.Errors}}</td>
{{- end -}}
</tr>
{{end}}</table>
@@ -0,0 +1,10 @@
<p><b>Span Name: {{.Name}} </b></p>
<p>{{.Num}} Requests</p>
<pre>
When Elapsed (sec)
----------------------------------------
{{range .Rows}}{{printf "%26s" (index .Fields 0)}} {{printf "%12s" (index .Fields 1)}} {{index .Fields 2}}{{.|spanRow}}
{{end}}</pre>
<br>
<p><b style="color:blue;">TraceId</b> means sampled request.
<b style="color:black;">TraceId</b> means not sampled request.</p>
+224
View File
@@ -0,0 +1,224 @@
// Copyright The OpenTelemetry Authors
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package zpages // import "go.opentelemetry.io/contrib/zpages"
import (
"context"
"sync"
"go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
var _ sdktrace.SpanProcessor = (*SpanProcessor)(nil)
// perMethodSummary is a summary of the spans stored for a single span name.
type perMethodSummary struct {
activeSpans int
latencySpans []int
errorSpans int
}
// SpanProcessor is an sdktrace.SpanProcessor implementation that exposes zpages functionality for opentelemetry-go.
//
// It tracks all active spans, and stores samples of spans based on latency for non errored spans,
// and samples for errored spans.
type SpanProcessor struct {
// Cannot keep track of the active Spans per name because the Span interface,
// allows the name to be changed, and that will leak memory.
activeSpansStore sync.Map
spanSampleStores sync.Map
}
// NewSpanProcessor returns a new SpanProcessor.
func NewSpanProcessor() *SpanProcessor {
return &SpanProcessor{}
}
// OnStart adds span as active and reports it with zpages.
func (ssm *SpanProcessor) OnStart(_ context.Context, span sdktrace.ReadWriteSpan) {
sc := span.SpanContext()
if sc.IsValid() {
ssm.activeSpansStore.Store(spanKey(sc), span)
}
}
// OnEnd processes all spans and reports them with zpages.
func (ssm *SpanProcessor) OnEnd(span sdktrace.ReadOnlySpan) {
sc := span.SpanContext()
if sc.IsValid() {
ssm.activeSpansStore.Delete(spanKey(sc))
}
name := span.Name()
value, ok := ssm.spanSampleStores.Load(name)
if !ok {
value, _ = ssm.spanSampleStores.LoadOrStore(name, newSampleStore(defaultBucketCapacity, defaultBucketCapacity))
}
value.(*sampleStore).sampleSpan(span)
}
// Shutdown does nothing.
func (ssm *SpanProcessor) Shutdown(context.Context) error {
// Do nothing
return nil
}
// ForceFlush does nothing.
func (ssm *SpanProcessor) ForceFlush(context.Context) error {
// Do nothing
return nil
}
// spanStoreForName returns the sampleStore for the given name.
//
// It returns nil if it doesn't exist.
func (ssm *SpanProcessor) spanStoreForName(name string) *sampleStore {
if value, ok := ssm.spanSampleStores.Load(name); ok {
return value.(*sampleStore)
}
return nil
}
// spansPerMethod returns a summary of what spans are being stored for each span name.
func (ssm *SpanProcessor) spansPerMethod() map[string]*perMethodSummary {
out := make(map[string]*perMethodSummary)
ssm.spanSampleStores.Range(func(name, s interface{}) bool {
out[name.(string)] = s.(*sampleStore).perMethodSummary()
return true
})
ssm.activeSpansStore.Range(func(_, sp interface{}) bool {
span := sp.(sdktrace.ReadOnlySpan)
if pms, ok := out[span.Name()]; ok {
pms.activeSpans++
return true
}
out[span.Name()] = &perMethodSummary{activeSpans: 1}
return true
})
return out
}
// activeSpans returns the active spans for the given name.
func (ssm *SpanProcessor) activeSpans(name string) []sdktrace.ReadOnlySpan {
var out []sdktrace.ReadOnlySpan
ssm.activeSpansStore.Range(func(_, sp interface{}) bool {
span := sp.(sdktrace.ReadOnlySpan)
if span.Name() == name {
out = append(out, span)
}
return true
})
return out
}
// errorSpans returns a sample of error spans.
func (ssm *SpanProcessor) errorSpans(name string) []sdktrace.ReadOnlySpan {
s := ssm.spanStoreForName(name)
if s == nil {
return nil
}
return s.errorSpans()
}
// spansByLatency returns a sample of successful spans.
//
// minLatency is the minimum latency of spans to be returned.
// maxDuration, if nonzero, is the maximum latency of spans to be returned.
func (ssm *SpanProcessor) spansByLatency(name string, latencyBucketIndex int) []sdktrace.ReadOnlySpan {
s := ssm.spanStoreForName(name)
if s == nil {
return nil
}
return s.spansByLatency(latencyBucketIndex)
}
// sampleStore stores a sampled of spans for a particular span name.
//
// It contains sample of spans for error requests (status code is codes.Error);
// and a sample of spans for successful requests, bucketed by latency.
type sampleStore struct {
sync.Mutex // protects everything below.
latency []*bucket
errors *bucket
}
// newSampleStore creates a sampleStore.
func newSampleStore(latencyBucketSize uint, errorBucketSize uint) *sampleStore {
s := &sampleStore{
latency: make([]*bucket, defaultBoundaries.numBuckets()),
errors: newBucket(errorBucketSize),
}
for i := range s.latency {
s.latency[i] = newBucket(latencyBucketSize)
}
return s
}
func (ss *sampleStore) perMethodSummary() *perMethodSummary {
ss.Lock()
defer ss.Unlock()
p := &perMethodSummary{}
p.errorSpans = ss.errors.len()
for _, b := range ss.latency {
p.latencySpans = append(p.latencySpans, b.len())
}
return p
}
func (ss *sampleStore) spansByLatency(latencyBucketIndex int) []sdktrace.ReadOnlySpan {
ss.Lock()
defer ss.Unlock()
if latencyBucketIndex < 0 || latencyBucketIndex >= len(ss.latency) {
return nil
}
return ss.latency[latencyBucketIndex].spans()
}
func (ss *sampleStore) errorSpans() []sdktrace.ReadOnlySpan {
ss.Lock()
defer ss.Unlock()
return ss.errors.spans()
}
// sampleSpan removes adds to the corresponding latency or error bucket.
func (ss *sampleStore) sampleSpan(span sdktrace.ReadOnlySpan) {
code := span.Status().Code
ss.Lock()
defer ss.Unlock()
if code == codes.Error {
ss.errors.add(span)
return
}
latency := span.EndTime().Sub(span.StartTime())
// In case of time skew or wrong time, sample as 0 latency.
if latency < 0 {
latency = 0
}
ss.latency[defaultBoundaries.getBucketIndex(latency)].add(span)
}
func spanKey(sc trace.SpanContext) [24]byte {
var sk [24]byte
tid := sc.TraceID()
copy(sk[0:16], tid[:])
sid := sc.SpanID()
copy(sk[16:24], sid[:])
return sk
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright The OpenTelemetry Authors
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package zpages // import "go.opentelemetry.io/contrib/zpages"
import (
"fmt"
"html/template"
"io"
"log"
"go.opentelemetry.io/contrib/zpages/internal"
)
var (
templateFunctions = template.FuncMap{
"even": even,
"spanRow": spanRowFormatter,
}
headerTemplate = parseTemplate("header")
summaryTableTemplate = parseTemplate("summary")
tracesTableTemplate = parseTemplate("traces")
footerTemplate = parseTemplate("footer")
)
// headerData contains data for the header template.
type headerData struct {
Title string
}
func parseTemplate(name string) *template.Template {
f, err := internal.Templates.Open("templates/" + name + ".html")
if err != nil {
log.Panicf("%v: %v", name, err) // nolint: revive // Called during initialization.
}
defer func() {
if err = f.Close(); err != nil {
log.Panicf("%v: %v", name, err) // nolint: revive // Called during initialization.
}
}()
text, err := io.ReadAll(f)
if err != nil {
log.Panicf("%v: %v", name, err) // nolint: revive // Called during initialization.
}
return template.Must(template.New(name).Funcs(templateFunctions).Parse(string(text)))
}
func spanRowFormatter(r spanRow) template.HTML {
if !r.SpanContext.IsValid() {
return ""
}
col := "black"
if r.SpanContext.IsSampled() {
col = "blue"
}
if r.ParentSpanContext.IsValid() {
return template.HTML(fmt.Sprintf(`trace_id: <b style="color:%s">%s</b> span_id: %s parent_span_id: %s`, col, r.SpanContext.TraceID(), r.SpanContext.SpanID(), r.ParentSpanContext.SpanID()))
}
return template.HTML(fmt.Sprintf(`trace_id: <b style="color:%s">%s</b> span_id: %s`, col, r.SpanContext.TraceID(), r.SpanContext.SpanID()))
}
func even(x int) bool {
return x%2 == 0
}
+261
View File
@@ -0,0 +1,261 @@
// Copyright The OpenTelemetry Authors
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package zpages // import "go.opentelemetry.io/contrib/zpages"
import (
"fmt"
"log"
"net/http"
"sort"
"strconv"
"strings"
"time"
"go.opentelemetry.io/otel/attribute"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
const (
// spanNameQueryField is the header for span name.
spanNameQueryField = "zspanname"
// spanTypeQueryField is the header for type (running = 0, latency = 1, error = 2) to display.
spanTypeQueryField = "ztype"
// spanLatencyBucketQueryField is the header for latency based samples.
// Default is [0, 8] representing the latency buckets, where 0 is the first one.
spanLatencyBucketQueryField = "zlatencybucket"
// maxTraceMessageLength is the maximum length of a message in tracez output.
maxTraceMessageLength = 1024
)
type summaryTableData struct {
Header []string
LatencyBucketNames []string
Links bool
TracesEndpoint string
Rows []summaryTableRowData
}
type summaryTableRowData struct {
Name string
Active int
Latency []int
Errors int
}
// traceTableData contains data for the trace data template.
type traceTableData struct {
Name string
Num int
Rows []spanRow
}
var _ http.Handler = (*tracezHandler)(nil)
type tracezHandler struct {
sp *SpanProcessor
}
// NewTracezHandler returns an http.Handler that can be used to serve HTTP requests for trace zpages.
func NewTracezHandler(sp *SpanProcessor) http.Handler {
return &tracezHandler{sp: sp}
}
// ServeHTTP implements the http.Handler and is capable of serving "tracez" HTTP requests.
func (th *tracezHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
spanName := r.Form.Get(spanNameQueryField)
spanType, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField))
spanSubtype, _ := strconv.Atoi(r.Form.Get(spanLatencyBucketQueryField))
if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil {
log.Printf("zpages: executing template: %v", err)
}
if err := summaryTableTemplate.Execute(w, th.getSummaryTableData()); err != nil {
log.Printf("zpages: executing template: %v", err)
}
if spanName != "" {
if err := tracesTableTemplate.Execute(w, th.getTraceTableData(spanName, spanType, spanSubtype)); err != nil {
log.Printf("zpages: executing template: %v", err)
}
}
if err := footerTemplate.Execute(w, nil); err != nil {
log.Printf("zpages: executing template: %v", err)
}
}
func (th *tracezHandler) getTraceTableData(spanName string, spanType, latencyBucket int) traceTableData {
var spans []sdktrace.ReadOnlySpan
switch spanType {
case 0: // active
spans = th.sp.activeSpans(spanName)
case 1: // latency
spans = th.sp.spansByLatency(spanName, latencyBucket)
case 2: // error
spans = th.sp.errorSpans(spanName)
}
data := traceTableData{
Name: spanName,
Num: len(spans),
}
for _, s := range spans {
data.Rows = append(data.Rows, spanRows(s)...)
}
return data
}
func (th *tracezHandler) getSummaryTableData() summaryTableData {
data := summaryTableData{
Links: true,
TracesEndpoint: "tracez",
}
data.Header = []string{"Name", "active"}
// An implicit 0 lower bound latency bucket is always present.
latencyBuckets := append([]time.Duration{0}, defaultBoundaries.durations...)
for _, l := range latencyBuckets {
s := fmt.Sprintf(">%v", l)
data.Header = append(data.Header, s)
data.LatencyBucketNames = append(data.LatencyBucketNames, s)
}
data.Header = append(data.Header, "Errors")
for name, s := range th.sp.spansPerMethod() {
row := summaryTableRowData{Name: name, Active: s.activeSpans, Errors: s.errorSpans, Latency: s.latencySpans}
data.Rows = append(data.Rows, row)
}
sort.Slice(data.Rows, func(i, j int) bool {
return data.Rows[i].Name < data.Rows[j].Name
})
return data
}
type spanRow struct {
Fields [3]string
trace.SpanContext
ParentSpanContext trace.SpanContext
}
type events []sdktrace.Event
func (e events) Len() int { return len(e) }
func (e events) Less(i, j int) bool {
return e[i].Time.Before(e[j].Time)
}
func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
type attributes []attribute.KeyValue
func (e attributes) Len() int { return len(e) }
func (e attributes) Less(i, j int) bool {
return string(e[i].Key) < string(e[j].Key)
}
func (e attributes) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func spanRows(s sdktrace.ReadOnlySpan) []spanRow {
start := s.StartTime()
lasty, lastm, lastd := start.Date()
wholeTime := func(t time.Time) string {
return t.Format("2006/01/02-15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
}
formatTime := func(t time.Time) string {
y, m, d := t.Date()
if y == lasty && m == lastm && d == lastd {
return t.Format(" 15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
}
lasty, lastm, lastd = y, m, d
return wholeTime(t)
}
lastTime := start
formatElapsed := func(t time.Time) string {
d := t.Sub(lastTime)
lastTime = t
u := int64(d / 1000)
// There are five cases for duration printing:
// -1234567890s
// -1234.123456
// .123456
// 12345.123456
// 12345678901s
switch {
case u < -9999999999:
return fmt.Sprintf("%11ds", u/1e6)
case u < 0:
sec := u / 1e6
u -= sec * 1e6
return fmt.Sprintf("%5d.%06d", sec, -u)
case u < 1e6:
return fmt.Sprintf(" .%6d", u)
case u <= 99999999999:
sec := u / 1e6
u -= sec * 1e6
return fmt.Sprintf("%5d.%06d", sec, u)
default:
return fmt.Sprintf("%11ds", u/1e6)
}
}
firstRow := spanRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext(), ParentSpanContext: s.Parent()}
if s.EndTime().IsZero() {
firstRow.Fields[1] = " "
} else {
firstRow.Fields[1] = formatElapsed(s.EndTime())
lastTime = start
}
out := []spanRow{firstRow}
formatAttributes := func(a attributes) string {
sort.Sort(a)
var s []string
for i := range a {
s = append(s, fmt.Sprintf("%s=%v", a[i].Key, a[i].Value.Emit()))
}
return "Attributes:{" + strings.Join(s, ", ") + "}"
}
msg := fmt.Sprintf("Status{Code=%s, description=%q}", s.Status().Code.String(), s.Status().Description)
out = append(out, spanRow{Fields: [3]string{"", "", msg}})
if len(s.Attributes()) != 0 {
out = append(out, spanRow{Fields: [3]string{"", "", formatAttributes(s.Attributes())}})
}
es := events(s.Events())
sort.Sort(es)
for _, e := range es {
msg := e.Name
if len(e.Attributes) != 0 {
msg = msg + " " + formatAttributes(e.Attributes)
}
row := spanRow{Fields: [3]string{
formatTime(e.Time),
formatElapsed(e.Time),
msg,
}}
out = append(out, row)
}
for i := range out {
if len(out[i].Fields[2]) > maxTraceMessageLength {
out[i].Fields[2] = out[i].Fields[2][:maxTraceMessageLength]
}
}
return out
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package zpages // import "go.opentelemetry.io/contrib/zpages"
// Version is the current release version of the zpages span processor.
func Version() string {
return "0.41.1"
// This string is updated by the pre_release.sh script during release
}
// SemVersion is the semantic version to be supplied to tracer/meter creation.
func SemVersion() string {
return "semver:" + Version()
}
+2
View File
@@ -7,6 +7,8 @@ Thumbs.db
*.iml
*.so
coverage.*
go.work
go.work.sum
gen/
+2
View File
@@ -85,6 +85,8 @@ linters-settings:
- "**/internal/matchers/*.go"
godot:
exclude:
# Exclude links.
- '^ *\[[^]]+\]:'
# Exclude sentence fragments for lists.
- '^[ ]*[-•]'
# Exclude sentences prefixing a list.
+153 -8
View File
@@ -8,6 +8,147 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
## [1.15.1/0.38.1] 2023-05-02
### Fixed
- Remove unused imports from `sdk/resource/host_id_bsd.go` which caused build failures. (#4040, #4041)
## [1.15.0/0.38.0] 2023-04-27
### Added
- The `go.opentelemetry.io/otel/metric/embedded` package. (#3916)
- The `Version` function to `go.opentelemetry.io/otel/sdk` to return the SDK version. (#3949)
- Add a `WithNamespace` option to `go.opentelemetry.io/otel/exporters/prometheus` to allow users to prefix metrics with a namespace. (#3970)
- The following configuration types were added to `go.opentelemetry.io/otel/metric/instrument` to be used in the configuration of measurement methods. (#3971)
- The `AddConfig` used to hold configuration for addition measurements
- `NewAddConfig` used to create a new `AddConfig`
- `AddOption` used to configure an `AddConfig`
- The `RecordConfig` used to hold configuration for recorded measurements
- `NewRecordConfig` used to create a new `RecordConfig`
- `RecordOption` used to configure a `RecordConfig`
- The `ObserveConfig` used to hold configuration for observed measurements
- `NewObserveConfig` used to create a new `ObserveConfig`
- `ObserveOption` used to configure an `ObserveConfig`
- `WithAttributeSet` and `WithAttributes` are added to `go.opentelemetry.io/otel/metric/instrument`.
They return an option used during a measurement that defines the attribute Set associated with the measurement. (#3971)
- The `Version` function to `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` to return the OTLP metrics client version. (#3956)
- The `Version` function to `go.opentelemetry.io/otel/exporters/otlp/otlptrace` to return the OTLP trace client version. (#3956)
### Changed
- The `Extrema` in `go.opentelemetry.io/otel/sdk/metric/metricdata` is redefined with a generic argument of `[N int64 | float64]`. (#3870)
- Update all exported interfaces from `go.opentelemetry.io/otel/metric` to embed their corresponding interface from `go.opentelemetry.io/otel/metric/embedded`.
This adds an implementation requirement to set the interface default behavior for unimplemented methods. (#3916)
- Move No-Op implementation from `go.opentelemetry.io/otel/metric` into its own package `go.opentelemetry.io/otel/metric/noop`. (#3941)
- `metric.NewNoopMeterProvider` is replaced with `noop.NewMeterProvider`
- Add all the methods from `"go.opentelemetry.io/otel/trace".SpanContext` to `bridgeSpanContext` by embedding `otel.SpanContext` in `bridgeSpanContext`. (#3966)
- Wrap `UploadMetrics` error in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/` to improve error message when encountering generic grpc errors. (#3974)
- The measurement methods for all instruments in `go.opentelemetry.io/otel/metric/instrument` accept an option instead of the variadic `"go.opentelemetry.io/otel/attribute".KeyValue`. (#3971)
- The `Int64Counter.Add` method now accepts `...AddOption`
- The `Float64Counter.Add` method now accepts `...AddOption`
- The `Int64UpDownCounter.Add` method now accepts `...AddOption`
- The `Float64UpDownCounter.Add` method now accepts `...AddOption`
- The `Int64Histogram.Record` method now accepts `...RecordOption`
- The `Float64Histogram.Record` method now accepts `...RecordOption`
- The `Int64Observer.Observe` method now accepts `...ObserveOption`
- The `Float64Observer.Observe` method now accepts `...ObserveOption`
- The `Observer` methods in `go.opentelemetry.io/otel/metric` accept an option instead of the variadic `"go.opentelemetry.io/otel/attribute".KeyValue`. (#3971)
- The `Observer.ObserveInt64` method now accepts `...ObserveOption`
- The `Observer.ObserveFloat64` method now accepts `...ObserveOption`
- Move global metric back to `go.opentelemetry.io/otel/metric/global` from `go.opentelemetry.io/otel`. (#3986)
### Fixed
- `TracerProvider` allows calling `Tracer()` while it's shutting down.
It used to deadlock. (#3924)
- Use the SDK version for the Telemetry SDK resource detector in `go.opentelemetry.io/otel/sdk/resource`. (#3949)
- Fix a data race in `SpanProcessor` returned by `NewSimpleSpanProcessor` in `go.opentelemetry.io/otel/sdk/trace`. (#3951)
- Automatically figure out the default aggregation with `aggregation.Default`. (#3967)
### Deprecated
- The `go.opentelemetry.io/otel/metric/instrument` package is deprecated.
Use the equivalent types added to `go.opentelemetry.io/otel/metric` instead. (#4018)
## [1.15.0-rc.2/0.38.0-rc.2] 2023-03-23
This is a release candidate for the v1.15.0/v0.38.0 release.
That release will include the `v1` release of the OpenTelemetry Go metric API and will provide stability guarantees of that API.
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees.
### Added
- The `WithHostID` option to `go.opentelemetry.io/otel/sdk/resource`. (#3812)
- The `WithoutTimestamps` option to `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` to sets all timestamps to zero. (#3828)
- The new `Exemplar` type is added to `go.opentelemetry.io/otel/sdk/metric/metricdata`.
Both the `DataPoint` and `HistogramDataPoint` types from that package have a new field of `Exemplars` containing the sampled exemplars for their timeseries. (#3849)
- Configuration for each metric instrument in `go.opentelemetry.io/otel/sdk/metric/instrument`. (#3895)
- The internal logging introduces a warning level verbosity equal to `V(1)`. (#3900)
- Added a log message warning about usage of `SimpleSpanProcessor` in production environments. (#3854)
### Changed
- Optimize memory allocation when creation a new `Set` using `NewSet` or `NewSetWithFiltered` in `go.opentelemetry.io/otel/attribute`. (#3832)
- Optimize memory allocation when creation new metric instruments in `go.opentelemetry.io/otel/sdk/metric`. (#3832)
- Avoid creating new objects on all calls to `WithDeferredSetup` and `SkipContextSetup` in OpenTracing bridge. (#3833)
- The `New` and `Detect` functions from `go.opentelemetry.io/otel/sdk/resource` return errors that wrap underlying errors instead of just containing the underlying error strings. (#3844)
- Both the `Histogram` and `HistogramDataPoint` are redefined with a generic argument of `[N int64 | float64]` in `go.opentelemetry.io/otel/sdk/metric/metricdata`. (#3849)
- The metric `Export` interface from `go.opentelemetry.io/otel/sdk/metric` accepts a `*ResourceMetrics` instead of `ResourceMetrics`. (#3853)
- Rename `Asynchronous` to `Observable` in `go.opentelemetry.io/otel/metric/instrument`. (#3892)
- Rename `Int64ObserverOption` to `Int64ObservableOption` in `go.opentelemetry.io/otel/metric/instrument`. (#3895)
- Rename `Float64ObserverOption` to `Float64ObservableOption` in `go.opentelemetry.io/otel/metric/instrument`. (#3895)
- The internal logging changes the verbosity level of info to `V(4)`, the verbosity level of debug to `V(8)`. (#3900)
### Fixed
- `TracerProvider` consistently doesn't allow to register a `SpanProcessor` after shutdown. (#3845)
### Removed
- The deprecated `go.opentelemetry.io/otel/metric/global` package is removed. (#3829)
- The unneeded `Synchronous` interface in `go.opentelemetry.io/otel/metric/instrument` was removed. (#3892)
- The `Float64ObserverConfig` and `NewFloat64ObserverConfig` in `go.opentelemetry.io/otel/sdk/metric/instrument`.
Use the added `float64` instrument configuration instead. (#3895)
- The `Int64ObserverConfig` and `NewInt64ObserverConfig` in `go.opentelemetry.io/otel/sdk/metric/instrument`.
Use the added `int64` instrument configuration instead. (#3895)
- The `NewNoopMeter` function in `go.opentelemetry.io/otel/metric`, use `NewMeterProvider().Meter("")` instead. (#3893)
## [1.15.0-rc.1/0.38.0-rc.1] 2023-03-01
This is a release candidate for the v1.15.0/v0.38.0 release.
That release will include the `v1` release of the OpenTelemetry Go metric API and will provide stability guarantees of that API.
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees.
This release drops the compatibility guarantee of [Go 1.18].
### Added
- Support global `MeterProvider` in `go.opentelemetry.io/otel`. (#3818)
- Use `Meter` for a `metric.Meter` from the global `metric.MeterProvider`.
- Use `GetMeterProivder` for a global `metric.MeterProvider`.
- Use `SetMeterProivder` to set the global `metric.MeterProvider`.
### Changed
- Dropped compatibility testing for [Go 1.18].
The project no longer guarantees support for this version of Go. (#3813)
### Fixed
- Handle empty environment variable as it they were not set. (#3764)
- Clarify the `httpconv` and `netconv` packages in `go.opentelemetry.io/otel/semconv/*` provide tracing semantic conventions. (#3823)
### Deprecated
- The `go.opentelemetry.io/otel/metric/global` package is deprecated.
Use `go.opentelemetry.io/otel` instead. (#3818)
### Removed
- The deprecated `go.opentelemetry.io/otel/metric/unit` package is removed. (#3814)
## [1.14.0/0.37.0/0.0.4] 2023-02-27
This release is the last to support [Go 1.18].
@@ -121,7 +262,7 @@ The next release will require at least [Go 1.19].
- The `go.opentelemetry.io/otel/semconv/v1.16.0` package.
The package contains semantic conventions from the `v1.16.0` version of the OpenTelemetry specification. (#3579)
- Metric instruments to `go.opentelemetry.io/otel/metric/instrument`.
These instruments are use as replacements of the depreacted `go.opentelemetry.io/otel/metric/instrument/{asyncfloat64,asyncint64,syncfloat64,syncint64}` packages.(#3575, #3586)
These instruments are use as replacements of the deprecated `go.opentelemetry.io/otel/metric/instrument/{asyncfloat64,asyncint64,syncfloat64,syncint64}` packages.(#3575, #3586)
- `Float64ObservableCounter` replaces the `asyncfloat64.Counter`
- `Float64ObservableUpDownCounter` replaces the `asyncfloat64.UpDownCounter`
- `Float64ObservableGauge` replaces the `asyncfloat64.Gauge`
@@ -144,7 +285,7 @@ The next release will require at least [Go 1.19].
### Changed
- Jaeger and Zipkin exporter use `github.com/go-logr/logr` as the logging interface, and add the `WithLogr` option. (#3497, #3500)
- Instrument configuration in `go.opentelemetry.io/otel/metric/instrument` is split into specific options and confguration based on the instrument type. (#3507)
- Instrument configuration in `go.opentelemetry.io/otel/metric/instrument` is split into specific options and configuration based on the instrument type. (#3507)
- Use the added `Int64Option` type to configure instruments from `go.opentelemetry.io/otel/metric/instrument/syncint64`.
- Use the added `Float64Option` type to configure instruments from `go.opentelemetry.io/otel/metric/instrument/syncfloat64`.
- Use the added `Int64ObserverOption` type to configure instruments from `go.opentelemetry.io/otel/metric/instrument/asyncint64`.
@@ -157,7 +298,7 @@ The next release will require at least [Go 1.19].
- The `Shutdown` method of the `"go.opentelemetry.io/otel/sdk/trace".TracerProvider` releases all computational resources when called the first time. (#3551)
- The `Sampler` returned from `TraceIDRatioBased` `go.opentelemetry.io/otel/sdk/trace` now uses the rightmost bits for sampling decisions.
This fixes random sampling when using ID generators like `xray.IDGenerator` and increasing parity with other language implementations. (#3557)
- Errors from `go.opentelemetry.io/otel/exporters/otlp/otlptrace` exporters are wrapped in erros identifying their signal name.
- Errors from `go.opentelemetry.io/otel/exporters/otlp/otlptrace` exporters are wrapped in errors identifying their signal name.
Existing users of the exporters attempting to identify specific errors will need to use `errors.Unwrap()` to get the underlying error. (#3516)
- Exporters from `go.opentelemetry.io/otel/exporters/otlp` will print the final retryable error message when attempts to retry time out. (#3514)
- The instrument kind names in `go.opentelemetry.io/otel/sdk/metric` are updated to match the API. (#3562)
@@ -266,7 +407,7 @@ The next release will require at least [Go 1.19].
- Asynchronous counters (`Counter` and `UpDownCounter`) from the metric SDK now produce delta sums when configured with delta temporality. (#3398)
- Exported `Status` codes in the `go.opentelemetry.io/otel/exporters/zipkin` exporter are now exported as all upper case values. (#3340)
- `Aggregation`s from `go.opentelemetry.io/otel/sdk/metric` with no data are not exported. (#3394, #3436)
- Reenabled Attribute Filters in the Metric SDK. (#3396)
- Re-enabled Attribute Filters in the Metric SDK. (#3396)
- Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggragation. (#3408)
- Do not report empty partial-success responses in the `go.opentelemetry.io/otel/exporters/otlp` exporters. (#3438, #3432)
- Handle partial success responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporters. (#3162, #3440)
@@ -847,7 +988,7 @@ This release includes an API and SDK for the tracing signal that will comply wit
- Setting the global `ErrorHandler` with `"go.opentelemetry.io/otel".SetErrorHandler` multiple times is now supported. (#2160, #2140)
- The `"go.opentelemetry.io/otel/attribute".Any` function now supports `int32` values. (#2169)
- Multiple calls to `"go.opentelemetry.io/otel/sdk/metric/controller/basic".WithResource()` are handled correctly, and when no resources are provided `"go.opentelemetry.io/otel/sdk/resource".Default()` is used. (#2120)
- The `WithoutTimestamps` option for the `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` exporter causes the exporter to correctly ommit timestamps. (#2195)
- The `WithoutTimestamps` option for the `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` exporter causes the exporter to correctly omit timestamps. (#2195)
- Fixed typos in resources.go. (#2201)
## [1.0.0-RC2] - 2021-07-26
@@ -1293,7 +1434,7 @@ with major version 0.
- `NewGRPCDriver` function returns a `ProtocolDriver` that maintains a single gRPC connection to the collector. (#1369)
- Added documentation about the project's versioning policy. (#1388)
- Added `NewSplitDriver` for OTLP exporter that allows sending traces and metrics to different endpoints. (#1418)
- Added codeql worfklow to GitHub Actions (#1428)
- Added codeql workflow to GitHub Actions (#1428)
- Added Gosec workflow to GitHub Actions (#1429)
- Add new HTTP driver for OTLP exporter in `exporters/otlp/otlphttp`. Currently it only supports the binary protobuf payloads. (#1420)
- Add an OpenCensus exporter bridge. (#1444)
@@ -2136,7 +2277,7 @@ There is still a possibility of breaking changes.
### Fixed
- Use stateful batcher on Prometheus exporter fixing regresion introduced in #395. (#428)
- Use stateful batcher on Prometheus exporter fixing regression introduced in #395. (#428)
## [0.2.1] - 2020-01-08
@@ -2302,7 +2443,11 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.14.0...HEAD
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.15.1...HEAD
[1.15.1/0.38.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.1
[1.15.0/0.38.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.0
[1.15.0-rc.2/0.38.0-rc.2]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.0-rc.2
[1.15.0-rc.1/0.38.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.0-rc.1
[1.14.0/0.37.0/0.0.4]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.14.0
[1.13.0/0.36.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.13.0
[1.12.0/0.35.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.12.0
+1 -1
View File
@@ -12,6 +12,6 @@
# https://help.github.com/en/articles/about-code-owners
#
* @jmacd @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu
* @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu
CODEOWNERS @MrAlias @Aneurysm9 @MadVikingGod
+62 -31
View File
@@ -6,7 +6,7 @@ OpenTelemetry
repo for information on this and other language SIGs.
See the [public meeting
notes](https://docs.google.com/document/d/1A63zSWX0x2CyCK_LoNhmQC4rqhLpYXJzXbEPDUQ2n6w/edit#heading=h.9tngw7jdwd6b)
notes](https://docs.google.com/document/d/1E5e7Ld0NuU1iVvf-42tOBpu2VBBLYnh73GJuITGJTTU/edit)
for a summary description of past meetings. To request edit access,
join the meeting or get in touch on
[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT).
@@ -94,30 +94,58 @@ request ID to the entry you added to `CHANGELOG.md`.
### How to Get PRs Merged
A PR is considered to be **ready to merge** when:
A PR is considered **ready to merge** when:
* It has received two approvals from Collaborators/Maintainers (at
different companies). This is not enforced through technical means
and a PR may be **ready to merge** with a single approval if the change
and its approach have been discussed and consensus reached.
* Feedback has been addressed.
* Any substantive changes to your PR will require that you clear any prior
Approval reviews, this includes changes resulting from other feedback. Unless
the approver explicitly stated that their approval will persist across
changes it should be assumed that the PR needs their review again. Other
project members (e.g. approvers, maintainers) can help with this if there are
any questions or if you forget to clear reviews.
* It has been open for review for at least one working day. This gives
people reasonable time to review.
* Trivial changes (typo, cosmetic, doc, etc.) do not have to wait for
one day and may be merged with a single Maintainer's approval.
* `CHANGELOG.md` has been updated to reflect what has been
added, changed, removed, or fixed.
* `README.md` has been updated if necessary.
* Urgent fix can take exception as long as it has been actively
communicated.
* It has received two qualified approvals[^1].
Any Maintainer can merge the PR once it is **ready to merge**.
This is not enforced through automation, but needs to be validated by the
maintainer merging.
* The qualified approvals need to be from [Approver]s/[Maintainer]s
affiliated with different companies. Two qualified approvals from
[Approver]s or [Maintainer]s affiliated with the same company counts as a
single qualified approval.
* PRs introducing changes that have already been discussed and consensus
reached only need one qualified approval. The discussion and resolution
needs to be linked to the PR.
* Trivial changes[^2] only need one qualified approval.
* All feedback has been addressed.
* All PR comments and suggestions are resolved.
* All GitHub Pull Request reviews with a status of "Request changes" have
been addressed. Another review by the objecting reviewer with a different
status can be submitted to clear the original review, or the review can be
dismissed by a [Maintainer] when the issues from the original review have
been addressed.
* Any comments or reviews that cannot be resolved between the PR author and
reviewers can be submitted to the community [Approver]s and [Maintainer]s
during the weekly SIG meeting. If consensus is reached among the
[Approver]s and [Maintainer]s during the SIG meeting the objections to the
PR may be dismissed or resolved or the PR closed by a [Maintainer].
* Any substantive changes to the PR require existing Approval reviews be
cleared unless the approver explicitly states that their approval persists
across changes. This includes changes resulting from other feedback.
[Approver]s and [Maintainer]s can help in clearing reviews and they should
be consulted if there are any questions.
* The PR branch is up to date with the base branch it is merging into.
* To ensure this does not block the PR, it should be configured to allow
maintainers to update it.
* It has been open for review for at least one working day. This gives people
reasonable time to review.
* Trivial changes[^2] do not have to wait for one day and may be merged with
a single [Maintainer]'s approval.
* All required GitHub workflows have succeeded.
* Urgent fix can take exception as long as it has been actively communicated
among [Maintainer]s.
Any [Maintainer] can merge the PR once the above criteria have been met.
[^1]: A qualified approval is a GitHub Pull Request review with "Approve"
status from an OpenTelemetry Go [Approver] or [Maintainer].
[^2]: Trivial changes include: typo corrections, cosmetic non-substantive
changes, documentation corrections or updates, dependency updates, etc.
## Design Choices
@@ -216,7 +244,7 @@ Meaning a `config` from one package should not be directly used by another. The
one exception is the API packages. The configs from the base API, eg.
`go.opentelemetry.io/otel/trace.TracerConfig` and
`go.opentelemetry.io/otel/metric.InstrumentConfig`, are intended to be consumed
by the SDK therefor it is expected that these are exported.
by the SDK therefore it is expected that these are exported.
When a config is exported we want to maintain forward and backward
compatibility, to achieve this no fields should be exported but should
@@ -234,12 +262,12 @@ func newConfig(options ...Option) config {
for _, option := range options {
config = option.apply(config)
}
// Preform any validation here.
// Perform any validation here.
return config
}
```
If validation of the `config` options is also preformed this can return an
If validation of the `config` options is also performed this can return an
error as well that is expected to be handled by the instantiation function
or propagated to the user.
@@ -438,7 +466,7 @@ their parameters appropriately named.
#### Interface Stability
All exported stable interfaces that include the following warning in their
doumentation are allowed to be extended with additional methods.
documentation are allowed to be extended with additional methods.
> Warning: methods may be added to this interface in minor releases.
@@ -500,27 +528,30 @@ interface that defines the specific functionality should be preferred.
## Approvers and Maintainers
Approvers:
### Approvers
- [Evan Torrie](https://github.com/evantorrie), Verizon Media
- [Josh MacDonald](https://github.com/jmacd), LightStep
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
- [David Ashpole](https://github.com/dashpole), Google
- [Robert Pająk](https://github.com/pellared), Splunk
- [Chester Cheung](https://github.com/hanyuancheung), Tencent
- [Damien Mathieu](https://github.com/dmathieu), Elastic
Maintainers:
### Maintainers
- [Aaron Clawson](https://github.com/MadVikingGod), LightStep
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS
- [Tyler Yahn](https://github.com/MrAlias), Splunk
Emeritus:
### Emeritus
- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep
- [Josh MacDonald](https://github.com/jmacd), LightStep
### Become an Approver or a Maintainer
See the [community membership document in OpenTelemetry community
repo](https://github.com/open-telemetry/community/blob/main/community-membership.md).
[Approver]: #approvers
[Maintainer]: #maintainers
+1 -1
View File
@@ -156,7 +156,7 @@ go-mod-tidy/%: DIR=$*
go-mod-tidy/%: | crosslink
@echo "$(GO) mod tidy in $(DIR)" \
&& cd $(DIR) \
&& $(GO) mod tidy -compat=1.18
&& $(GO) mod tidy -compat=1.19
.PHONY: lint-modules
lint-modules: go-mod-tidy
+1 -6
View File
@@ -14,7 +14,7 @@ It provides a set of APIs to directly measure performance and behavior of your s
| Signal | Status | Project |
| ------- | ---------- | ------- |
| Traces | Stable | N/A |
| Metrics | Alpha | N/A |
| Metrics | Beta | N/A |
| Logs | Frozen [1] | N/A |
- [1]: The Logs signal development is halted for this project while we develop both Traces and Metrics.
@@ -52,19 +52,14 @@ Currently, this project supports the following environments.
| ------- | ---------- | ------------ |
| Ubuntu | 1.20 | amd64 |
| Ubuntu | 1.19 | amd64 |
| Ubuntu | 1.18 | amd64 |
| Ubuntu | 1.20 | 386 |
| Ubuntu | 1.19 | 386 |
| Ubuntu | 1.18 | 386 |
| MacOS | 1.20 | amd64 |
| MacOS | 1.19 | amd64 |
| MacOS | 1.18 | amd64 |
| Windows | 1.20 | amd64 |
| Windows | 1.19 | amd64 |
| Windows | 1.18 | amd64 |
| Windows | 1.20 | 386 |
| Windows | 1.19 | 386 |
| Windows | 1.18 | 386 |
While this project should work for other systems, no compatibility guarantees
are made for those systems currently.
+16 -4
View File
@@ -18,6 +18,7 @@ import (
"encoding/json"
"reflect"
"sort"
"sync"
)
type (
@@ -62,6 +63,12 @@ var (
iface: [0]KeyValue{},
},
}
// sortables is a pool of Sortables used to create Sets with a user does
// not provide one.
sortables = sync.Pool{
New: func() interface{} { return new(Sortable) },
}
)
// EmptySet returns a reference to a Set with no elements.
@@ -91,7 +98,7 @@ func (l *Set) Len() int {
// Get returns the KeyValue at ordered position idx in this set.
func (l *Set) Get(idx int) (KeyValue, bool) {
if l == nil {
if l == nil || !l.equivalent.Valid() {
return KeyValue{}, false
}
value := l.equivalent.reflectValue()
@@ -107,7 +114,7 @@ func (l *Set) Get(idx int) (KeyValue, bool) {
// Value returns the value of a specified key in this set.
func (l *Set) Value(k Key) (Value, bool) {
if l == nil {
if l == nil || !l.equivalent.Valid() {
return Value{}, false
}
rValue := l.equivalent.reflectValue()
@@ -191,7 +198,9 @@ func NewSet(kvs ...KeyValue) Set {
if len(kvs) == 0 {
return empty()
}
s, _ := NewSetWithSortableFiltered(kvs, new(Sortable), nil)
srt := sortables.Get().(*Sortable)
s, _ := NewSetWithSortableFiltered(kvs, srt, nil)
sortables.Put(srt)
return s
}
@@ -218,7 +227,10 @@ func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
if len(kvs) == 0 {
return empty(), nil
}
return NewSetWithSortableFiltered(kvs, new(Sortable), filter)
srt := sortables.Get().(*Sortable)
s, filtered := NewSetWithSortableFiltered(kvs, srt, filter)
sortables.Put(srt)
return s, filtered
}
// NewSetWithSortableFiltered returns a new Set.
+1 -1
View File
@@ -47,4 +47,4 @@ When re-generating Thrift code in the future, please adapt import paths as neces
- [Jaeger](https://www.jaegertracing.io/)
- [OpenTelemetry to Jaeger Transformation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md)
- [OpenTelemetry Environment Variable Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md)
- [OpenTelemetry Environment Variable Specification](https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/#general-sdk-configuration)
+1 -1
View File
@@ -37,7 +37,7 @@ const (
// envOr returns an env variable's value if it is exists or the default if not.
func envOr(key, defaultValue string) string {
if v, ok := os.LookupEnv(key); ok && v != "" {
if v := os.Getenv(key); v != "" {
return v
}
return defaultValue
@@ -77,7 +77,7 @@ const (
// WrapTException wraps an error into TException.
//
// If err is nil or already TException, it's returned as-is.
// Otherwise it will be wraped into TException with TExceptionType() returning
// Otherwise it will be wrapped into TException with TExceptionType() returning
// TExceptionTypeUnknown, and Unwrap() returning the original error.
func WrapTException(err error) TException {
if err == nil {
@@ -56,7 +56,7 @@ type stringWriter interface {
WriteString(s string) (n int, err error)
}
// This is "enchanced" transport with extra capabilities. You need to use one of these
// This is "enhanced" transport with extra capabilities. You need to use one of these
// to construct protocol.
// Notably, TSocket does not implement this interface, and it is always a mistake to use
// TSocket directly in protocol.
@@ -40,7 +40,7 @@ const (
LIST = 15
UTF8 = 16
UTF16 = 17
//BINARY = 18 wrong and unusued
//BINARY = 18 wrong and unused
)
var typeNames = map[int]string{
@@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package internal contains common functionality for all OTLP exporters.
package internal // import "go.opentelemetry.io/otel/exporters/otlp/internal"
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal"
import "go.opentelemetry.io/otel"
import (
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
)
// GetUserAgentHeader return an OTLP header value form "OTel OTLP Exporter Go/{{ .Version }}"
// GetUserAgentHeader returns an OTLP header value form "OTel OTLP Exporter Go/{{ .Version }}"
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#user-agent
func GetUserAgentHeader() string {
return "OTel OTLP Exporter Go/" + otel.Version()
return "OTel OTLP Exporter Go/" + otlptrace.Version()
}
@@ -27,6 +27,7 @@ import (
"go.opentelemetry.io/otel/exporters/otlp/internal"
"go.opentelemetry.io/otel/exporters/otlp/internal/retry"
otinternal "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal"
)
const (
@@ -97,7 +98,7 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
Timeout: DefaultTimeout,
},
RetryConfig: retry.DefaultConfig,
DialOptions: []grpc.DialOption{grpc.WithUserAgent(internal.GetUserAgentHeader())},
DialOptions: []grpc.DialOption{grpc.WithUserAgent(otinternal.GetUserAgentHeader())},
}
cfg = ApplyGRPCEnvConfigs(cfg)
for _, opt := range opts {
@@ -130,13 +130,16 @@ var errAlreadyStopped = errors.New("the client is already stopped")
// If the client has already stopped, an error will be returned describing
// this.
func (c *client) Stop(ctx context.Context) error {
// Make sure to return context error if the context is done when calling this method.
err := ctx.Err()
// Acquire the c.tscMu lock within the ctx lifetime.
acquired := make(chan struct{})
go func() {
c.tscMu.Lock()
close(acquired)
}()
var err error
select {
case <-ctx.Done():
// The Stop timeout is reached. Kill any remaining exports to force
+20
View File
@@ -0,0 +1,20 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
func Version() string {
return "1.15.1"
}
+8 -56
View File
@@ -15,58 +15,16 @@
package otel // import "go.opentelemetry.io/otel"
import (
"log"
"os"
"sync/atomic"
"unsafe"
"go.opentelemetry.io/otel/internal/global"
)
var (
// globalErrorHandler provides an ErrorHandler that can be used
// throughout an OpenTelemetry instrumented project. When a user
// specified ErrorHandler is registered (`SetErrorHandler`) all calls to
// `Handle` and will be delegated to the registered ErrorHandler.
globalErrorHandler = defaultErrorHandler()
// Compile-time check that delegator implements ErrorHandler.
_ ErrorHandler = (*delegator)(nil)
// Compile-time check that errLogger implements ErrorHandler.
_ ErrorHandler = (*errLogger)(nil)
// Compile-time check global.ErrDelegator implements ErrorHandler.
_ ErrorHandler = (*global.ErrDelegator)(nil)
// Compile-time check global.ErrLogger implements ErrorHandler.
_ ErrorHandler = (*global.ErrLogger)(nil)
)
type delegator struct {
delegate unsafe.Pointer
}
func (d *delegator) Handle(err error) {
d.getDelegate().Handle(err)
}
func (d *delegator) getDelegate() ErrorHandler {
return *(*ErrorHandler)(atomic.LoadPointer(&d.delegate))
}
// setDelegate sets the ErrorHandler delegate.
func (d *delegator) setDelegate(eh ErrorHandler) {
atomic.StorePointer(&d.delegate, unsafe.Pointer(&eh))
}
func defaultErrorHandler() *delegator {
d := &delegator{}
d.setDelegate(&errLogger{l: log.New(os.Stderr, "", log.LstdFlags)})
return d
}
// errLogger logs errors if no delegate is set, otherwise they are delegated.
type errLogger struct {
l *log.Logger
}
// Handle logs err if no delegate is set, otherwise it is delegated.
func (h *errLogger) Handle(err error) {
h.l.Print(err)
}
// GetErrorHandler returns the global ErrorHandler instance.
//
// The default ErrorHandler instance returned will log all errors to STDERR
@@ -76,9 +34,7 @@ func (h *errLogger) Handle(err error) {
//
// Subsequent calls to SetErrorHandler after the first will not forward errors
// to the new ErrorHandler for prior returned instances.
func GetErrorHandler() ErrorHandler {
return globalErrorHandler
}
func GetErrorHandler() ErrorHandler { return global.GetErrorHandler() }
// SetErrorHandler sets the global ErrorHandler to h.
//
@@ -86,11 +42,7 @@ func GetErrorHandler() ErrorHandler {
// GetErrorHandler will send errors to h instead of the default logging
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
// delegate errors to h.
func SetErrorHandler(h ErrorHandler) {
globalErrorHandler.setDelegate(h)
}
func SetErrorHandler(h ErrorHandler) { global.SetErrorHandler(h) }
// Handle is a convenience function for ErrorHandler().Handle(err).
func Handle(err error) {
GetErrorHandler().Handle(err)
}
func Handle(err error) { global.Handle(err) }
+103
View File
@@ -0,0 +1,103 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package global // import "go.opentelemetry.io/otel/internal/global"
import (
"log"
"os"
"sync/atomic"
"unsafe"
)
var (
// GlobalErrorHandler provides an ErrorHandler that can be used
// throughout an OpenTelemetry instrumented project. When a user
// specified ErrorHandler is registered (`SetErrorHandler`) all calls to
// `Handle` and will be delegated to the registered ErrorHandler.
GlobalErrorHandler = defaultErrorHandler()
// Compile-time check that delegator implements ErrorHandler.
_ ErrorHandler = (*ErrDelegator)(nil)
// Compile-time check that errLogger implements ErrorHandler.
_ ErrorHandler = (*ErrLogger)(nil)
)
// ErrorHandler handles irremediable events.
type ErrorHandler interface {
// Handle handles any error deemed irremediable by an OpenTelemetry
// component.
Handle(error)
}
type ErrDelegator struct {
delegate unsafe.Pointer
}
func (d *ErrDelegator) Handle(err error) {
d.getDelegate().Handle(err)
}
func (d *ErrDelegator) getDelegate() ErrorHandler {
return *(*ErrorHandler)(atomic.LoadPointer(&d.delegate))
}
// setDelegate sets the ErrorHandler delegate.
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
atomic.StorePointer(&d.delegate, unsafe.Pointer(&eh))
}
func defaultErrorHandler() *ErrDelegator {
d := &ErrDelegator{}
d.setDelegate(&ErrLogger{l: log.New(os.Stderr, "", log.LstdFlags)})
return d
}
// ErrLogger logs errors if no delegate is set, otherwise they are delegated.
type ErrLogger struct {
l *log.Logger
}
// Handle logs err if no delegate is set, otherwise it is delegated.
func (h *ErrLogger) Handle(err error) {
h.l.Print(err)
}
// GetErrorHandler returns the global ErrorHandler instance.
//
// The default ErrorHandler instance returned will log all errors to STDERR
// until an override ErrorHandler is set with SetErrorHandler. All
// ErrorHandler returned prior to this will automatically forward errors to
// the set instance instead of logging.
//
// Subsequent calls to SetErrorHandler after the first will not forward errors
// to the new ErrorHandler for prior returned instances.
func GetErrorHandler() ErrorHandler {
return GlobalErrorHandler
}
// SetErrorHandler sets the global ErrorHandler to h.
//
// The first time this is called all ErrorHandler previously returned from
// GetErrorHandler will send errors to h instead of the default logging
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
// delegate errors to h.
func SetErrorHandler(h ErrorHandler) {
GlobalErrorHandler.setDelegate(h)
}
// Handle is a convenience function for ErrorHandler().Handle(err).
func Handle(err error) {
GetErrorHandler().Handle(err)
}
+13 -6
View File
@@ -24,7 +24,7 @@ import (
"github.com/go-logr/stdr"
)
// globalLogger is the logging interface used within the otel api and sdk provide deatails of the internals.
// globalLogger is the logging interface used within the otel api and sdk provide details of the internals.
//
// The default logger uses stdr which is backed by the standard `log.Logger`
// interface. This logger will only show messages at the Error Level.
@@ -36,8 +36,9 @@ func init() {
// SetLogger overrides the globalLogger with l.
//
// To see Info messages use a logger with `l.V(1).Enabled() == true`
// To see Debug messages use a logger with `l.V(5).Enabled() == true`.
// To see Warn messages use a logger with `l.V(1).Enabled() == true`
// To see Info messages use a logger with `l.V(4).Enabled() == true`
// To see Debug messages use a logger with `l.V(8).Enabled() == true`.
func SetLogger(l logr.Logger) {
atomic.StorePointer(&globalLogger, unsafe.Pointer(&l))
}
@@ -47,9 +48,9 @@ func getLogger() logr.Logger {
}
// Info prints messages about the general state of the API or SDK.
// This should usually be less then 5 messages a minute.
// This should usually be less than 5 messages a minute.
func Info(msg string, keysAndValues ...interface{}) {
getLogger().V(1).Info(msg, keysAndValues...)
getLogger().V(4).Info(msg, keysAndValues...)
}
// Error prints messages about exceptional states of the API or SDK.
@@ -59,5 +60,11 @@ func Error(err error, msg string, keysAndValues ...interface{}) {
// Debug prints messages about all internal changes in the API or SDK.
func Debug(msg string, keysAndValues ...interface{}) {
getLogger().V(5).Info(msg, keysAndValues...)
getLogger().V(8).Info(msg, keysAndValues...)
}
// Warn prints messages about warnings in the API or SDK.
// Not an error but is likely more important than an informational event.
func Warn(msg string, keysAndValues ...interface{}) {
getLogger().V(1).Info(msg, keysAndValues...)
}
+5 -5
View File
@@ -70,8 +70,8 @@ const (
// returned.
func firstInt(defaultValue int, keys ...string) int {
for _, key := range keys {
value, ok := os.LookupEnv(key)
if !ok {
value := os.Getenv(key)
if value == "" {
continue
}
@@ -88,10 +88,10 @@ func firstInt(defaultValue int, keys ...string) int {
}
// IntEnvOr returns the int value of the environment variable with name key if
// it exists and the value is an int. Otherwise, defaultValue is returned.
// it exists, it is not empty, and the value is an int. Otherwise, defaultValue is returned.
func IntEnvOr(key string, defaultValue int) int {
value, ok := os.LookupEnv(key)
if !ok {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
+1 -10
View File
@@ -14,16 +14,7 @@
package internal // import "go.opentelemetry.io/otel/sdk/internal"
import (
"fmt"
"time"
"go.opentelemetry.io/otel"
)
// UserAgent is the user agent to be added to the outgoing
// requests from the exporters.
var UserAgent = fmt.Sprintf("opentelemetry-go/%s", otel.Version())
import "time"
// MonotonicEndTime returns the end time at present
// but offset from start, monotonically.
+48 -10
View File
@@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"strings"
)
var (
@@ -45,28 +46,65 @@ type Detector interface {
// Detect calls all input detectors sequentially and merges each result with the previous one.
// It returns the merged error too.
func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
var autoDetectedRes *Resource
var errInfo []string
r := new(Resource)
return r, detect(ctx, r, detectors)
}
// detect runs all detectors using ctx and merges the result into res. This
// assumes res is allocated and not nil, it will panic otherwise.
func detect(ctx context.Context, res *Resource, detectors []Detector) error {
var (
r *Resource
errs detectErrs
err error
)
for _, detector := range detectors {
if detector == nil {
continue
}
res, err := detector.Detect(ctx)
r, err = detector.Detect(ctx)
if err != nil {
errInfo = append(errInfo, err.Error())
errs = append(errs, err)
if !errors.Is(err, ErrPartialResource) {
continue
}
}
autoDetectedRes, err = Merge(autoDetectedRes, res)
r, err = Merge(res, r)
if err != nil {
errInfo = append(errInfo, err.Error())
errs = append(errs, err)
}
*res = *r
}
var aggregatedError error
if len(errInfo) > 0 {
aggregatedError = fmt.Errorf("detecting resources: %s", errInfo)
if len(errs) == 0 {
return nil
}
return autoDetectedRes, aggregatedError
return errs
}
type detectErrs []error
func (e detectErrs) Error() string {
errStr := make([]string, len(e))
for i, err := range e {
errStr[i] = fmt.Sprintf("* %s", err)
}
format := "%d errors occurred detecting resource:\n\t%s"
return fmt.Sprintf(format, len(e), strings.Join(errStr, "\n\t"))
}
func (e detectErrs) Unwrap() error {
switch len(e) {
case 0:
return nil
case 1:
return e[0]
}
return e[1:]
}
func (e detectErrs) Is(target error) bool {
return len(e) != 0 && errors.Is(e[0], target)
}
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"os"
"path/filepath"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
@@ -62,7 +62,7 @@ func (telemetrySDK) Detect(context.Context) (*Resource, error) {
semconv.SchemaURL,
semconv.TelemetrySDKName("opentelemetry"),
semconv.TelemetrySDKLanguageGo,
semconv.TelemetrySDKVersion(otel.Version()),
semconv.TelemetrySDKVersion(sdk.Version()),
), nil
}
+5
View File
@@ -71,6 +71,11 @@ func WithHost() Option {
return WithDetectors(host{})
}
// WithHostID adds host ID information to the configured resource.
func WithHostID() Option {
return WithDetectors(hostIDDetector{})
}
// WithTelemetrySDK adds TelemetrySDK version info to the configured resource.
func WithTelemetrySDK() Option {
return WithDetectors(telemetrySDK{})
+120
View File
@@ -0,0 +1,120 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package resource // import "go.opentelemetry.io/otel/sdk/resource"
import (
"context"
"errors"
"strings"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
type hostIDProvider func() (string, error)
var defaultHostIDProvider hostIDProvider = platformHostIDReader.read
var hostID = defaultHostIDProvider
type hostIDReader interface {
read() (string, error)
}
type fileReader func(string) (string, error)
type commandExecutor func(string, ...string) (string, error)
// hostIDReaderBSD implements hostIDReader.
type hostIDReaderBSD struct {
execCommand commandExecutor
readFile fileReader
}
// read attempts to read the machine-id from /etc/hostid. If not found it will
// execute `kenv -q smbios.system.uuid`. If neither location yields an id an
// error will be returned.
func (r *hostIDReaderBSD) read() (string, error) {
if result, err := r.readFile("/etc/hostid"); err == nil {
return strings.TrimSpace(result), nil
}
if result, err := r.execCommand("kenv", "-q", "smbios.system.uuid"); err == nil {
return strings.TrimSpace(result), nil
}
return "", errors.New("host id not found in: /etc/hostid or kenv")
}
// hostIDReaderDarwin implements hostIDReader.
type hostIDReaderDarwin struct {
execCommand commandExecutor
}
// read executes `ioreg -rd1 -c "IOPlatformExpertDevice"` and parses host id
// from the IOPlatformUUID line. If the command fails or the uuid cannot be
// parsed an error will be returned.
func (r *hostIDReaderDarwin) read() (string, error) {
result, err := r.execCommand("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
if err != nil {
return "", err
}
lines := strings.Split(result, "\n")
for _, line := range lines {
if strings.Contains(line, "IOPlatformUUID") {
parts := strings.Split(line, " = ")
if len(parts) == 2 {
return strings.Trim(parts[1], "\""), nil
}
break
}
}
return "", errors.New("could not parse IOPlatformUUID")
}
type hostIDReaderLinux struct {
readFile fileReader
}
// read attempts to read the machine-id from /etc/machine-id followed by
// /var/lib/dbus/machine-id. If neither location yields an ID an error will
// be returned.
func (r *hostIDReaderLinux) read() (string, error) {
if result, err := r.readFile("/etc/machine-id"); err == nil {
return strings.TrimSpace(result), nil
}
if result, err := r.readFile("/var/lib/dbus/machine-id"); err == nil {
return strings.TrimSpace(result), nil
}
return "", errors.New("host id not found in: /etc/machine-id or /var/lib/dbus/machine-id")
}
type hostIDDetector struct{}
// Detect returns a *Resource containing the platform specific host id.
func (hostIDDetector) Detect(ctx context.Context) (*Resource, error) {
hostID, err := hostID()
if err != nil {
return nil, err
}
return NewWithAttributes(
semconv.SchemaURL,
semconv.HostID(hostID),
), nil
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build dragonfly || freebsd || netbsd || openbsd || solaris
// +build dragonfly freebsd netbsd openbsd solaris
package resource // import "go.opentelemetry.io/otel/sdk/resource"
var platformHostIDReader hostIDReader = &hostIDReaderBSD{
execCommand: execCommand,
readFile: readFile,
}
@@ -1,4 +1,4 @@
// Copyright 2017, OpenCensus Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package ocgrpc contains OpenCensus stats and trace
// integrations for gRPC.
//
// Use ServerHandler for servers and ClientHandler for clients.
package ocgrpc // import "go.opencensus.io/plugin/ocgrpc"
package resource // import "go.opentelemetry.io/otel/sdk/resource"
var platformHostIDReader hostIDReader = &hostIDReaderDarwin{
execCommand: execCommand,
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build bsd || darwin
package resource // import "go.opentelemetry.io/otel/sdk/resource"
import "os/exec"
func execCommand(name string, arg ...string) (string, error) {
cmd := exec.Command(name, arg...)
b, err := cmd.Output()
if err != nil {
return "", err
}
return string(b), nil
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// +build linux
package resource // import "go.opentelemetry.io/otel/sdk/resource"
var platformHostIDReader hostIDReader = &hostIDReaderLinux{
readFile: readFile,
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build bsd || linux
package resource // import "go.opentelemetry.io/otel/sdk/resource"
import "os"
func readFile(filename string) (string, error) {
b, err := os.ReadFile(filename)
if err != nil {
return "", nil
}
return string(b), nil
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !darwin
// +build !dragonfly
// +build !freebsd
// +build !linux
// +build !netbsd
// +build !openbsd
// +build !solaris
// +build !windows
package resource // import "go.opentelemetry.io/otel/sdk/resource"
// hostIDReaderUnsupported is a placeholder implementation for operating systems
// for which this project currently doesn't support host.id
// attribute detection. See build tags declaration early on this file
// for a list of unsupported OSes.
type hostIDReaderUnsupported struct{}
func (*hostIDReaderUnsupported) read() (string, error) {
return "<unknown>", nil
}
var platformHostIDReader hostIDReader = &hostIDReaderUnsupported{}
+48
View File
@@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
// +build windows
package resource // import "go.opentelemetry.io/otel/sdk/resource"
import (
"golang.org/x/sys/windows/registry"
)
// implements hostIDReader
type hostIDReaderWindows struct{}
// read reads MachineGuid from the windows registry key:
// SOFTWARE\Microsoft\Cryptography
func (*hostIDReaderWindows) read() (string, error) {
k, err := registry.OpenKey(
registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`,
registry.QUERY_VALUE|registry.WOW64_64KEY,
)
if err != nil {
return "", err
}
defer k.Close()
guid, _, err := k.GetStringValue("MachineGuid")
if err != nil {
return "", err
}
return guid, nil
}
var platformHostIDReader hostIDReader = &hostIDReaderWindows{}
+2 -12
View File
@@ -17,7 +17,6 @@ package resource // import "go.opentelemetry.io/otel/sdk/resource"
import (
"context"
"errors"
"fmt"
"sync"
"go.opentelemetry.io/otel"
@@ -51,17 +50,8 @@ func New(ctx context.Context, opts ...Option) (*Resource, error) {
cfg = opt.apply(cfg)
}
resource, err := Detect(ctx, cfg.detectors...)
var err2 error
resource, err2 = Merge(resource, &Resource{schemaURL: cfg.schemaURL})
if err == nil {
err = err2
} else if err2 != nil {
err = fmt.Errorf("detecting resources: %s", []string{err.Error(), err2.Error()})
}
return resource, err
r := &Resource{schemaURL: cfg.schemaURL}
return r, detect(ctx, r, cfg.detectors)
}
// NewWithAttributes creates a resource from attrs and associates the resource with a
+1 -1
View File
@@ -91,7 +91,7 @@ var _ SpanProcessor = (*batchSpanProcessor)(nil)
// NewBatchSpanProcessor creates a new SpanProcessor that will send completed
// span batches to the exporter with the supplied options.
//
// If the exporter is nil, the span processor will preform no action.
// If the exporter is nil, the span processor will perform no action.
func NewBatchSpanProcessor(exporter SpanExporter, options ...BatchSpanProcessorOption) SpanProcessor {
maxQueueSize := env.BatchSpanProcessorMaxQueueSize(DefaultMaxQueueSize)
maxExportBatchSize := env.BatchSpanProcessorMaxExportBatchSize(DefaultMaxExportBatchSize)
+75 -36
View File
@@ -75,8 +75,9 @@ func (cfg tracerProviderConfig) MarshalLog() interface{} {
type TracerProvider struct {
mu sync.Mutex
namedTracer map[instrumentation.Scope]*tracer
spanProcessors atomic.Value
isShutdown bool
spanProcessors atomic.Pointer[spanProcessorStates]
isShutdown atomic.Bool
// These fields are not protected by the lock mu. They are assumed to be
// immutable after creation of the TracerProvider.
@@ -119,11 +120,11 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
}
global.Info("TracerProvider created", "config", o)
spss := spanProcessorStates{}
spss := make(spanProcessorStates, 0, len(o.processors))
for _, sp := range o.processors {
spss = append(spss, newSpanProcessorState(sp))
}
tp.spanProcessors.Store(spss)
tp.spanProcessors.Store(&spss)
return tp
}
@@ -136,10 +137,11 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
//
// This method is safe to be called concurrently.
func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
// This check happens before the mutex is acquired to avoid deadlocking if Tracer() is called from within Shutdown().
if p.isShutdown.Load() {
return trace.NewNoopTracerProvider().Tracer(name, opts...)
}
c := trace.NewTracerConfig(opts...)
p.mu.Lock()
defer p.mu.Unlock()
if name == "" {
name = defaultTracerName
}
@@ -148,44 +150,74 @@ func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T
Version: c.InstrumentationVersion(),
SchemaURL: c.SchemaURL(),
}
t, ok := p.namedTracer[is]
if !ok {
t = &tracer{
provider: p,
instrumentationScope: is,
t, ok := func() (trace.Tracer, bool) {
p.mu.Lock()
defer p.mu.Unlock()
// Must check the flag after acquiring the mutex to avoid returning a valid tracer if Shutdown() ran
// after the first check above but before we acquired the mutex.
if p.isShutdown.Load() {
return trace.NewNoopTracerProvider().Tracer(name, opts...), true
}
p.namedTracer[is] = t
global.Info("Tracer created", "name", name, "version", c.InstrumentationVersion(), "schemaURL", c.SchemaURL())
t, ok := p.namedTracer[is]
if !ok {
t = &tracer{
provider: p,
instrumentationScope: is,
}
p.namedTracer[is] = t
}
return t, ok
}()
if !ok {
// This code is outside the mutex to not hold the lock while calling third party logging code:
// - That code may do slow things like I/O, which would prolong the duration the lock is held,
// slowing down all tracing consumers.
// - Logging code may be instrumented with tracing and deadlock because it could try
// acquiring the same non-reentrant mutex.
global.Info("Tracer created", "name", name, "version", is.Version, "schemaURL", is.SchemaURL)
}
return t
}
// RegisterSpanProcessor adds the given SpanProcessor to the list of SpanProcessors.
func (p *TracerProvider) RegisterSpanProcessor(sp SpanProcessor) {
p.mu.Lock()
defer p.mu.Unlock()
if p.isShutdown {
// This check prevents calls during a shutdown.
if p.isShutdown.Load() {
return
}
newSPS := spanProcessorStates{}
newSPS = append(newSPS, p.spanProcessors.Load().(spanProcessorStates)...)
p.mu.Lock()
defer p.mu.Unlock()
// This check prevents calls after a shutdown.
if p.isShutdown.Load() {
return
}
current := p.getSpanProcessors()
newSPS := make(spanProcessorStates, 0, len(current)+1)
newSPS = append(newSPS, current...)
newSPS = append(newSPS, newSpanProcessorState(sp))
p.spanProcessors.Store(newSPS)
p.spanProcessors.Store(&newSPS)
}
// UnregisterSpanProcessor removes the given SpanProcessor from the list of SpanProcessors.
func (p *TracerProvider) UnregisterSpanProcessor(sp SpanProcessor) {
p.mu.Lock()
defer p.mu.Unlock()
if p.isShutdown {
// This check prevents calls during a shutdown.
if p.isShutdown.Load() {
return
}
old := p.spanProcessors.Load().(spanProcessorStates)
p.mu.Lock()
defer p.mu.Unlock()
// This check prevents calls after a shutdown.
if p.isShutdown.Load() {
return
}
old := p.getSpanProcessors()
if len(old) == 0 {
return
}
spss := spanProcessorStates{}
spss = append(spss, old...)
spss := make(spanProcessorStates, len(old))
copy(spss, old)
// stop the span processor if it is started and remove it from the list
var stopOnce *spanProcessorState
@@ -209,13 +241,13 @@ func (p *TracerProvider) UnregisterSpanProcessor(sp SpanProcessor) {
spss[len(spss)-1] = nil
spss = spss[:len(spss)-1]
p.spanProcessors.Store(spss)
p.spanProcessors.Store(&spss)
}
// ForceFlush immediately exports all spans that have not yet been exported for
// all the registered span processors.
func (p *TracerProvider) ForceFlush(ctx context.Context) error {
spss := p.spanProcessors.Load().(spanProcessorStates)
spss := p.getSpanProcessors()
if len(spss) == 0 {
return nil
}
@@ -236,18 +268,21 @@ func (p *TracerProvider) ForceFlush(ctx context.Context) error {
// Shutdown shuts down TracerProvider. All registered span processors are shut down
// in the order they were registered and any held computational resources are released.
// After Shutdown is called, all methods are no-ops.
func (p *TracerProvider) Shutdown(ctx context.Context) error {
spss := p.spanProcessors.Load().(spanProcessorStates)
if len(spss) == 0 {
// This check prevents deadlocks in case of recursive shutdown.
if p.isShutdown.Load() {
return nil
}
p.mu.Lock()
defer p.mu.Unlock()
// This check prevents calls after a shutdown has already been done concurrently.
if !p.isShutdown.CompareAndSwap(false, true) { // did toggle?
return nil
}
p.mu.Lock()
defer p.mu.Unlock()
p.isShutdown = true
var retErr error
for _, sps := range spss {
for _, sps := range p.getSpanProcessors() {
select {
case <-ctx.Done():
return ctx.Err()
@@ -267,10 +302,14 @@ func (p *TracerProvider) Shutdown(ctx context.Context) error {
}
}
}
p.spanProcessors.Store(spanProcessorStates{})
p.spanProcessors.Store(&spanProcessorStates{})
return retErr
}
func (p *TracerProvider) getSpanProcessors() spanProcessorStates {
return *(p.spanProcessors.Load())
}
// TracerProviderOption configures a TracerProvider.
type TracerProviderOption interface {
apply(tracerProviderConfig) tracerProviderConfig
+6 -3
View File
@@ -19,12 +19,13 @@ import (
"sync"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/internal/global"
)
// simpleSpanProcessor is a SpanProcessor that synchronously sends all
// completed Spans to a trace.Exporter immediately.
type simpleSpanProcessor struct {
exporterMu sync.RWMutex
exporterMu sync.Mutex
exporter SpanExporter
stopOnce sync.Once
}
@@ -43,6 +44,8 @@ func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor {
ssp := &simpleSpanProcessor{
exporter: exporter,
}
global.Warn("SimpleSpanProcessor is not recommended for production use, consider using BatchSpanProcessor instead.")
return ssp
}
@@ -51,8 +54,8 @@ func (ssp *simpleSpanProcessor) OnStart(context.Context, ReadWriteSpan) {}
// OnEnd immediately exports a ReadOnlySpan.
func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) {
ssp.exporterMu.RLock()
defer ssp.exporterMu.RUnlock()
ssp.exporterMu.Lock()
defer ssp.exporterMu.Unlock()
if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() {
if err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}); err != nil {
+2 -2
View File
@@ -302,7 +302,7 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
// most a length of limit. Each string slice value is truncated in this fashion
// (the slice length itself is unaffected).
//
// No truncation is perfromed for a negative limit.
// No truncation is performed for a negative limit.
func truncateAttr(limit int, attr attribute.KeyValue) attribute.KeyValue {
if limit < 0 {
return attr
@@ -410,7 +410,7 @@ func (s *recordingSpan) End(options ...trace.SpanEndOption) {
}
s.mu.Unlock()
sps := s.tracer.provider.spanProcessors.Load().(spanProcessorStates)
sps := s.tracer.provider.getSpanProcessors()
if len(sps) == 0 {
return
}
+1 -1
View File
@@ -38,7 +38,7 @@ type SpanExporter interface {
// must never be done outside of a new major release.
// Shutdown notifies the exporter of a pending halt to operations. The
// exporter is expected to preform any cleanup or synchronization it
// exporter is expected to perform any cleanup or synchronization it
// requires while honoring all timeouts and cancellations contained in
// the passed context.
Shutdown(ctx context.Context) error
+2 -2
View File
@@ -62,11 +62,11 @@ type SpanProcessor interface {
type spanProcessorState struct {
sp SpanProcessor
state *sync.Once
state sync.Once
}
func newSpanProcessorState(sp SpanProcessor) *spanProcessorState {
return &spanProcessorState{sp: sp, state: &sync.Once{}}
return &spanProcessorState{sp: sp}
}
type spanProcessorStates []*spanProcessorState
+1 -1
View File
@@ -51,7 +51,7 @@ func (tr *tracer) Start(ctx context.Context, name string, options ...trace.SpanS
s := tr.newSpan(ctx, name, &config)
if rw, ok := s.(ReadWriteSpan); ok && s.IsRecording() {
sps := tr.provider.spanProcessors.Load().(spanProcessorStates)
sps := tr.provider.getSpanProcessors()
for _, sp := range sps {
sp.sp.OnStart(ctx, rw)
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sdk // import "go.opentelemetry.io/otel/sdk"
// Version is the current release version of the OpenTelemetry SDK in use.
func Version() string {
return "1.15.1"
}
+2 -2
View File
@@ -37,7 +37,7 @@ func (p noopTracerProvider) Tracer(string, ...TracerOption) Tracer {
return noopTracer{}
}
// noopTracer is an implementation of Tracer that preforms no operations.
// noopTracer is an implementation of Tracer that performs no operations.
type noopTracer struct{}
var _ Tracer = noopTracer{}
@@ -53,7 +53,7 @@ func (t noopTracer) Start(ctx context.Context, name string, _ ...SpanStartOption
return ContextWithSpan(ctx, span), span
}
// noopSpan is an implementation of Span that preforms no operations.
// noopSpan is an implementation of Span that performs no operations.
type noopSpan struct{}
var _ Span = noopSpan{}
+1 -1
View File
@@ -16,5 +16,5 @@ package otel // import "go.opentelemetry.io/otel"
// Version is the current release version of OpenTelemetry in use.
func Version() string {
return "1.14.0"
return "1.15.1"
}
+5 -5
View File
@@ -14,7 +14,7 @@
module-sets:
stable-v1:
version: v1.14.0
version: v1.15.1
modules:
- go.opentelemetry.io/otel
- go.opentelemetry.io/otel/bridge/opentracing
@@ -26,16 +26,16 @@ module-sets:
- go.opentelemetry.io/otel/example/passthrough
- go.opentelemetry.io/otel/example/zipkin
- go.opentelemetry.io/otel/exporters/jaeger
- go.opentelemetry.io/otel/exporters/zipkin
- go.opentelemetry.io/otel/exporters/otlp/internal/retry
- go.opentelemetry.io/otel/exporters/otlp/otlptrace
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
- go.opentelemetry.io/otel/exporters/otlp/internal/retry
- go.opentelemetry.io/otel/exporters/stdout/stdouttrace
- go.opentelemetry.io/otel/trace
- go.opentelemetry.io/otel/exporters/zipkin
- go.opentelemetry.io/otel/sdk
- go.opentelemetry.io/otel/trace
experimental-metrics:
version: v0.37.0
version: v0.38.1
modules:
- go.opentelemetry.io/otel/example/opencensus
- go.opentelemetry.io/otel/example/prometheus
-336
View File
@@ -1,336 +0,0 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.21.9
// source: google/rpc/code.proto
package code
import (
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// The canonical error codes for gRPC APIs.
//
// Sometimes multiple error codes may apply. Services should return
// the most specific error code that applies. For example, prefer
// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply.
// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`.
type Code int32
const (
// Not an error; returned on success.
//
// HTTP Mapping: 200 OK
Code_OK Code = 0
// The operation was cancelled, typically by the caller.
//
// HTTP Mapping: 499 Client Closed Request
Code_CANCELLED Code = 1
// Unknown error. For example, this error may be returned when
// a `Status` value received from another address space belongs to
// an error space that is not known in this address space. Also
// errors raised by APIs that do not return enough error information
// may be converted to this error.
//
// HTTP Mapping: 500 Internal Server Error
Code_UNKNOWN Code = 2
// The client specified an invalid argument. Note that this differs
// from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments
// that are problematic regardless of the state of the system
// (e.g., a malformed file name).
//
// HTTP Mapping: 400 Bad Request
Code_INVALID_ARGUMENT Code = 3
// The deadline expired before the operation could complete. For operations
// that change the state of the system, this error may be returned
// even if the operation has completed successfully. For example, a
// successful response from a server could have been delayed long
// enough for the deadline to expire.
//
// HTTP Mapping: 504 Gateway Timeout
Code_DEADLINE_EXCEEDED Code = 4
// Some requested entity (e.g., file or directory) was not found.
//
// Note to server developers: if a request is denied for an entire class
// of users, such as gradual feature rollout or undocumented allowlist,
// `NOT_FOUND` may be used. If a request is denied for some users within
// a class of users, such as user-based access control, `PERMISSION_DENIED`
// must be used.
//
// HTTP Mapping: 404 Not Found
Code_NOT_FOUND Code = 5
// The entity that a client attempted to create (e.g., file or directory)
// already exists.
//
// HTTP Mapping: 409 Conflict
Code_ALREADY_EXISTS Code = 6
// The caller does not have permission to execute the specified
// operation. `PERMISSION_DENIED` must not be used for rejections
// caused by exhausting some resource (use `RESOURCE_EXHAUSTED`
// instead for those errors). `PERMISSION_DENIED` must not be
// used if the caller can not be identified (use `UNAUTHENTICATED`
// instead for those errors). This error code does not imply the
// request is valid or the requested entity exists or satisfies
// other pre-conditions.
//
// HTTP Mapping: 403 Forbidden
Code_PERMISSION_DENIED Code = 7
// The request does not have valid authentication credentials for the
// operation.
//
// HTTP Mapping: 401 Unauthorized
Code_UNAUTHENTICATED Code = 16
// Some resource has been exhausted, perhaps a per-user quota, or
// perhaps the entire file system is out of space.
//
// HTTP Mapping: 429 Too Many Requests
Code_RESOURCE_EXHAUSTED Code = 8
// The operation was rejected because the system is not in a state
// required for the operation's execution. For example, the directory
// to be deleted is non-empty, an rmdir operation is applied to
// a non-directory, etc.
//
// Service implementors can use the following guidelines to decide
// between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:
//
// (a) Use `UNAVAILABLE` if the client can retry just the failing call.
// (b) Use `ABORTED` if the client should retry at a higher level. For
// example, when a client-specified test-and-set fails, indicating the
// client should restart a read-modify-write sequence.
// (c) Use `FAILED_PRECONDITION` if the client should not retry until
// the system state has been explicitly fixed. For example, if an "rmdir"
// fails because the directory is non-empty, `FAILED_PRECONDITION`
// should be returned since the client should not retry unless
// the files are deleted from the directory.
//
// HTTP Mapping: 400 Bad Request
Code_FAILED_PRECONDITION Code = 9
// The operation was aborted, typically due to a concurrency issue such as
// a sequencer check failure or transaction abort.
//
// See the guidelines above for deciding between `FAILED_PRECONDITION`,
// `ABORTED`, and `UNAVAILABLE`.
//
// HTTP Mapping: 409 Conflict
Code_ABORTED Code = 10
// The operation was attempted past the valid range. E.g., seeking or
// reading past end-of-file.
//
// Unlike `INVALID_ARGUMENT`, this error indicates a problem that may
// be fixed if the system state changes. For example, a 32-bit file
// system will generate `INVALID_ARGUMENT` if asked to read at an
// offset that is not in the range [0,2^32-1], but it will generate
// `OUT_OF_RANGE` if asked to read from an offset past the current
// file size.
//
// There is a fair bit of overlap between `FAILED_PRECONDITION` and
// `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific
// error) when it applies so that callers who are iterating through
// a space can easily look for an `OUT_OF_RANGE` error to detect when
// they are done.
//
// HTTP Mapping: 400 Bad Request
Code_OUT_OF_RANGE Code = 11
// The operation is not implemented or is not supported/enabled in this
// service.
//
// HTTP Mapping: 501 Not Implemented
Code_UNIMPLEMENTED Code = 12
// Internal errors. This means that some invariants expected by the
// underlying system have been broken. This error code is reserved
// for serious errors.
//
// HTTP Mapping: 500 Internal Server Error
Code_INTERNAL Code = 13
// The service is currently unavailable. This is most likely a
// transient condition, which can be corrected by retrying with
// a backoff. Note that it is not always safe to retry
// non-idempotent operations.
//
// See the guidelines above for deciding between `FAILED_PRECONDITION`,
// `ABORTED`, and `UNAVAILABLE`.
//
// HTTP Mapping: 503 Service Unavailable
Code_UNAVAILABLE Code = 14
// Unrecoverable data loss or corruption.
//
// HTTP Mapping: 500 Internal Server Error
Code_DATA_LOSS Code = 15
)
// Enum value maps for Code.
var (
Code_name = map[int32]string{
0: "OK",
1: "CANCELLED",
2: "UNKNOWN",
3: "INVALID_ARGUMENT",
4: "DEADLINE_EXCEEDED",
5: "NOT_FOUND",
6: "ALREADY_EXISTS",
7: "PERMISSION_DENIED",
16: "UNAUTHENTICATED",
8: "RESOURCE_EXHAUSTED",
9: "FAILED_PRECONDITION",
10: "ABORTED",
11: "OUT_OF_RANGE",
12: "UNIMPLEMENTED",
13: "INTERNAL",
14: "UNAVAILABLE",
15: "DATA_LOSS",
}
Code_value = map[string]int32{
"OK": 0,
"CANCELLED": 1,
"UNKNOWN": 2,
"INVALID_ARGUMENT": 3,
"DEADLINE_EXCEEDED": 4,
"NOT_FOUND": 5,
"ALREADY_EXISTS": 6,
"PERMISSION_DENIED": 7,
"UNAUTHENTICATED": 16,
"RESOURCE_EXHAUSTED": 8,
"FAILED_PRECONDITION": 9,
"ABORTED": 10,
"OUT_OF_RANGE": 11,
"UNIMPLEMENTED": 12,
"INTERNAL": 13,
"UNAVAILABLE": 14,
"DATA_LOSS": 15,
}
)
func (x Code) Enum() *Code {
p := new(Code)
*p = x
return p
}
func (x Code) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Code) Descriptor() protoreflect.EnumDescriptor {
return file_google_rpc_code_proto_enumTypes[0].Descriptor()
}
func (Code) Type() protoreflect.EnumType {
return &file_google_rpc_code_proto_enumTypes[0]
}
func (x Code) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Code.Descriptor instead.
func (Code) EnumDescriptor() ([]byte, []int) {
return file_google_rpc_code_proto_rawDescGZIP(), []int{0}
}
var File_google_rpc_code_proto protoreflect.FileDescriptor
var file_google_rpc_code_proto_rawDesc = []byte{
0x0a, 0x15, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x64,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x72, 0x70, 0x63, 0x2a, 0xb7, 0x02, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x06, 0x0a, 0x02,
0x4f, 0x4b, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45,
0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x02,
0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x41, 0x52, 0x47, 0x55,
0x4d, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49,
0x4e, 0x45, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0d, 0x0a,
0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e,
0x41, 0x4c, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x53, 0x10, 0x06,
0x12, 0x15, 0x0a, 0x11, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44,
0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x41, 0x55, 0x54,
0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x10, 0x12, 0x16, 0x0a, 0x12,
0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x48, 0x41, 0x55, 0x53, 0x54,
0x45, 0x44, 0x10, 0x08, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x50,
0x52, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x09, 0x12, 0x0b, 0x0a,
0x07, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x55,
0x54, 0x5f, 0x4f, 0x46, 0x5f, 0x52, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x0b, 0x12, 0x11, 0x0a, 0x0d,
0x55, 0x4e, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x0c, 0x12,
0x0c, 0x0a, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x0d, 0x12, 0x0f, 0x0a,
0x0b, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0e, 0x12, 0x0d,
0x0a, 0x09, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x4c, 0x4f, 0x53, 0x53, 0x10, 0x0f, 0x42, 0x58, 0x0a,
0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x42,
0x09, 0x43, 0x6f, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x33, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f,
0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61,
0x70, 0x69, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3b, 0x63, 0x6f, 0x64,
0x65, 0xa2, 0x02, 0x03, 0x52, 0x50, 0x43, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_google_rpc_code_proto_rawDescOnce sync.Once
file_google_rpc_code_proto_rawDescData = file_google_rpc_code_proto_rawDesc
)
func file_google_rpc_code_proto_rawDescGZIP() []byte {
file_google_rpc_code_proto_rawDescOnce.Do(func() {
file_google_rpc_code_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_rpc_code_proto_rawDescData)
})
return file_google_rpc_code_proto_rawDescData
}
var file_google_rpc_code_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_google_rpc_code_proto_goTypes = []interface{}{
(Code)(0), // 0: google.rpc.Code
}
var file_google_rpc_code_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_google_rpc_code_proto_init() }
func file_google_rpc_code_proto_init() {
if File_google_rpc_code_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_google_rpc_code_proto_rawDesc,
NumEnums: 1,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_google_rpc_code_proto_goTypes,
DependencyIndexes: file_google_rpc_code_proto_depIdxs,
EnumInfos: file_google_rpc_code_proto_enumTypes,
}.Build()
File_google_rpc_code_proto = out.File
file_google_rpc_code_proto_rawDesc = nil
file_google_rpc_code_proto_goTypes = nil
file_google_rpc_code_proto_depIdxs = nil
}
+23 -21
View File
@@ -254,7 +254,7 @@ github.com/bombsimon/logrusr/v3
# github.com/cenkalti/backoff v2.2.1+incompatible
## explicit
github.com/cenkalti/backoff
# github.com/cenkalti/backoff/v4 v4.2.0
# github.com/cenkalti/backoff/v4 v4.2.1
## explicit; go 1.18
github.com/cenkalti/backoff/v4
# github.com/ceph/go-ceph v0.18.0
@@ -926,9 +926,9 @@ github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker
# github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
## explicit; go 1.17
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus
# github.com/go-micro/plugins/v4/wrapper/trace/opencensus v1.1.0
# github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
## explicit; go 1.17
github.com/go-micro/plugins/v4/wrapper/trace/opencensus
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry
# github.com/go-ozzo/ozzo-validation/v4 v4.3.0
## explicit; go 1.13
github.com/go-ozzo/ozzo-validation/v4
@@ -1780,7 +1780,6 @@ go.opencensus.io/internal/tagencoding
go.opencensus.io/metric/metricdata
go.opencensus.io/metric/metricexport
go.opencensus.io/metric/metricproducer
go.opencensus.io/plugin/ocgrpc
go.opencensus.io/plugin/ochttp
go.opencensus.io/plugin/ochttp/propagation/b3
go.opencensus.io/resource
@@ -1792,14 +1791,16 @@ go.opencensus.io/trace
go.opencensus.io/trace/internal
go.opencensus.io/trace/propagation
go.opencensus.io/trace/tracestate
go.opencensus.io/zpages
go.opencensus.io/zpages/internal
# go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4
## explicit; go 1.18
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal
# go.opentelemetry.io/otel v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/contrib/zpages v0.41.1
## explicit; go 1.19
go.opentelemetry.io/contrib/zpages
go.opentelemetry.io/contrib/zpages/internal
# go.opentelemetry.io/otel v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel
go.opentelemetry.io/otel/attribute
go.opentelemetry.io/otel/baggage
@@ -1816,33 +1817,35 @@ go.opentelemetry.io/otel/semconv/v1.10.0
go.opentelemetry.io/otel/semconv/v1.12.0
go.opentelemetry.io/otel/semconv/v1.17.0
go.opentelemetry.io/otel/semconv/v1.4.0
# go.opentelemetry.io/otel/exporters/jaeger v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/otel/exporters/jaeger v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel/exporters/jaeger
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/agent
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/zipkincore
go.opentelemetry.io/otel/exporters/jaeger/internal/third_party/thrift/lib/go/thrift
# go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel/exporters/otlp/internal/retry
# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel/exporters/otlp/otlptrace
go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal
go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlpconfig
go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform
# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
# go.opentelemetry.io/otel/sdk v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/otel/sdk v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel/sdk
go.opentelemetry.io/otel/sdk/instrumentation
go.opentelemetry.io/otel/sdk/internal
go.opentelemetry.io/otel/sdk/internal/env
go.opentelemetry.io/otel/sdk/resource
go.opentelemetry.io/otel/sdk/trace
# go.opentelemetry.io/otel/trace v1.14.0
## explicit; go 1.18
# go.opentelemetry.io/otel/trace v1.15.1
## explicit; go 1.19
go.opentelemetry.io/otel/trace
# go.opentelemetry.io/proto/otlp v0.19.0
## explicit; go 1.14
@@ -2019,7 +2022,6 @@ google.golang.org/appengine/urlfetch
google.golang.org/genproto/googleapis/api
google.golang.org/genproto/googleapis/api/annotations
google.golang.org/genproto/googleapis/api/httpbody
google.golang.org/genproto/googleapis/rpc/code
google.golang.org/genproto/googleapis/rpc/errdetails
google.golang.org/genproto/googleapis/rpc/status
google.golang.org/genproto/protobuf/field_mask