router/core/operation_metrics.go (76 lines of code) (raw):
package core
import (
"context"
"time"
rotel "github.com/wundergraph/cosmo/router/pkg/otel"
otelmetric "go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/attribute"
)
type OperationProtocol string
const (
OperationProtocolHTTP = OperationProtocol("http")
OperationProtocolWS = OperationProtocol("ws")
)
func (p OperationProtocol) String() string {
return string(p)
}
// OperationMetrics is a struct that holds the metrics for an operation. It should be created on the parent router request
// subgraph metrics are created in the transport or engine loader hooks.
type OperationMetrics struct {
requestContentLength int64
routerMetrics RouterMetrics
operationStartTime time.Time
inflightMetric func()
routerConfigVersion string
logger *zap.Logger
trackUsageInfo bool
}
func (m *OperationMetrics) Finish(reqContext *requestContext, statusCode int, responseSize int, exportSynchronous bool) {
ctx := context.Background()
m.inflightMetric()
sliceAttrs := reqContext.telemetry.metricSliceAttrs
attrs := *reqContext.telemetry.AcquireAttributes()
defer reqContext.telemetry.ReleaseAttributes(&attrs)
attrs = append(attrs, semconv.HTTPStatusCode(statusCode))
attrs = append(attrs, reqContext.telemetry.metricAttrs...)
rm := m.routerMetrics.MetricStore()
latency := time.Since(m.operationStartTime)
o := otelmetric.WithAttributeSet(attribute.NewSet(attrs...))
if reqContext.error != nil {
rm.MeasureRequestError(ctx, sliceAttrs, o)
attrs = append(attrs, rotel.WgRequestError.Bool(true))
attrOpt := otelmetric.WithAttributeSet(attribute.NewSet(attrs...))
rm.MeasureRequestCount(ctx, sliceAttrs, attrOpt)
rm.MeasureLatency(ctx, latency, sliceAttrs, attrOpt)
} else {
rm.MeasureRequestCount(ctx, sliceAttrs, o)
rm.MeasureLatency(ctx, latency, sliceAttrs, o)
}
rm.MeasureRequestSize(ctx, m.requestContentLength, sliceAttrs, o)
rm.MeasureResponseSize(ctx, int64(responseSize), sliceAttrs, o)
if m.trackUsageInfo && reqContext.operation != nil && !reqContext.operation.executionOptions.SkipLoader {
m.routerMetrics.ExportSchemaUsageInfo(reqContext.operation, statusCode, reqContext.error != nil, exportSynchronous)
}
}
type OperationMetricsOptions struct {
InFlightAddOption otelmetric.AddOption
SliceAttributes []attribute.KeyValue
RouterConfigVersion string
RequestContentLength int64
RouterMetrics RouterMetrics
Logger *zap.Logger
TrackUsageInfo bool
}
// newOperationMetrics creates a new OperationMetrics struct and starts the operation metrics.
// routerMetrics.StartOperation()
func newOperationMetrics(opts OperationMetricsOptions) *OperationMetrics {
operationStartTime := time.Now()
inflightMetric := opts.RouterMetrics.MetricStore().MeasureInFlight(context.Background(), opts.SliceAttributes, opts.InFlightAddOption)
return &OperationMetrics{
requestContentLength: opts.RequestContentLength,
operationStartTime: operationStartTime,
inflightMetric: inflightMetric,
routerConfigVersion: opts.RouterConfigVersion,
routerMetrics: opts.RouterMetrics,
logger: opts.Logger,
trackUsageInfo: opts.TrackUsageInfo,
}
}