fix: update to latest otel semconv (#1668)

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
This commit is contained in:
Michael Beemer 2025-07-17 12:36:02 -04:00 committed by GitHub
parent c07ffba554
commit 81855d76f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 68 additions and 45 deletions

View File

@ -21,7 +21,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"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"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

View File

@ -12,7 +12,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"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/zaptest/observer"
)

View File

@ -10,7 +10,7 @@ import (
"go.opentelemetry.io/otel/sdk/instrumentation"
msdk "go.opentelemetry.io/otel/sdk/metric"
"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 (
@ -19,15 +19,15 @@ const (
FeatureFlagReasonKey = attribute.Key("feature_flag.reason")
ExceptionTypeKey = attribute.Key("ExceptionTypeKeyName")
httpRequestDurationMetric = "http.server.duration"
httpResponseSizeMetric = "http.server.response.size"
httpRequestDurationMetric = "http.server.request.duration"
httpResponseSizeMetric = "http.server.response.body.size"
httpActiveRequestsMetric = "http.server.active_requests"
impressionMetric = "feature_flag." + ProviderName + ".impression"
reasonMetric = "feature_flag." + ProviderName + ".evaluation.reason"
reasonMetric = "feature_flag." + ProviderName + ".result.reason"
)
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)
HTTPResponseSize(ctx context.Context, sizeBytes int64, attrs []attribute.KeyValue)
InFlightRequestStart(ctx context.Context, attrs []attribute.KeyValue)
@ -38,7 +38,7 @@ type IMetricsRecorder interface {
type NoopMetricsRecorder struct{}
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _ string) []attribute.KeyValue {
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _, _ string) []attribute.KeyValue {
return []attribute.KeyValue{}
}
@ -68,12 +68,13 @@ type MetricsRecorder struct {
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{
semconv.ServiceNameKey.String(svcName),
semconv.HTTPURLKey.String(url),
semconv.HTTPMethodKey.String(method),
semconv.HTTPStatusCodeKey.String(code),
semconv.HTTPRouteKey.String(url),
semconv.HTTPRequestMethodKey.String(method),
semconv.HTTPResponseStatusCodeKey.String(code),
semconv.URLSchemeKey.String(scheme),
}
}

View File

