fix: update to latest otel semconv (#1668)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
This commit is contained in:
parent
c07ffba554
commit
81855d76f9
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"go.opentelemetry.io/otel/sdk/metric"
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
"go.opentelemetry.io/otel/sdk/trace"
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"go.opentelemetry.io/otel/sdk/metric"
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zaptest/observer"
|
"go.uber.org/zap/zaptest/observer"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||||
msdk "go.opentelemetry.io/otel/sdk/metric"
|
msdk "go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -19,15 +19,15 @@ const (
|
||||||
FeatureFlagReasonKey = attribute.Key("feature_flag.reason")
|
FeatureFlagReasonKey = attribute.Key("feature_flag.reason")
|
||||||
ExceptionTypeKey = attribute.Key("ExceptionTypeKeyName")
|
ExceptionTypeKey = attribute.Key("ExceptionTypeKeyName")
|
||||||
|
|
||||||
httpRequestDurationMetric = "http.server.duration"
|
httpRequestDurationMetric = "http.server.request.duration"
|
||||||
httpResponseSizeMetric = "http.server.response.size"
|
httpResponseSizeMetric = "http.server.response.body.size"
|
||||||
httpActiveRequestsMetric = "http.server.active_requests"
|
httpActiveRequestsMetric = "http.server.active_requests"
|
||||||
impressionMetric = "feature_flag." + ProviderName + ".impression"
|
impressionMetric = "feature_flag." + ProviderName + ".impression"
|
||||||
reasonMetric = "feature_flag." + ProviderName + ".evaluation.reason"
|
reasonMetric = "feature_flag." + ProviderName + ".result.reason"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IMetricsRecorder interface {
|
type IMetricsRecorder interface {
|
||||||
HTTPAttributes(svcName, url, method, code string) []attribute.KeyValue
|
HTTPAttributes(svcName, url, method, code, scheme string) []attribute.KeyValue
|
||||||
HTTPRequestDuration(ctx context.Context, duration time.Duration, attrs []attribute.KeyValue)
|
HTTPRequestDuration(ctx context.Context, duration time.Duration, attrs []attribute.KeyValue)
|
||||||
HTTPResponseSize(ctx context.Context, sizeBytes int64, attrs []attribute.KeyValue)
|
HTTPResponseSize(ctx context.Context, sizeBytes int64, attrs []attribute.KeyValue)
|
||||||
InFlightRequestStart(ctx context.Context, attrs []attribute.KeyValue)
|
InFlightRequestStart(ctx context.Context, attrs []attribute.KeyValue)
|
||||||
|
|
@ -38,7 +38,7 @@ type IMetricsRecorder interface {
|
||||||
|
|
||||||
type NoopMetricsRecorder struct{}
|
type NoopMetricsRecorder struct{}
|
||||||
|
|
||||||
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _ string) []attribute.KeyValue {
|
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _, _ string) []attribute.KeyValue {
|
||||||
return []attribute.KeyValue{}
|
return []attribute.KeyValue{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,12 +68,13 @@ type MetricsRecorder struct {
|
||||||
reasons metric.Int64Counter
|
reasons metric.Int64Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r MetricsRecorder) HTTPAttributes(svcName, url, method, code string) []attribute.KeyValue {
|
func (r MetricsRecorder) HTTPAttributes(svcName, url, method, code, scheme string) []attribute.KeyValue {
|
||||||
return []attribute.KeyValue{
|
return []attribute.KeyValue{
|
||||||
semconv.ServiceNameKey.String(svcName),
|
semconv.ServiceNameKey.String(svcName),
|
||||||
semconv.HTTPURLKey.String(url),
|
semconv.HTTPRouteKey.String(url),
|
||||||
semconv.HTTPMethodKey.String(method),
|
semconv.HTTPRequestMethodKey.String(method),
|
||||||
semconv.HTTPStatusCodeKey.String(code),
|
semconv.HTTPResponseStatusCodeKey.String(code),
|
||||||
|
semconv.URLSchemeKey.String(scheme),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"go.opentelemetry.io/otel/sdk/metric"
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.13.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
const svcName = "mySvc"
|
const svcName = "mySvc"
|
||||||
|
|
@ -38,9 +38,10 @@ func TestHTTPAttributes(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: []attribute.KeyValue{
|
want: []attribute.KeyValue{
|
||||||
semconv.ServiceNameKey.String(""),
|
semconv.ServiceNameKey.String(""),
|
||||||
semconv.HTTPURLKey.String(""),
|
semconv.HTTPRouteKey.String(""),
|
||||||
semconv.HTTPMethodKey.String(""),
|
semconv.HTTPRequestMethodKey.String(""),
|
||||||
semconv.HTTPStatusCodeKey.String(""),
|
semconv.HTTPResponseStatusCodeKey.String(""),
|
||||||
|
semconv.URLSchemeKey.String("http"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -53,9 +54,10 @@ func TestHTTPAttributes(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: []attribute.KeyValue{
|
want: []attribute.KeyValue{
|
||||||
semconv.ServiceNameKey.String("myService"),
|
semconv.ServiceNameKey.String("myService"),
|
||||||
semconv.HTTPURLKey.String("#123"),
|
semconv.HTTPRouteKey.String("#123"),
|
||||||
semconv.HTTPMethodKey.String("POST"),
|
semconv.HTTPRequestMethodKey.String("POST"),
|
||||||
semconv.HTTPStatusCodeKey.String("300"),
|
semconv.HTTPResponseStatusCodeKey.String("300"),
|
||||||
|
semconv.URLSchemeKey.String("http"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -68,16 +70,17 @@ func TestHTTPAttributes(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: []attribute.KeyValue{
|
want: []attribute.KeyValue{
|
||||||
semconv.ServiceNameKey.String("!@#$%^&*()_+|}{[];',./<>"),
|
semconv.ServiceNameKey.String("!@#$%^&*()_+|}{[];',./<>"),
|
||||||
semconv.HTTPURLKey.String(""),
|
semconv.HTTPRouteKey.String(""),
|
||||||
semconv.HTTPMethodKey.String(""),
|
semconv.HTTPRequestMethodKey.String(""),
|
||||||
semconv.HTTPStatusCodeKey.String(""),
|
semconv.HTTPResponseStatusCodeKey.String(""),
|
||||||
|
semconv.URLSchemeKey.String("http"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
rec := MetricsRecorder{}
|
rec := MetricsRecorder{}
|
||||||
res := rec.HTTPAttributes(tt.req.Service, tt.req.ID, tt.req.Method, tt.req.Code)
|
res := rec.HTTPAttributes(tt.req.Service, tt.req.ID, tt.req.Method, tt.req.Code, "http")
|
||||||
require.Equal(t, tt.want, res)
|
require.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +211,7 @@ func TestMetrics(t *testing.T) {
|
||||||
// some really simple tests just to make sure all methods are actually implemented and nothing panics
|
// some really simple tests just to make sure all methods are actually implemented and nothing panics
|
||||||
func TestNoopMetricsRecorder_HTTPAttributes(t *testing.T) {
|
func TestNoopMetricsRecorder_HTTPAttributes(t *testing.T) {
|
||||||
no := NoopMetricsRecorder{}
|
no := NoopMetricsRecorder{}
|
||||||
got := no.HTTPAttributes("", "", "", "")
|
got := no.HTTPAttributes("", "", "", "", "")
|
||||||
require.Empty(t, got)
|
require.Empty(t, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package telemetry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// utils contain common utilities to help with telemetry
|
// utils contain common utilities to help with telemetry
|
||||||
|
|
@ -14,7 +14,7 @@ const provider = "flagd"
|
||||||
func SemConvFeatureFlagAttributes(ffKey string, ffVariant string) []attribute.KeyValue {
|
func SemConvFeatureFlagAttributes(ffKey string, ffVariant string) []attribute.KeyValue {
|
||||||
return []attribute.KeyValue{
|
return []attribute.KeyValue{
|
||||||
semconv.FeatureFlagKey(ffKey),
|
semconv.FeatureFlagKey(ffKey),
|
||||||
semconv.FeatureFlagVariant(ffVariant),
|
semconv.FeatureFlagResultVariant(ffVariant),
|
||||||
semconv.FeatureFlagProviderName(provider),
|
semconv.FeatureFlagProviderName(provider),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSemConvFeatureFlagAttributes(t *testing.T) {
|
func TestSemConvFeatureFlagAttributes(t *testing.T) {
|
||||||
|
|
@ -35,7 +35,7 @@ func TestSemConvFeatureFlagAttributes(t *testing.T) {
|
||||||
case semconv.FeatureFlagKeyKey:
|
case semconv.FeatureFlagKeyKey:
|
||||||
require.Equal(t, test.key, attribute.Value.AsString(),
|
require.Equal(t, test.key, attribute.Value.AsString(),
|
||||||
"expected flag key: %s, but received: %s", test.key, attribute.Value.AsString())
|
"expected flag key: %s, but received: %s", test.key, attribute.Value.AsString())
|
||||||
case semconv.FeatureFlagVariantKey:
|
case semconv.FeatureFlagResultVariantKey:
|
||||||
require.Equal(t, test.variant, attribute.Value.AsString(),
|
require.Equal(t, test.variant, attribute.Value.AsString(),
|
||||||
"expected flag variant: %s, but received %s", test.variant, attribute.Value.AsString())
|
"expected flag variant: %s, but received %s", test.variant, attribute.Value.AsString())
|
||||||
case semconv.FeatureFlagProviderNameKey:
|
case semconv.FeatureFlagProviderNameKey:
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,25 @@ Given below is the current implementation overview of flagd telemetry internals,
|
||||||
|
|
||||||
flagd exposes the following metrics:
|
flagd exposes the following metrics:
|
||||||
|
|
||||||
- `http.server.duration`
|
- `http.server.request.duration` - Measures the duration of inbound HTTP requests
|
||||||
- `http.server.response.size`
|
- `http.server.response.body.size` - Measures the size of HTTP response messages
|
||||||
- `http.server.active_requests`
|
- `http.server.active_requests` - Measures the number of concurrent HTTP requests that are currently in-flight
|
||||||
- `feature_flag.flagd.impression`
|
- `feature_flag.flagd.impression` - Measures the number of evaluations for a given flag
|
||||||
- `feature_flag.flagd.evaluation.reason`
|
- `feature_flag.flagd.result.reason` - Measures the number of evaluations for a given reason
|
||||||
|
|
||||||
> Please note that metric names may vary based on the consuming monitoring tool naming requirements.
|
> Please note that metric names may vary based on the consuming monitoring tool naming requirements.
|
||||||
> For example, the transformation of OTLP metrics to Prometheus is described [here](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus).
|
> For example, the transformation of OTLP metrics to Prometheus is described [here](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus).
|
||||||
|
|
||||||
|
### HTTP Metric Attributes
|
||||||
|
|
||||||
|
flagd uses the following OpenTelemetry Semantic Conventions for HTTP metrics:
|
||||||
|
|
||||||
|
- `service.name` - The name of the service
|
||||||
|
- `http.route` - The matched route (path template)
|
||||||
|
- `http.request.method` - The HTTP request method (GET, POST, etc.)
|
||||||
|
- `http.response.status_code` - The HTTP response status code
|
||||||
|
- `url.scheme` - The URI scheme (http or https)
|
||||||
|
|
||||||
## Traces
|
## Traces
|
||||||
|
|
||||||
flagd creates the following spans as part of a trace:
|
flagd creates the following spans as part of a trace:
|
||||||
|
|
@ -83,9 +93,8 @@ official [OTEL collector example](https://github.com/open-telemetry/opentelemetr
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
# Jaeger
|
jaeger:
|
||||||
jaeger-all-in-one:
|
image: cr.jaegertracing.io/jaegertracing/jaeger:2.8.0
|
||||||
image: jaegertracing/all-in-one:latest
|
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "16686:16686"
|
- "16686:16686"
|
||||||
|
|
@ -93,7 +102,7 @@ services:
|
||||||
- "14250"
|
- "14250"
|
||||||
# Collector
|
# Collector
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: otel/opentelemetry-collector:latest
|
image: otel/opentelemetry-collector:0.129.1
|
||||||
restart: always
|
restart: always
|
||||||
command: [ "--config=/etc/otel-collector-config.yaml" ]
|
command: [ "--config=/etc/otel-collector-config.yaml" ]
|
||||||
volumes:
|
volumes:
|
||||||
|
|
@ -106,10 +115,10 @@ services:
|
||||||
- "4317:4317" # OTLP gRPC receiver
|
- "4317:4317" # OTLP gRPC receiver
|
||||||
- "55679:55679" # zpages extension
|
- "55679:55679" # zpages extension
|
||||||
depends_on:
|
depends_on:
|
||||||
- jaeger-all-in-one
|
- jaeger
|
||||||
prometheus:
|
prometheus:
|
||||||
container_name: prometheus
|
container_name: prometheus
|
||||||
image: prom/prometheus:latest
|
image: prom/prometheus:v2.53.5
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
|
||||||
|
|
@ -128,10 +137,8 @@ receivers:
|
||||||
exporters:
|
exporters:
|
||||||
prometheus:
|
prometheus:
|
||||||
endpoint: "0.0.0.0:8889"
|
endpoint: "0.0.0.0:8889"
|
||||||
const_labels:
|
|
||||||
label1: value1
|
|
||||||
otlp/jaeger:
|
otlp/jaeger:
|
||||||
endpoint: jaeger-all-in-one:4317
|
endpoint: jaeger:4317
|
||||||
tls:
|
tls:
|
||||||
insecure: true
|
insecure: true
|
||||||
processors:
|
processors:
|
||||||
|
|
@ -148,7 +155,7 @@ service:
|
||||||
exporters: [ prometheus ]
|
exporters: [ prometheus ]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### prometheus.yml
|
#### prometheus.yaml
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
|
|
@ -156,10 +163,9 @@ scrape_configs:
|
||||||
scrape_interval: 10s
|
scrape_interval: 10s
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: [ 'otel-collector:8889' ]
|
- targets: [ 'otel-collector:8889' ]
|
||||||
- targets: [ 'otel-collector:8888' ]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Once, configuration files are ready, use `docker-compose up` to start the local setup. With successful startup, you can
|
Once, configuration files are ready, use `docker compose up` to start the local setup. With successful startup, you can
|
||||||
access metrics through [Prometheus](http://localhost:9090/graph) & traces through [Jaeger](http://localhost:16686/).
|
access metrics through [Prometheus](http://localhost:9090/graph) & traces through [Jaeger](http://localhost:16686/).
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ func (m Middleware) Measure(ctx context.Context, handlerID string, reporter Repo
|
||||||
hid,
|
hid,
|
||||||
reporter.Method(),
|
reporter.Method(),
|
||||||
code,
|
code,
|
||||||
|
reporter.Scheme(),
|
||||||
)
|
)
|
||||||
|
|
||||||
m.cfg.MetricRecorder.InFlightRequestStart(ctx, httpAttrs)
|
m.cfg.MetricRecorder.InFlightRequestStart(ctx, httpAttrs)
|
||||||
|
|
@ -112,6 +113,7 @@ type Reporter interface {
|
||||||
URLPath() string
|
URLPath() string
|
||||||
StatusCode() int
|
StatusCode() int
|
||||||
BytesWritten() int64
|
BytesWritten() int64
|
||||||
|
Scheme() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type stdReporter struct {
|
type stdReporter struct {
|
||||||
|
|
@ -127,6 +129,13 @@ func (s *stdReporter) StatusCode() int { return s.w.statusCode }
|
||||||
|
|
||||||
func (s *stdReporter) BytesWritten() int64 { return int64(s.w.bytesWritten) }
|
func (s *stdReporter) BytesWritten() int64 { return int64(s.w.bytesWritten) }
|
||||||
|
|
||||||
|
func (s *stdReporter) Scheme() string {
|
||||||
|
if s.r.TLS != nil {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
// responseWriterInterceptor is a simple wrapper to intercept set data on a
|
// responseWriterInterceptor is a simple wrapper to intercept set data on a
|
||||||
// ResponseWriter.
|
// ResponseWriter.
|
||||||
type responseWriterInterceptor struct {
|
type responseWriterInterceptor struct {
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,10 @@ func (m *MockReporter) BytesWritten() int64 {
|
||||||
return m.Bytes
|
return m.Bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockReporter) Scheme() string {
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockReporter) URLCalled() bool { return m.urlCalled }
|
func (m *MockReporter) URLCalled() bool { return m.urlCalled }
|
||||||
func (m *MockReporter) MethodCalled() bool { return m.methodCalled }
|
func (m *MockReporter) MethodCalled() bool { return m.methodCalled }
|
||||||
func (m *MockReporter) StatusCalled() bool { return m.statusCalled }
|
func (m *MockReporter) StatusCalled() bool { return m.statusCalled }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue