mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-12-21 10:19:04 +03:00
[hw-7] add metrics, tracing
This commit is contained in:
@@ -15,6 +15,8 @@ func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
||||
return nil, errors.Wrap(err, "pgxpool.ParseConfig")
|
||||
}
|
||||
|
||||
config.ConnConfig.Tracer = promTracer{}
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgxpool.NewWithConfig")
|
||||
@@ -34,6 +36,8 @@ func NewPools(ctx context.Context, DSNs ...string) ([]*pgxpool.Pool, error) {
|
||||
return nil, errors.Wrap(err, "pgxpool.ParseConfig")
|
||||
}
|
||||
|
||||
cfg.ConnConfig.Tracer = promTracer{}
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
closeOpened(pools[:i])
|
||||
38
loms/internal/infra/db/postgres/tracer.go
Normal file
38
loms/internal/infra/db/postgres/tracer.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"route256/loms/internal/infra/grpc/metrics"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type startKey struct{}
|
||||
|
||||
type promTracer struct{}
|
||||
|
||||
func (promTracer) TraceQueryStart(ctx context.Context, _ *pgx.Conn, _ pgx.TraceQueryStartData) context.Context {
|
||||
return context.WithValue(ctx, startKey{}, time.Now())
|
||||
}
|
||||
|
||||
func (promTracer) TraceQueryEnd(ctx context.Context, _ *pgx.Conn, data pgx.TraceQueryEndData) {
|
||||
start, _ := ctx.Value(startKey{}).(time.Time)
|
||||
|
||||
sql := strings.TrimSpace(strings.ToLower(data.CommandTag.String()))
|
||||
parts := strings.SplitN(sql, " ", 2)
|
||||
category := "unknown"
|
||||
if len(parts) > 0 {
|
||||
category = parts[0]
|
||||
}
|
||||
|
||||
errLabel := "none"
|
||||
if data.Err != nil {
|
||||
errLabel = "error"
|
||||
}
|
||||
|
||||
metrics.IncDBQueryCount(category, errLabel)
|
||||
metrics.StoreDBQueryDuration(category, errLabel, time.Since(start))
|
||||
}
|
||||
31
loms/internal/infra/grpc/metrics/db.go
Normal file
31
loms/internal/infra/grpc/metrics/db.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
dbQueryCounter = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: "app",
|
||||
Name: "db_queries_total",
|
||||
Help: "Total DB queries",
|
||||
}, []string{"category", "error"})
|
||||
|
||||
dbQueryDurationHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: "app",
|
||||
Name: "db_query_duration_seconds",
|
||||
Help: "Latency of DB queries, seconds",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"category", "error"})
|
||||
)
|
||||
|
||||
func IncDBQueryCount(cat, errLabel string) {
|
||||
dbQueryCounter.WithLabelValues(cat, errLabel).Inc()
|
||||
}
|
||||
|
||||
func StoreDBQueryDuration(cat, errLabel string, d time.Duration) {
|
||||
dbQueryDurationHistogram.WithLabelValues(cat, errLabel).Observe(d.Seconds())
|
||||
}
|
||||
31
loms/internal/infra/grpc/metrics/metrics.go
Normal file
31
loms/internal/infra/grpc/metrics/metrics.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
requestCounter = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: "app",
|
||||
Name: "requests_total",
|
||||
Help: "Total amount of request by handler",
|
||||
}, []string{"method", "path", "status_code"})
|
||||
|
||||
requestDurationHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: "app",
|
||||
Name: "request_duration_seconds",
|
||||
Help: "Latency of handler processing, seconds",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"method", "path", "status_code"})
|
||||
)
|
||||
|
||||
func IncRequestHandlerCount(method, path, statusCode string) {
|
||||
requestCounter.WithLabelValues(method, path, statusCode).Inc()
|
||||
}
|
||||
|
||||
func StoreHandlerRequestDuration(method, path, statusCode string, d time.Duration) {
|
||||
requestDurationHistogram.WithLabelValues(method, path, statusCode).Observe(d.Seconds())
|
||||
}
|
||||
43
loms/internal/infra/grpc/middleware/metrics.go
Normal file
43
loms/internal/infra/grpc/middleware/metrics.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package mw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"route256/loms/internal/infra/grpc/metrics"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const UNKNOWN = "unknown"
|
||||
|
||||
func WithMetrics(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||
start := time.Now()
|
||||
|
||||
resp, err = handler(ctx, req)
|
||||
|
||||
// "/pkg.Service/Method"
|
||||
service, method := parseFullMethod(info.FullMethod)
|
||||
|
||||
code := status.Code(err).String()
|
||||
|
||||
metrics.IncRequestHandlerCount(service, method, code)
|
||||
metrics.StoreHandlerRequestDuration(service, method, code, time.Since(start))
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func parseFullMethod(full string) (service, method string) {
|
||||
if full == "" {
|
||||
return UNKNOWN, UNKNOWN
|
||||
}
|
||||
full = strings.TrimPrefix(full, "/")
|
||||
|
||||
slash := strings.Index(full, "/")
|
||||
if slash < 0 {
|
||||
return full, UNKNOWN
|
||||
}
|
||||
return full[:slash], full[slash+1:]
|
||||
}
|
||||
38
loms/internal/infra/grpc/middleware/tracing.go
Normal file
38
loms/internal/infra/grpc/middleware/tracing.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package mw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"route256/loms/internal/infra/tracing"
|
||||
)
|
||||
|
||||
func WithTracing(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
service, method := splitFullMethod(info.FullMethod)
|
||||
ctx, span := tracing.Tracer().Start(ctx, service+"/"+method)
|
||||
defer span.End()
|
||||
|
||||
resp, err := handler(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, status.Convert(err).Message())
|
||||
} else {
|
||||
span.SetStatus(codes.Ok, "OK")
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func splitFullMethod(full string) (service, method string) {
|
||||
full = strings.TrimPrefix(full, "/")
|
||||
if i := strings.Index(full, "/"); i >= 0 {
|
||||
return full[:i], full[i+1:]
|
||||
}
|
||||
|
||||
return "unknown", full
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"route256/loms/internal/domain/entity"
|
||||
"route256/loms/internal/infra/tracing"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -64,6 +65,9 @@ func (p *statusProducer) runCallbacks() {
|
||||
}
|
||||
|
||||
func (p *statusProducer) Send(ctx context.Context, id entity.ID, status string) error {
|
||||
ctx, span := tracing.Tracer().Start(ctx, "Producer.Send")
|
||||
defer span.End()
|
||||
|
||||
log.Debug().Msgf("sending event for id: %d; status: %s", id, status)
|
||||
|
||||
newStatus, err := mapOrderStatus(status)
|
||||
@@ -86,6 +90,9 @@ func (p *statusProducer) Send(ctx context.Context, id entity.ID, status string)
|
||||
}
|
||||
|
||||
func (p *statusProducer) SendRaw(ctx context.Context, id entity.ID, value []byte) error {
|
||||
ctx, span := tracing.Tracer().Start(ctx, "Producer.SendRaw")
|
||||
defer span.End()
|
||||
|
||||
if len(value) == 0 {
|
||||
return fmt.Errorf("empty message value")
|
||||
}
|
||||
|
||||
48
loms/internal/infra/tracing/init.go
Normal file
48
loms/internal/infra/tracing/init.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
globalTracer oteltrace.Tracer
|
||||
provider *trace.TracerProvider
|
||||
)
|
||||
|
||||
func NewTracer(serviceName string, option ...trace.TracerProviderOption) oteltrace.Tracer {
|
||||
option = append([]trace.TracerProviderOption{
|
||||
trace.WithResource(resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceName(serviceName),
|
||||
)),
|
||||
}, option...)
|
||||
|
||||
provider = trace.NewTracerProvider(option...)
|
||||
|
||||
otel.SetTracerProvider(provider)
|
||||
|
||||
globalTracer = otel.GetTracerProvider().Tracer("loms")
|
||||
|
||||
return globalTracer
|
||||
}
|
||||
|
||||
func Shutdown(ctx context.Context) error {
|
||||
if provider != nil {
|
||||
return provider.Shutdown(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Tracer() oteltrace.Tracer {
|
||||
if globalTracer == nil {
|
||||
return otel.Tracer("loms")
|
||||
}
|
||||
|
||||
return globalTracer
|
||||
}
|
||||
22
loms/internal/infra/tracing/otel.go
Normal file
22
loms/internal/infra/tracing/otel.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
func InitOTLP(ctx context.Context, serviceName, endpoint string) (shutdown func(context.Context) error, err error) {
|
||||
exp, err := otlptracehttp.New(ctx,
|
||||
otlptracehttp.WithEndpoint(endpoint),
|
||||
otlptracehttp.WithInsecure(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
NewTracer(serviceName, sdktrace.WithBatcher(exp))
|
||||
|
||||
return Shutdown, nil
|
||||
}
|
||||
Reference in New Issue
Block a user