mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-06 19:40:42 -05:00
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:
committed by
GitHub
parent
12dff34442
commit
632b206675
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
-31
@@ -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)
|
||||
}
|
||||
```
|
||||
-150
@@ -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
@@ -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,
|
||||
}
|
||||
)
|
||||
-45
@@ -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{}
|
||||
}
|
||||
-81
@@ -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()
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+14
@@ -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()),
|
||||
)
|
||||
```
|
||||
Generated
Vendored
+55
@@ -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
|
||||
}
|
||||
+72
@@ -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
|
||||
}
|
||||
}
|
||||
+175
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()]...)
|
||||
}
|
||||
Generated
Vendored
+6
-5
@@ -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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
</body>
|
||||
</html>
|
||||
+11
@@ -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>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<table style="border-spacing: 0">
|
||||
<tr>
|
||||
<td colspan=1 align=left><b>Span Name</b></td>
|
||||
<td> | </td><td colspan=1 align="center"><b>Running</b></td>
|
||||
<td> | </td>
|
||||
<td colspan=9 align="center"><b>Latency Samples</b></td>
|
||||
<td> | </td>
|
||||
<td colspan=1 align="center"><b>Error Samples</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan=1></td>
|
||||
<td> | </td>
|
||||
<td colspan=1></td>
|
||||
<td> | </td>
|
||||
{{range .LatencyBucketNames}}<th colspan=1 align="center"><b>[{{.}}]</b></th>{{end}}
|
||||
<td> | </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> | </td>
|
||||
{{- if $links -}}
|
||||
<td align="center"><a href="{{$a}}?zspanname={{$name}}&ztype=0">{{.Active}}</a></td>
|
||||
{{- else -}}
|
||||
<td>{{.Active}}</td>
|
||||
{{- end -}}
|
||||
<td> | </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> | </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>
|
||||
+10
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -7,6 +7,8 @@ Thumbs.db
|
||||
*.iml
|
||||
*.so
|
||||
coverage.*
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
gen/
|
||||
|
||||
|
||||
+2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -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 {
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -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.
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -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{
|
||||
|
||||
Generated
Vendored
+6
-5
@@ -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()
|
||||
}
|
||||
Generated
Vendored
+2
-1
@@ -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 {
|
||||
|
||||
+4
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
}
|
||||
Generated
Vendored
+6
-6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
Vendored
+23
-21
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user