-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexporter.go
182 lines (153 loc) · 5.75 KB
/
exporter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package telemetry
import (
"context"
"fmt"
"github.com/go-logr/logr"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
// Data defines common telemetry data points for NGINX Kubernetes-related projects.
//
//go:generate go run -tags=generator github.com/nginx/telemetry-exporter/cmd/generator -type Data
type Data struct {
// ProjectName is the name of the project.
ProjectName string
// ProjectVersion is the version of the project.
ProjectVersion string
// ProjectArchitecture is the architecture of the project. For example, "amd64".
ProjectArchitecture string
// ClusterID is the unique id of the Kubernetes cluster where the project is installed.
// It is the UID of the `kube-system` Namespace.
ClusterID string
// ClusterVersion is the Kubernetes version of the cluster.
ClusterVersion string
// ClusterPlatform is the Kubernetes platform of the cluster.
ClusterPlatform string
// InstallationID is the unique id of the project installation in the cluster.
InstallationID string
// ClusterNodeCount is the number of nodes in the cluster.
ClusterNodeCount int64
}
// Exportable allows exporting telemetry data using the Exporter.
type Exportable interface {
// Attributes returns a list of key-value pairs that represent the telemetry data.
Attributes() []attribute.KeyValue
}
// ExporterConfig contains the configuration for the Exporter.
type ExporterConfig struct {
// SpanProvider contains SpanProvider for exporting spans.
SpanProvider SpanProvider
}
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . SpanExporter
// SpanExporter is used to generate a fake for the unit test.
type SpanExporter interface {
sdktrace.SpanExporter
}
// Exporter exports telemetry data.
type Exporter struct {
spanProvider SpanProvider
traceProvider *sdktrace.TracerProvider
handler *ErrorHandler
}
type optionsCfg struct {
errorHandler *ErrorHandler
logger logr.Logger
}
// Option is a configuration option for the Exporter.
type Option func(*optionsCfg)
// WithGlobalOTelErrorHandler sets the global OpenTelemetry error handler.
//
// Note that the error handler captures all errors generated by the OpenTelemetry SDK.
// The Exporter uses it to catch errors that occur during exporting.
// If this option is not used, the Exporter will not be able to catch errors that occur during the export process.
//
// Warning: This option changes the global OpenTelemetry state. If OpenTelemetry is used in other parts of
// your application, the error handler will catch errors from those parts as well. As a result, the Exporter might
// return errors when exporting telemetry data, even if no error occurred.
func WithGlobalOTelErrorHandler(errorHandler *ErrorHandler) Option {
return func(o *optionsCfg) {
o.errorHandler = errorHandler
}
}
// WithGlobalOTelLogger sets the global OpenTelemetry logger.
// The logger is used by the OpenTelemetry SDK to log messages.
//
// Warning: This option changes the global OpenTelemetry state. If OpenTelemetry is used in other parts of your
// application, the logger will be used for those parts as well.
func WithGlobalOTelLogger(logger logr.Logger) Option {
return func(o *optionsCfg) {
o.logger = logger
}
}
// NewExporter creates a new Exporter.
func NewExporter(cfg ExporterConfig, options ...Option) (*Exporter, error) {
var optCfg optionsCfg
for _, opt := range options {
opt(&optCfg)
}
if optCfg.errorHandler != nil {
otel.SetErrorHandler(optCfg.errorHandler)
}
if (optCfg.logger != logr.Logger{}) {
otel.SetLogger(optCfg.logger)
}
res, err := resource.Merge(
resource.Default(),
resource.NewSchemaless(),
)
if err != nil {
return nil, fmt.Errorf("failed to create an OTel resource: %w", err)
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
)
return &Exporter{
spanProvider: cfg.SpanProvider,
traceProvider: tracerProvider,
handler: optCfg.errorHandler,
}, nil
}
// Export exports telemetry data.
func (e *Exporter) Export(ctx context.Context, exportable Exportable) error {
spanExporter, err := e.spanProvider(ctx)
if err != nil {
return fmt.Errorf("failed to create span exporter: %w", err)
}
// We create a new span processor for each export to ensure the Exporter doesn't keep a GRPC connection to
// the OTLP endpoint in between exports.
// We create a SpanProcessor that synchronously exports spans to the OTLP endpoint.
// As mentioned in the NewSimpleSpanProcessor doc, it is not recommended to use this in production,
// because it is synchronous. However, in our case, we only send one span and we want to catch errors during
// sending, so synchronous is good for us.
spanProcessor := sdktrace.NewSimpleSpanProcessor(spanExporter)
defer func() {
// This error is ignored because it happens after the span has been exported, so it is not useful.
_ = spanProcessor.Shutdown(ctx)
}()
e.traceProvider.RegisterSpanProcessor(spanProcessor)
defer e.traceProvider.UnregisterSpanProcessor(spanProcessor)
if e.handler != nil {
e.handler.Clear()
}
tracer := e.traceProvider.Tracer("product-telemetry")
_, span := tracer.Start(ctx, "report")
span.SetAttributes(exportable.Attributes()...)
// Because we use a synchronous span processor, the span is exported immediately and synchronously.
// Any error will be caught by the error handler.
span.End()
if e.handler != nil {
if handlerErr := e.handler.Error(); handlerErr != nil {
return fmt.Errorf("failed to export telemetry: %w", handlerErr)
}
}
return nil
}
// Shutdown shuts down the Exporter.
func (e *Exporter) Shutdown(ctx context.Context) error {
if err := e.traceProvider.Shutdown(ctx); err != nil {
return fmt.Errorf("failed to shutdown OTel trace provider: %w", err)
}
return nil
}