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/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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Reference in New Issue