@ -10,7 +10,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"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"
@ -38,9 +38,10 @@ func TestHTTPAttributes(t *testing.T) {
},
want: []attribute.KeyValue{
semconv.ServiceNameKey.String(""),
semconv.HTTPURLKey.String(""),
semconv.HTTPMethodKey.String(""),
semconv.HTTPStatusCodeKey.String(""),
semconv.HTTPRouteKey.String(""),
semconv.HTTPRequestMethodKey.String(""),
semconv.HTTPResponseStatusCodeKey.String(""),
semconv.URLSchemeKey.String("http"),
},
},
{
@ -53,9 +54,10 @@ func TestHTTPAttributes(t *testing.T) {
},
want: []attribute.KeyValue{
semconv.ServiceNameKey.String("myService"),
semconv.HTTPURLKey.String("#123"),
semconv.HTTPMethodKey.String("POST"),
semconv.HTTPStatusCodeKey.String("300"),
semconv.HTTPRouteKey.String("#123"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPResponseStatusCodeKey.String("300"),
semconv.URLSchemeKey.String("http"),
},
},
{
@ -68,16 +70,17 @@ func TestHTTPAttributes(t *testing.T) {
},
want: []attribute.KeyValue{
semconv.ServiceNameKey.String("!@#$%^&*()_+|}{[];',./<>"),
semconv.HTTPURLKey.String(""),
semconv.HTTPMethodKey.String(""),
semconv.HTTPStatusCodeKey.String(""),
semconv.HTTPRouteKey.String(""),
semconv.HTTPRequestMethodKey.String(""),
semconv.HTTPResponseStatusCodeKey.String(""),
semconv.URLSchemeKey.String("http"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
})
}
@ -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
func TestNoopMetricsRecorder_HTTPAttributes(t *testing.T) {
no := NoopMetricsRecorder{}
got := no.HTTPAttributes("", "", "", "")
got := no.HTTPAttributes("", "", "", "", "")
require.Empty(t, got)
}

View File

@ -2,7 +2,7 @@ package telemetry
import (
"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
@ -14,7 +14,7 @@ const provider = "flagd"
func SemConvFeatureFlagAttributes(ffKey string, ffVariant string) []attribute.KeyValue {
return []attribute.KeyValue{
semconv.FeatureFlagKey(ffKey),
semconv.FeatureFlagVariant(ffVariant),
semconv.FeatureFlagResultVariant(ffVariant),
semconv.FeatureFlagProviderName(provider),
}
}

View File

@ -4,7 +4,7 @@ import (
"testing"
"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) {
@ -35,7 +35,7 @@ func TestSemConvFeatureFlagAttributes(t *testing.T) {
case semconv.FeatureFlagKeyKey:
require.Equal(t, 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(),
"expected flag variant: %s, but received %s", test.variant, attribute.Value.AsString())
case semconv.FeatureFlagProviderNameKey:

View File

@ -47,15 +47,25 @@ Given below is the current implementation overview of flagd telemetry internals,
flagd exposes the following metrics:
- `http.server.duration`
- `http.server.response.size`
- `http.server.active_requests`
- `feature_flag.flagd.impression`
- `feature_flag.flagd.evaluation.reason`
- `http.server.request.duration` - Measures the duration of inbound HTTP requests
- `http.server.response.body.size` - Measures the size of HTTP response messages
- `http.server.active_requests` - Measures the number of concurrent HTTP requests that are currently in-flight
- `feature_flag.flagd.impression` - Measures the number of evaluations for a given flag
- `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.
> 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
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
services:
# Jaeger
jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
jaeger:
image: cr.jaegertracing.io/jaegertracing/jaeger:2.8.0
restart: always
ports:
- "16686:16686"
@ -93,7 +102,7 @@ services:
- "14250"
# Collector
otel-collector:
image: otel/opentelemetry-collector:latest
image: otel/opentelemetry-collector:0.129.1
restart: always
command: [ "--config=/etc/otel-collector-config.yaml" ]
volumes:
@ -106,10 +115,10 @@ services:
- "4317:4317" # OTLP gRPC receiver
- "55679:55679" # zpages extension
depends_on:
- jaeger-all-in-one
- jaeger
prometheus:
container_name: prometheus
image: prom/prometheus:latest
image: prom/prometheus:v2.53.5
restart: always
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
@ -128,10 +137,8 @@ receivers:
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
const_labels:
label1: value1
otlp/jaeger:
endpoint: jaeger-all-in-one:4317
endpoint: jaeger:4317
tls:
insecure: true
processors:
@ -148,7 +155,7 @@ service:
exporters: [ prometheus ]
```
#### prometheus.yml
#### prometheus.yaml
```yaml
scrape_configs:
@ -156,10 +163,9 @@ scrape_configs:
scrape_interval: 10s
static_configs:
- 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/).
## Metadata

View File

@ -68,6 +68,7 @@ func (m Middleware) Measure(ctx context.Context, handlerID string, reporter Repo
hid,
reporter.Method(),
code,
reporter.Scheme(),
)
m.cfg.MetricRecorder.InFlightRequestStart(ctx, httpAttrs)
@ -112,6 +113,7 @@ type Reporter interface {
URLPath() string
StatusCode() int
BytesWritten() int64
Scheme() string
}
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) Scheme() string {
if s.r.TLS != nil {
return "https"
}
return "http"
}
// responseWriterInterceptor is a simple wrapper to intercept set data on a
// ResponseWriter.
type responseWriterInterceptor struct {

View File

@ -190,6 +190,10 @@ func (m *MockReporter) BytesWritten() int64 {
return m.Bytes
}
func (m *MockReporter) Scheme() string {
return "http"
}
func (m *MockReporter) URLCalled() bool { return m.urlCalled }
func (m *MockReporter) MethodCalled() bool { return m.methodCalled }
func (m *MockReporter) StatusCalled() bool { return m.statusCalled }