mirror of
https://github.com/go-kratos/kratos.git
synced 2024-12-28 21:09:04 +02:00
197 lines
5.4 KiB
Go
197 lines
5.4 KiB
Go
package metrics
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/go-kratos/kratos/v2/errors"
|
|
"github.com/go-kratos/kratos/v2/middleware"
|
|
"github.com/go-kratos/kratos/v2/transport"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/metric"
|
|
metricsdk "go.opentelemetry.io/otel/sdk/metric"
|
|
)
|
|
|
|
const (
|
|
metricLabelKind = "kind"
|
|
metricLabelOperation = "operation"
|
|
metricLabelCode = "code"
|
|
metricLabelReason = "reason"
|
|
)
|
|
|
|
const (
|
|
DefaultServerSecondsHistogramName = "server_requests_seconds_bucket"
|
|
DefaultServerRequestsCounterName = "server_requests_code_total"
|
|
DefaultClientSecondsHistogramName = "client_requests_seconds_bucket"
|
|
DefaultClientRequestsCounterName = "client_requests_code_total"
|
|
)
|
|
|
|
// Option is metrics option.
|
|
type Option func(*options)
|
|
|
|
// WithRequests with requests counter.
|
|
func WithRequests(c metric.Int64Counter) Option {
|
|
return func(o *options) {
|
|
o.requests = c
|
|
}
|
|
}
|
|
|
|
// WithSeconds with seconds histogram.
|
|
// notice: the record unit in current middleware is s(Seconds)
|
|
func WithSeconds(histogram metric.Float64Histogram) Option {
|
|
return func(o *options) {
|
|
o.seconds = histogram
|
|
}
|
|
}
|
|
|
|
// DefaultRequestsCounter
|
|
// return metric.Int64Counter for WithRequests
|
|
// suggest histogramName = <client/server>_requests_code_total
|
|
func DefaultRequestsCounter(meter metric.Meter, histogramName string) (metric.Int64Counter, error) {
|
|
return meter.Int64Counter(histogramName, metric.WithUnit("{call}"))
|
|
}
|
|
|
|
// DefaultSecondsHistogram
|
|
// return metric.Float64Histogram for WithSeconds
|
|
// suggest histogramName = <client/server>_requests_seconds_bucket
|
|
func DefaultSecondsHistogram(meter metric.Meter, histogramName string) (metric.Float64Histogram, error) {
|
|
return meter.Float64Histogram(histogramName, metric.WithUnit("s"))
|
|
}
|
|
|
|
// DefaultSecondsHistogramView
|
|
// need register in sdkmetric.MeterProvider
|
|
// eg:
|
|
// view := SecondsHistogramView()
|
|
// mp := sdkmetric.NewMeterProvider(sdkmetric.WithView(view))
|
|
// otel.SetMeterProvider(mp)
|
|
func DefaultSecondsHistogramView(histogramName string) metricsdk.View {
|
|
return func(instrument metricsdk.Instrument) (metricsdk.Stream, bool) {
|
|
if instrument.Name == histogramName {
|
|
return metricsdk.Stream{
|
|
Name: instrument.Name,
|
|
Description: instrument.Description,
|
|
Unit: instrument.Unit,
|
|
Aggregation: metricsdk.AggregationExplicitBucketHistogram{
|
|
Boundaries: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.250, 0.5, 1},
|
|
NoMinMax: true,
|
|
},
|
|
AttributeFilter: func(value attribute.KeyValue) bool {
|
|
return true
|
|
},
|
|
}, true
|
|
}
|
|
return metricsdk.Stream{}, false
|
|
}
|
|
}
|
|
|
|
type options struct {
|
|
// counter: <client/server>_requests_code_total{kind, operation, code, reason}
|
|
requests metric.Int64Counter
|
|
// histogram: <client/server>_requests_seconds_bucket{kind, operation}
|
|
seconds metric.Float64Histogram
|
|
}
|
|
|
|
// Server is middleware server-side metrics.
|
|
func Server(opts ...Option) middleware.Middleware {
|
|
op := options{}
|
|
for _, o := range opts {
|
|
o(&op)
|
|
}
|
|
return func(handler middleware.Handler) middleware.Handler {
|
|
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
// if requests and seconds are nil, return directly
|
|
if op.requests == nil && op.seconds == nil {
|
|
return handler(ctx, req)
|
|
}
|
|
|
|
var (
|
|
code int
|
|
reason string
|
|
kind string
|
|
operation string
|
|
)
|
|
startTime := time.Now()
|
|
if info, ok := transport.FromServerContext(ctx); ok {
|
|
kind = info.Kind().String()
|
|
operation = info.Operation()
|
|
}
|
|
reply, err := handler(ctx, req)
|
|
if se := errors.FromError(err); se != nil {
|
|
code = int(se.Code)
|
|
reason = se.Reason
|
|
}
|
|
if op.requests != nil {
|
|
op.requests.Add(
|
|
ctx, 1,
|
|
metric.WithAttributes(
|
|
attribute.String(metricLabelKind, kind),
|
|
attribute.String(metricLabelOperation, operation),
|
|
attribute.Int(metricLabelCode, code),
|
|
attribute.String(metricLabelReason, reason),
|
|
),
|
|
)
|
|
}
|
|
if op.seconds != nil {
|
|
op.seconds.Record(
|
|
ctx, time.Since(startTime).Seconds(),
|
|
metric.WithAttributes(
|
|
attribute.String(metricLabelKind, kind),
|
|
attribute.String(metricLabelOperation, operation),
|
|
),
|
|
)
|
|
}
|
|
return reply, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Client is middleware client-side metrics.
|
|
func Client(opts ...Option) middleware.Middleware {
|
|
op := options{}
|
|
for _, o := range opts {
|
|
o(&op)
|
|
}
|
|
return func(handler middleware.Handler) middleware.Handler {
|
|
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
var (
|
|
code int
|
|
reason string
|
|
kind string
|
|
operation string
|
|
)
|
|
startTime := time.Now()
|
|
if info, ok := transport.FromClientContext(ctx); ok {
|
|
kind = info.Kind().String()
|
|
operation = info.Operation()
|
|
}
|
|
reply, err := handler(ctx, req)
|
|
if se := errors.FromError(err); se != nil {
|
|
code = int(se.Code)
|
|
reason = se.Reason
|
|
}
|
|
if op.requests != nil {
|
|
op.requests.Add(
|
|
ctx, 1,
|
|
metric.WithAttributes(
|
|
attribute.String(metricLabelKind, kind),
|
|
attribute.String(metricLabelOperation, operation),
|
|
attribute.Int(metricLabelCode, code),
|
|
attribute.String(metricLabelReason, reason),
|
|
),
|
|
)
|
|
}
|
|
if op.seconds != nil {
|
|
op.seconds.Record(
|
|
ctx, time.Since(startTime).Seconds(),
|
|
metric.WithAttributes(
|
|
attribute.String(metricLabelKind, kind),
|
|
attribute.String(metricLabelOperation, operation),
|
|
),
|
|
)
|
|
}
|
|
return reply, err
|
|
}
|
|
}
|
|
}
|