diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 1e1460e1..4eb7ea19 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -45,10 +45,10 @@ jobs: echo "run setup script" ./scripts/setup echo "run server" - ./bin/forecaster -monitoring-enabled=false & - ./bin/locator -monitoring-enabled=false & - ./bin/tester -monitoring-enabled=false & - ./bin/front -monitoring-enabled=false & + ./bin/forecaster & + ./bin/locator & + ./bin/tester & + ./bin/front & check_service_health "http://localhost:8081/healthz" & check_service_health "http://localhost:8083/healthz" & check_service_health "http://localhost:8091/healthz" & diff --git a/.gitignore b/.gitignore index ddb646d8..738e2d8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .vscode/ -example/weather/services/forecaster/cmd/forecaster/forecaster -example/weather/services/locator/cmd/locator/locator -example/weather/services/front/cmd/front/front example/weather/.overmind.sock example/weather/bin/* +example/weather/services/forecaster/cmd/forecaster/forecaster +example/weather/services/front/cmd/front/front +example/weather/services/locator/cmd/locator/locator +example/weather/signoz coverage.out -go.work diff --git a/LICENSE b/LICENSE index d0e57815..8fbd1ce6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2022 Raphael Simon and Clue Contributors +Copyright (c) 2024 Raphael Simon and Clue Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f1a2d546..e7722a1b 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,403 @@ +
+ # clue: Microservice Instrumentation -[![Build Status](https://github.com/goadesign/clue/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/goadesign/clue/actions?query=branch%3Amain+event%3Apush) -[![codecov](https://codecov.io/gh/goadesign/clue/branch/main/graph/badge.svg?token=HVP4WT1PS6)](https://codecov.io/gh/goadesign/clue) -[![Go Report Card](https://goreportcard.com/badge/goa.design/clue)](https://goreportcard.com/report/goa.design/clue) -[![Go Reference](https://pkg.go.dev/badge/goa.design/clue.svg)](https://pkg.go.dev/goa.design/clue) +[![Go Reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=for-the-badge)](https://pkg.go.dev/goa.design/clue) +[![License](https://img.shields.io/badge/License-MIT%202.0-blue?style=for-the-badge)](LICENSE) + +[![Build Status](https://img.shields.io/github/actions/workflow/status/goadesign/clue/ci.yaml?style=for-the-badge)](https://github.com/goadesign/clue/actions?query=branch%3Amain+event%3Apush) +[![codecov](https://img.shields.io/codecov/c/github/goadesign/clue/main?style=for-the-badge&token=HVP4WT1PS6)](https://codecov.io/gh/goadesign/clue) +[![Go Report Card](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=for-the-badge)](https://goreportcard.com/report/goa.design/clue) + +
## Overview -`clue` provides a set of Go packages for instrumenting microservices. The -emphasis is on simplicity and ease of use. Although not a requirement, `clue` +Clue provides a set of Go packages for instrumenting microservices. The +emphasis is on simplicity and ease of use. Although not a requirement, Clue works best when used in microservices written using [Goa](https://github.com/goadesign/goa). -`clue` covers the following topics: +Clue covers the following topics: +* Instrumentation: the [clue](clue/) package configures OpenTelemetry + for service instrumentation. * Logging: the [log](log/) package provides a context-based logging API that intelligently selects what to log. -* Metrics: the [metrics](metrics/) package makes it possible for - services to expose a Prometheus compatible `/metrics` HTTP endpoint. * Health checks: the [health](health/) package provides a simple way for services to expose a health check endpoint. * Dependency mocks: the [mock](mock/) package provides a way to mock downstream dependencies for testing. -* Tracing: the [trace](trace/) package conforms to the - [OpenTelemetry](https://opentelemetry.io/) specification to trace requests. * Debugging: the [debug](debug/) package makes it possible to troubleshoot and profile services at runtime. -The [weather](example/weather) example illustrates how to use `clue` to -instrument a system of Goa microservices. The example comes with a set of -scripts that can be used to compile and start the system as well as a complete -Grafana stack to query metrics and traces. See the +Clue's goal is to provide all the anciallary functionality required to efficiently +operate a microservice style architecture including instrumentation, logging, +debugging and health checks. Clue is not a framework and does not provide +functionality that is already available in the standard library or in other +packages. For example, Clue does not provide a HTTP router or a HTTP server +implementation. Instead, Clue provides a way to instrument existing HTTP or gRPC +servers and clients using the standard library and the OpenTelemetry API. + +## Packages + +* The `clue` package provides a simple API for configuring OpenTelemetry + instrumentation. The package also provides a way to configure the log + package to automatically annotate log messages with trace and span IDs. + The package also implements a dynamic trace sampler that can be used to + sample traces based on a target maximum number of traces per second. + +* The `log` package offers a streamlined, context-based structured logger that + efficiently buffers log messages. It smartly determines the optimal time to + flush these messages to the underlying logger. In its default configuration, + the log package flushes logs upon the logging of an error or when a request is + traced. This design choice minimizes logging overhead for untraced requests, + ensuring efficient logging operations. + +* The `health` package provides a simple way to expose a health check endpoint + that can be used by orchestration systems such as Kubernetes to determine + whether a service is ready to receive traffic. The package implements the + concept of checkers that can be used to implement custom health checks with + a default implementation that relies on the ability to ping downstream + dependencies. + +* The `mock` package provides a way to mock downstream dependencies for testing. + The package provides a tool that generates mock implementations of interfaces + and a way to configure the generated mocks to validate incoming payloads and + return canned responses. + +* The `debug` package provides a way to dynamically control the log level of a + running service. The package also provides a way to expose the pprof Go + profiling endpoints and a way to expose the log level control endpoint. + +## Installation + +Clue requires Go 1.21 or later. Install the packages required for your +application using `go get`, for example: + +```bash +go get goa.design/clue/clue +go get goa.design/clue/log +go get goa.design/clue/health +go get goa.design/clue/mock +go get goa.design/clue/debug +``` + +## Usage + +The following snippet illustrates how to use `clue` to instrument a HTTP server: + +```go +ctx := log.Context(context.Background(), // Create a clue logger context. + log.WithFunc(log.Span)) // Log trace and span IDs. + +metricExporter := stdoutmetric.New() // Create metric and span exporters. +spanExporter := stdouttrace.New() +cfg := clue.NewConfig(ctx, "service", "1.0.0", metricExporter, spanExporter) +clue.ConfigureOpenTelemetry(ctx, cfg) // Configure OpenTelemetry. + +handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, World!")) +}) // Create HTTP handler. +handler = otelhttp.NewHandler(handler, "service" ) // Instrument handler. +handler = debug.HTTP()(handler) // Setup debug log level control. +handler = log.HTTP(ctx)(handler) // Add logger to request context and log requests. + +mux := http.NewServeMux() // Create HTTP mux. +mux.HandleFunc("/", handler) // Mount handler. +debug.MountDebugLogEnabler(mux) // Mount debug log level control handler. +debug.MountDebugPprof(mux) // Mount pprof handlers. +mux.HandleFunc("/health", + health.NewHandler(health.NewChecker())) // Mount health check handler. + +http.ListenAndServe(":8080", mux) // Start HTTP server. +``` + +Similarly, the following snippet illustrates how to instrument a gRPC server: + +```go +ctx := log.Context(context.Background(), // Create a clue logger context. + log.WithFunc(log.Span)) // Log trace and span IDs. + +metricExporter := stdoutmetric.New() +spanExporter := stdouttrace.New() +cfg := clue.NewConfig(ctx, "service", "1.0.0", metricExporter, spanExporter) +clue.ConfigureOpenTelemetry(ctx, cfg) // Configure OpenTelemetry. + +svr := grpc.NewServer( + grpc.ChainUnaryInterceptor( + log.UnaryServerInterceptor(ctx), // Add logger to request context and log requests. + debug.UnaryServerInterceptor()), // Enable debug log level control + grpc.StatsHandler(otelgrpc.NewServerHandler()), // Instrument server. +) +``` + +Note that in the case of gRPC, a separate HTTP server is required to expose the +debug log level control, pprof and health check endpoints: + +```go +mux := http.NewServeMux() // Create HTTP mux. +debug.MountDebugLogEnabler(mux) // Mount debug log level control handler. +debug.MountDebugPprof(mux) // Mount pprof handlers. +mux.HandleFunc("/health", + health.NewHandler(health.NewChecker())) // Mount health check handler. + +go http.ListenAndServe(":8081", mux) // Start HTTP server. +``` + +## Exporters + +Exporter are responsible for exporting telemetry data to a backend. The +[OpenTelemetry Exporters documentation](https://opentelemetry.io/docs/instrumentation/go/exporters/) +provides a list of available exporters. + +For example, configuring an [OTLP](https://opentelemetry.io/docs/specs/otlp/) +compliant span exporter can be done as follows: + +```go +import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" +// ... +spanExporter, err := otlptracegrpc.New( + context.Background(), + otlptracegrpc.WithEndpoint("localhost:4317"), + otlptracegrpc.WithTLSCredentials(insecure.NewCredentials())) +``` + +While configuring an OTLP compliant metric exporters can be done as follows: + +```go +import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" +// ... +metricExporter, err := otlpmetricgrpc.New( + context.Background(), + otlpmetricgrpc.WithEndpoint("localhost:4317"), + otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials())) +``` + +These exporters can then be used to configure Clue: + +```go +// Configure OpenTelemetry. +cfg := clue.NewConfig(ctx, "service", "1.0.0", metricExporter, spanExporter) +clue.ConfigureOpenTelemetry(ctx, cfg) +``` + +## Clients + +HTTP clients can be instrumented using the Clue `log` and OpenTelemetry `otelhttptrace` packages. The `log.Client` function wraps a HTTP transport and logs the request and response. The `otelhttptrace.Client` function wraps a HTTP transport and adds OpenTelemetry tracing to the request. + +```go +import ( + "context" + "net/http" + "net/http/httptrace" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttptrace" + "goa.design/clue/log" +) + +// ... +httpc := &http.Client{ + Transport: log.Client( + otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { + return otelhttptrace.NewClientTrace(ctx) + }), + ), + ), +} +``` + +Similarly, gRPC clients can be instrumented using the Clue `log` and OpenTelemtry `otelgrpc` packages. + +```go +import ( + "context" + + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "goa.design/clue/log" +) + +// ... +grpcconn, err := grpc.DialContext(ctx, + "localhost:8080", + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) +) +``` + +## Custom Instrumentation + +Clue relies on the OpenTelemetry API for creating custom instrumentation. The +following snippet illustrates how to create a custom counter and span: + +```go +// ... configure OpenTelemetry like in example above +clue.ConfigureOpenTelemetry(ctx, cfg) + +// Create a meter and tracer +meter := otel.Meter("mymeter") +tracer := otel.Tracer("mytracer") + +// Create a counter +counter, err := meter.Int64Counter("my.counter", + metric.WithDescription("The number of times the service has been called"), + metric.WithUnit("{call}")) +if err != nil { + log.Fatalf("failed to create counter: %s", err) +} + +handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Create a span + ctx, span := tracer.Start(r.Context(), "myhandler") + defer span.End() + + // ... do something + + // Add custom attributes to span and counter + attr := attribute.Int("myattr", 42) + span.SetAttributes(attr) + counter.Add(ctx, 1, metric.WithAttributes(attr)) + + // ... do something else + if _, err := w.Write([]byte("Hello, World!")); err != nil { + log.Errorf(ctx, err, "failed to write response") + } + }) +``` + +## Goa + +The `log` package provides a Goa endpoint middleware that adds the service and +method names to the logger context. The `debug` package provides a Goa endpoint +middleware that logs the request and response payloads when debug logging is +enabled. The example below is a snippet extracted from the `main` function of +the +[genforecaster](example/weather/services/forecaster/cmd/forecaster/main.go#L88) +service: + +```go +svc := forecaster.New(wc) +endpoints := genforecaster.NewEndpoints(svc) +endpoints.Use(debug.LogPayloads()) +endpoints.Use(log.Endpoint) +``` + +## Example + +The [weather](example/weather) example illustrates how to use Clue to instrument +a system of Goa microservices. The example comes with a set of scripts that can +be used to install all necessary dependencies including the +[SigNoz](https://signoz.io/) instrumentation backend. See the [README](example/weather/README.md) for more information. +## Migrating from v0.x to v1.x + +The v1.0.0 release of `clue` is a major release that introduces breaking +changes. The following sections describe the changes and how to migrate. + +### Initialization + +The `clue` package provides a cohesive API for both metrics and tracing, +effectively replacing the previous `metrics` and `trace` packages. The +traditional `Context` function, utilized in the `metrics` and `trace` packages +for setting up telemetry, has been deprecated. In its place, the `clue` package +introduces the `NewConfig` function, which generates a `Config` object used in +conjunction with the `ConfigureOpenTelemetry` function to facilitate telemetry +setup. + +v0.x: + +```go +ctx := log.Context(context.Background()) +traceExporter := tracestdout.New() +metricsExporter := metricstdout.New() +ctx = trace.Context(ctx, "service", traceExporter) +ctx = metrics.Context(ctx, "service", metricsExporter) +``` + +v1.x: + +```go +ctx := log.Context(context.Background()) +traceExporter := tracestdout.New() +metricsExporter := metricstdout.New() +cfg := clue.NewConfig(ctx, "service", "1.0.0", metricsExporter, traceExporter) +clue.ConfigureOpenTelemetry(ctx, cfg) +``` + +### Instrumentation + +Instrumenting a HTTP service is now done using the standard `otelhttp` package: + +v0.x: + +```go +handler = trace.HTTP(ctx)(handler) +handler = metrics.HTTP(ctx)(handler) +http.ListenAndServe(":8080", handler) +``` + +v1.x: + +```go +http.ListenAndServe(":8080", otelhttp.NewHandler("service", handler)) +``` + +Similarly, instrumenting a gRPC service is now done using the standard +`otelgrpc` package. v1.x also switches to using a gRPC stats handler instead of +interceptors: + +v0.x: + +```go +grpcsvr := grpc.NewServer( + grpcmiddleware.WithUnaryServerChain( + trace.UnaryServerInterceptor(ctx), + metrics.UnaryServerInterceptor(ctx), + ), + grpcmiddleware.WithStreamServerChain( + trace.StreamServerInterceptor(ctx), + metrics.StreamServerInterceptor(ctx), + ), +) +``` + +v1.x: + +```go +grpcsvr := grpc.NewServer(grpc.StatsHandler(otelgrpc.NewServerHandler())) +``` + +### Goa + +The `otel` Goa plugin leverages the OpenTelemetry API to annotate spans and +metrics with HTTP routes. + +v0.x: + +```go +mux := goahttp.NewMuxer() +ctx = metrics.Context(ctx, genfront.ServiceName, + metrics.WithRouteResolver(func(r *http.Request) string { + return mux.ResolvePattern(r) + }), +) +``` + +v1.x: + +```go +package design + +import ( + . "goa.design/goa/v3/dsl" + _ "goa.design/plugins/clue" +) +``` + ## Contributing See [Contributing](CONTRIBUTING.md) diff --git a/clue/config.go b/clue/config.go new file mode 100644 index 00000000..9065c9c9 --- /dev/null +++ b/clue/config.go @@ -0,0 +1,137 @@ +package clue + +import ( + "context" + + "github.com/go-logr/logr" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + metricnoop "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/propagation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" + tracenoop "go.opentelemetry.io/otel/trace/noop" + + "goa.design/clue/log" +) + +type ( + // Config is used to initialize metrics and tracing. + Config struct { + // MeterProvider is the OpenTelemetry meter provider used by the clue + // metrics package. + MeterProvider metric.MeterProvider + // TracerProvider is the OpenTelemetry tracer provider used by the clue + // trace package. + TracerProvider trace.TracerProvider + // Propagators is the OpenTelemetry propagator used by the clue trace + // package. + Propagators propagation.TextMapPropagator + // ErrorHandler is the error handler used by the OpenTelemetry + // package. + ErrorHandler otel.ErrorHandler + } +) + +// ConfigureOpenTelemetry sets up code instrumentation using the OpenTelemetry +// API. It leverages the clue logger configured in ctx to log errors. +func ConfigureOpenTelemetry(ctx context.Context, cfg *Config) { + otel.SetMeterProvider(cfg.MeterProvider) + otel.SetTracerProvider(cfg.TracerProvider) + otel.SetTextMapPropagator(cfg.Propagators) + otel.SetLogger(logr.New(log.ToLogrSink(ctx))) + otel.SetErrorHandler(cfg.ErrorHandler) +} + +// NewConfig creates a new Config object adequate for use by +// ConfigureOpenTelemetry. The metricsExporter and spanExporter are used to +// record telemetry. If either is nil then the corresponding package will not +// record any telemetry. The OpenTelemetry metrics provider is configured with a +// periodic reader. The OpenTelemetry tracer provider is configured to use a +// batch span processor and an adaptive sampler that aims at a maximum sampling +// rate of requests per second. The resulting configuration can be modified +// (and providers replaced) by the caller prior to calling +// ConfigureOpenTelemetry. +// +// Example: +// +// metricsExporter, err := stdoutmetric.New() +// if err != nil { +// return err +// } +// spanExporter, err := stdouttrace.New() +// if err != nil { +// return err +// } +// cfg := clue.NewConfig("mysvc", "1.0.0", metricsExporter, spanExporter) +func NewConfig( + ctx context.Context, + svcName string, + svcVersion string, + metricsExporter sdkmetric.Exporter, + spanExporter sdktrace.SpanExporter, + opts ...Option, +) (*Config, error) { + options := defaultOptions(ctx) + for _, o := range opts { + o(options) + } + res, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(svcName), + semconv.ServiceVersionKey.String(svcVersion), + )) + if err != nil { + return nil, err + } + var meterProvider metric.MeterProvider + if metricsExporter == nil { + meterProvider = metricnoop.NewMeterProvider() + } else { + var reader sdkmetric.Reader + if options.readerInterval == 0 { + reader = sdkmetric.NewPeriodicReader(metricsExporter) + } else { + reader = sdkmetric.NewPeriodicReader( + metricsExporter, + sdkmetric.WithInterval(options.readerInterval), + ) + } + meterProvider = sdkmetric.NewMeterProvider( + sdkmetric.WithResource(res), + sdkmetric.WithReader(reader), + ) + } + var tracerProvider trace.TracerProvider + if spanExporter == nil { + tracerProvider = tracenoop.NewTracerProvider() + } else { + sampler := sdktrace.ParentBased( + AdaptiveSampler(options.maxSamplingRate, options.sampleSize), + ) + tracerProvider = sdktrace.NewTracerProvider( + sdktrace.WithResource(res), + sdktrace.WithSampler(sampler), + sdktrace.WithBatcher(spanExporter), + ) + } + return &Config{ + MeterProvider: meterProvider, + TracerProvider: tracerProvider, + Propagators: options.propagators, + ErrorHandler: options.errorHandler, + }, nil +} + +// NewErrorHandler returns an error handler that logs errors using the clue +// logger configured in ctx. +func NewErrorHandler(ctx context.Context) otel.ErrorHandler { + return otel.ErrorHandlerFunc(func(err error) { + log.Error(ctx, err) + }) +} diff --git a/clue/config_test.go b/clue/config_test.go new file mode 100644 index 00000000..9f54e567 --- /dev/null +++ b/clue/config_test.go @@ -0,0 +1,140 @@ +package clue + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/metric" + metricnoop "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/propagation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + tracenoop "go.opentelemetry.io/otel/trace/noop" + "goa.design/clue/log" +) + +type ( + // dummyErrorHandler is a dummy implementation of the OpenTelemetry error handler interface. + dummyErrorHandler struct{} +) + +func TestConfigureOpenTelemetry(t *testing.T) { + ctx := log.Context(context.Background()) + noopMeterProvider := metricnoop.NewMeterProvider() + noopTracerProvider := tracenoop.NewTracerProvider() + noopErrorHandler := dummyErrorHandler{} + + cases := []struct { + name string + meterProvider metric.MeterProvider + tracerProvider trace.TracerProvider + propagators propagation.TextMapPropagator + errorHandler otel.ErrorHandler + + wantMeterProvider metric.MeterProvider + wantTracerProvider trace.TracerProvider + wantPropagators propagation.TextMapPropagator + wantErrorHandler bool + }{ + { + name: "default", + }, { + name: "meter provider", + meterProvider: noopMeterProvider, + wantMeterProvider: noopMeterProvider, + }, { + name: "tracer provider", + tracerProvider: noopTracerProvider, + wantTracerProvider: noopTracerProvider, + }, { + name: "propagators", + propagators: propagation.Baggage{}, + wantPropagators: propagation.Baggage{}, + }, { + name: "error handler", + errorHandler: &noopErrorHandler, + wantErrorHandler: true, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + cfg := &Config{ + MeterProvider: c.meterProvider, + TracerProvider: c.tracerProvider, + Propagators: c.propagators, + ErrorHandler: c.errorHandler, + } + ConfigureOpenTelemetry(ctx, cfg) + assert.Equal(t, c.wantMeterProvider, otel.GetMeterProvider()) + assert.Equal(t, c.wantTracerProvider, otel.GetTracerProvider()) + assert.Equal(t, c.wantPropagators, otel.GetTextMapPropagator()) + }) + } +} + +func TestNewConfig(t *testing.T) { + ctx := log.Context(context.Background()) + svcName := "svcName" + svcVersion := "svcVersion" + spanExporter, err := stdouttrace.New() + require.NoError(t, err) + metricsExporter, err := stdoutmetric.New() + require.NoError(t, err) + noopErrorHandler := dummyErrorHandler{} + + cases := []struct { + name string + metricsExporter sdkmetric.Exporter + spanExporter sdktrace.SpanExporter + propagators propagation.TextMapPropagator + errorHandler otel.ErrorHandler + + wantPropagators propagation.TextMapPropagator + wantErrorHandler bool + }{ + { + name: "default", + }, { + name: "metrics exporter", + metricsExporter: metricsExporter, + }, { + name: "tracer provider", + spanExporter: spanExporter, + }, { + name: "propagators", + propagators: propagation.Baggage{}, + wantPropagators: propagation.Baggage{}, + }, { + name: "error handler", + errorHandler: &noopErrorHandler, + wantErrorHandler: true, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + cfg, err := NewConfig(ctx, + svcName, + svcVersion, + c.metricsExporter, + c.spanExporter, + WithPropagators(c.propagators), + WithErrorHandler(c.errorHandler)) + assert.NoError(t, err) + if c.spanExporter != nil { + serialized := fmt.Sprintf("%+v", cfg.TracerProvider) + assert.Contains(t, serialized, "maxSamplingRate:2") + } + assert.Equal(t, c.wantPropagators, cfg.Propagators) + assert.Equal(t, c.wantErrorHandler, cfg.ErrorHandler != nil) + }) + } +} + +func (dummyErrorHandler) Handle(error) {} diff --git a/clue/options.go b/clue/options.go new file mode 100644 index 00000000..a7d25f6c --- /dev/null +++ b/clue/options.go @@ -0,0 +1,79 @@ +package clue + +import ( + "context" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" +) + +type ( + // Option is a function that initializes the clue configuration. + Option func(*options) + + // options contains the clue configuration options. + options struct { + // readerInterval is the interval at which the metrics reader is + // invoked. + readerInterval time.Duration + // maxSamplingRate is the maximum sampling rate for the trace exporter. + maxSamplingRate int + // sampleSize is the number of requests between two adjustments of the + // sampling rate. + sampleSize int + // propagators is the trace propagators. + propagators propagation.TextMapPropagator + // errorHandler is the error handler used by the otel package. + errorHandler otel.ErrorHandler + } +) + +// defaultOptions returns a new options struct with default values. +// The logger in ctx is used to log errors. +func defaultOptions(ctx context.Context) *options { + return &options{ + maxSamplingRate: 2, + sampleSize: 10, + propagators: propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}), + errorHandler: NewErrorHandler(ctx), + } +} + +// WithReaderInterval returns an option that sets the interval at which the +// metrics reader is invoked. +func WithReaderInterval(interval time.Duration) Option { + return func(c *options) { + c.readerInterval = interval + } +} + +// WithMaxSamplingRate sets the maximum sampling rate in requests per second. +func WithMaxSamplingRate(rate int) Option { + return func(opts *options) { + opts.maxSamplingRate = rate + } +} + +// WithSampleSize sets the number of requests between two adjustments of the +// sampling rate. +func WithSampleSize(size int) Option { + return func(opts *options) { + opts.sampleSize = size + } +} + +// WithPropagators sets the propagators when extracting and injecting trace +// context. +func WithPropagators(propagator propagation.TextMapPropagator) Option { + return func(opts *options) { + opts.propagators = propagator + } +} + +// WithErrorHandler sets the error handler used by the telemetry package. +func WithErrorHandler(errorHandler otel.ErrorHandler) Option { + return func(opts *options) { + opts.errorHandler = errorHandler + } +} diff --git a/clue/options_test.go b/clue/options_test.go new file mode 100644 index 00000000..44f9f3b6 --- /dev/null +++ b/clue/options_test.go @@ -0,0 +1,65 @@ +package clue + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/propagation" + "goa.design/clue/log" +) + +func TestOptions(t *testing.T) { + ctx := log.Context(context.Background()) + cases := []struct { + name string + option Option + want func(*options) // mutate default options + }{ + { + name: "default", + }, + { + name: "with reader interval", + option: WithReaderInterval(1000), + want: func(o *options) { o.readerInterval = 1000 }, + }, + { + name: "with max sampling rate", + option: WithMaxSamplingRate(1000), + want: func(o *options) { o.maxSamplingRate = 1000 }, + }, + { + name: "with sample size", + option: WithSampleSize(1000), + want: func(o *options) { o.sampleSize = 1000 }, + }, + { + name: "with propagator", + option: WithPropagators(propagation.TraceContext{}), + want: func(o *options) { o.propagators = propagation.TraceContext{} }, + }, + { + name: "with error handler", + option: WithErrorHandler(dummyErrorHandler{}), + want: func(o *options) { o.errorHandler = dummyErrorHandler{} }, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := defaultOptions(ctx) + if c.option != nil { + c.option(got) + } + want := defaultOptions(ctx) + if c.want != nil { + c.want(want) + } + assert.Equal(t, want.maxSamplingRate, got.maxSamplingRate) + assert.Equal(t, want.sampleSize, got.sampleSize) + assert.Equal(t, want.readerInterval, got.readerInterval) + assert.Equal(t, want.propagators, got.propagators) + assert.IsType(t, want.errorHandler, got.errorHandler) + }) + } +} diff --git a/trace/sampler.go b/clue/sampler.go similarity index 73% rename from trace/sampler.go rename to clue/sampler.go index cc2c059f..9f9418f6 100644 --- a/trace/sampler.go +++ b/clue/sampler.go @@ -1,10 +1,9 @@ -package trace +package clue import ( "fmt" sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/trace" "goa.design/goa/v3/middleware" ) @@ -15,15 +14,15 @@ type sampler struct { sampleSize int } -// adaptiveSampler computes the interval for sampling for tracing middleware. -// it can also be used by non-web go routines to trace internal API calls. +// AdaptiveSampler returns a trace sampler that dynamically computes the +// interval between samples to target a desired maximum sampling rate. // // maxSamplingRate is the desired maximum sampling rate in requests per second. // // sampleSize sets the number of requests between two adjustments of the // sampling rate when MaxSamplingRate is set. the sample rate cannot be adjusted // until the sample size is reached at least once. -func adaptiveSampler(maxSamplingRate, sampleSize int) sdktrace.Sampler { +func AdaptiveSampler(maxSamplingRate, sampleSize int) sdktrace.Sampler { return sampler{ s: middleware.NewAdaptiveSampler(maxSamplingRate, sampleSize), maxSamplingRate: maxSamplingRate, @@ -41,10 +40,5 @@ func (s sampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingRe if !s.s.Sample() { return sdktrace.SamplingResult{Decision: sdktrace.Drop} } - - psc := trace.SpanContextFromContext(p.ParentContext) - return sdktrace.SamplingResult{ - Decision: sdktrace.RecordAndSample, - Tracestate: psc.TraceState(), - } + return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample} } diff --git a/clue/sampler_test.go b/clue/sampler_test.go new file mode 100644 index 00000000..a3f1008c --- /dev/null +++ b/clue/sampler_test.go @@ -0,0 +1,24 @@ +package clue + +import ( + "testing" + + "github.com/stretchr/testify/assert" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +func TestAdaptiveSampler(t *testing.T) { + // We don't need to test Goa, keep it simple... + s := AdaptiveSampler(2, 10) + expected := "Adaptive{maxSamplingRate:2,sampleSize:10}" + assert.Equal(t, expected, s.Description()) + res := s.ShouldSample(sdktrace.SamplingParameters{}) + assert.Equal(t, sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample}, res) + s2 := AdaptiveSampler(1, 2) + expected = "Adaptive{maxSamplingRate:1,sampleSize:2}" + assert.Equal(t, expected, s2.Description()) + res2 := s2.ShouldSample(sdktrace.SamplingParameters{}) + res3 := s2.ShouldSample(sdktrace.SamplingParameters{}) + assert.Equal(t, sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample}, res2) + assert.Equal(t, sdktrace.SamplingResult{Decision: sdktrace.Drop}, res3) +} diff --git a/debug/debug.go b/debug/debug.go index 4915c183..a768c961 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -24,18 +24,19 @@ var ( debugLogs bool ) -// MountDebugLogEnabler mounts an endpoint under "/debug" and returns a HTTP -// middleware that manages the status of debug logs. The endpoint accepts a -// single query parameter "debug-logs". If the parameter is set to "on" then -// debug logs are enabled. If the parameter is set to "off" then debug logs are -// disabled. In all other cases the endpoint returns the current debug logs -// status. The path, query parameter name and values can be changed using the -// WithPath, WithQuery, WithOnValue and WithOffValue options. +// MountDebugLogEnabler mounts an endpoint under "/debug" that manages the +// status of debug logs. The endpoint accepts a single query parameter +// "debug-logs". If the parameter is set to "on" then debug logs are enabled. If +// the parameter is set to "off" then debug logs are disabled. In all other +// cases the endpoint returns the current debug logs status. The path, query +// parameter name and values can be changed using the WithPath, WithQuery, +// WithOnValue and WithOffValue options. // // Note: the endpoint merely controls the status of debug logs. It does not // actually configure the current logger. The logger is configured by the // middleware returned by the HTTP function or by the gRPC interceptors returned -// by the UnaryServerInterceptor and StreamServerInterceptor functions. +// by the UnaryServerInterceptor and StreamServerInterceptor functions which +// should be used in conjunction with the MountDebugLogEnabler function. func MountDebugLogEnabler(mux Muxer, opts ...DebugLogEnablerOption) { o := defaultDebugLogEnablerOptions() for _, opt := range opts { diff --git a/debug/grpc_test.go b/debug/grpc_test.go index b902983b..4cba704e 100644 --- a/debug/grpc_test.go +++ b/debug/grpc_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "testing" + "github.com/stretchr/testify/assert" "google.golang.org/grpc" "goa.design/clue/internal/testsvc" @@ -18,7 +19,7 @@ func TestUnaryServerInterceptor(t *testing.T) { ctx := log.Context(context.Background(), log.WithOutput(&buf), log.WithFormat(logKeyValsOnly)) cli, stop := testsvc.SetupGRPC(t, testsvc.WithServerOptions(grpc.ChainUnaryInterceptor( - log.UnaryServerInterceptor(ctx, log.WithDisableCallLogging()), + log.UnaryServerInterceptor(ctx, log.WithDisableCallLogging(), log.WithDisableCallID()), UnaryServerInterceptor())), testsvc.WithUnaryFunc(logUnaryMethod)) defer stop() @@ -28,10 +29,10 @@ func TestUnaryServerInterceptor(t *testing.T) { defer ts.Close() steps := []struct { - name string - on bool - off bool - expectedLogs string + name string + on bool + off bool + wantLog string }{ {"start", false, false, ""}, {"turn debug logs on", true, false, "debug=message "}, @@ -47,12 +48,8 @@ func TestUnaryServerInterceptor(t *testing.T) { makeRequest(t, ts.URL+"/debug?debug-logs=off") } _, err := cli.GRPCMethod(context.Background(), nil) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if buf.String() != step.expectedLogs { - t.Errorf("expected log %q, got %q", step.expectedLogs, buf.String()) - } + assert.NoError(t, err) + assert.Equal(t, step.wantLog, buf.String()) buf.Reset() } } @@ -62,14 +59,14 @@ func TestStreamServerInterceptor(t *testing.T) { ctx := log.Context(context.Background(), log.WithOutput(&buf), log.WithFormat(logKeyValsOnly)) cli, stop := testsvc.SetupGRPC(t, testsvc.WithServerOptions(grpc.ChainStreamInterceptor( - log.StreamServerInterceptor(ctx, log.WithDisableCallLogging()), + log.StreamServerInterceptor(ctx, log.WithDisableCallLogging(), log.WithDisableCallID()), StreamServerInterceptor())), testsvc.WithStreamFunc(echoMethod)) defer stop() steps := []struct { name string enableDebugLogs bool - expectedLogs string + wantLog string }{ {"no debug logs", false, ""}, {"debug logs", true, "debug=message "}, @@ -78,19 +75,13 @@ func TestStreamServerInterceptor(t *testing.T) { for _, step := range steps { debugLogs = step.enableDebugLogs stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("%s: unexpected error: %v", step.name, err) - } + assert.NoError(t, err) defer stream.Close() - if err = stream.Send(&testsvc.Fields{}); err != nil { - t.Errorf("%s: unexpected send error: %v", step.name, err) - } - if _, err = stream.Recv(); err != nil { - t.Errorf("%s: unexpected recv error: %v", step.name, err) - } - if buf.String() != step.expectedLogs { - t.Errorf("%s: unexpected log %q", step.name, buf.String()) - } + err = stream.Send(&testsvc.Fields{}) + assert.NoError(t, err) + _, err = stream.Recv() + assert.NoError(t, err) + assert.Equal(t, step.wantLog, buf.String()) buf.Reset() } } diff --git a/debug/http_test.go b/debug/http_test.go index 3148e1e3..2d1d7d1d 100644 --- a/debug/http_test.go +++ b/debug/http_test.go @@ -7,13 +7,17 @@ import ( "net/http/httptest" "testing" + "github.com/stretchr/testify/assert" + "goa.design/clue/log" ) func TestHTTP(t *testing.T) { // Create log context var buf bytes.Buffer - ctx := log.Context(context.Background(), log.WithOutput(&buf), log.WithFormat(logKeyValsOnly)) + ctx := log.Context(context.Background(), + log.WithOutput(&buf), + log.WithFormat(logKeyValsOnly)) log.FlushAndDisableBuffering(ctx) // Create HTTP handler @@ -32,7 +36,9 @@ func TestHTTP(t *testing.T) { // Mount debug handler and log middleware MountDebugLogEnabler(mux) handler = HTTP()(handler) - handler = log.HTTP(ctx, log.WithDisableRequestLogging())(handler) + handler = log.HTTP(ctx, + log.WithDisableRequestLogging(), + log.WithDisableRequestID())(handler) // Start test server mux.Handle("/", handler) @@ -40,17 +46,16 @@ func TestHTTP(t *testing.T) { defer ts.Close() steps := []struct { - name string - on bool - off bool - expectedResp string - expectedLogs string + name string + on bool + off bool + wantLog string }{ - {"start", false, false, "", "test=info "}, - {"turn debug logs on", true, false, `{"debug-logs":true}`, "test=info test=debug "}, - {"with debug logs on", false, false, `{"debug-logs":true}`, "test=info test=debug "}, - {"turn debug logs off", false, true, `{"debug-logs":false}`, "test=info "}, - {"with debug logs off", false, false, `{"debug-logs":false}`, "test=info "}, + {"start", false, false, "test=info "}, + {"turn debug logs on", true, false, "test=info test=debug "}, + {"with debug logs on", false, false, "test=info test=debug "}, + {"turn debug logs off", false, true, "test=info "}, + {"with debug logs off", false, false, "test=info "}, } for _, step := range steps { if step.on { @@ -62,15 +67,9 @@ func TestHTTP(t *testing.T) { status, resp := makeRequest(t, ts.URL) - if status != http.StatusOK { - t.Errorf("%s: got status %d, expected %d", step.name, status, http.StatusOK) - } - if resp != "OK" { - t.Errorf("%s: got body %q, expected %q", step.name, resp, "OK") - } - if buf.String() != step.expectedLogs { - t.Errorf("%s: got logs %q, expected %q", step.name, buf.String(), step.expectedLogs) - } + assert.Equal(t, http.StatusOK, status) + assert.Equal(t, "OK", resp) + assert.Equal(t, step.wantLog, buf.String()) buf.Reset() } } diff --git a/example/weather/README.md b/example/weather/README.md index e26da310..9f3fe1e3 100644 --- a/example/weather/README.md +++ b/example/weather/README.md @@ -22,49 +22,49 @@ scripts/setup scripts/server ``` -`scripts/setup` download build dependencies and compiles the services. +`scripts/setup` configures all the required dependencies and compiles the services. `scripts/server` runs the services using [overmind](https://github.com/DarthSim/overmind). `scripts/server` also starts -`docker-compose` with a configuration that runs the Grafana agent, cortex, tempo -and dashboard locally. +`docker-compose` with a configuration that runs a self-hosted deployment of +[SigNoz](https://signoz.io/) as backend for instrumentation. ### Making a Request Assuming you have a running weather system, you can make a request to the front -service using the `curl` command: +service using the `curl` command as follows: ```bash curl http://localhost:8084/forecast/8.8.8.8 ``` -### Looking at Traces +If this returns successfully start the script that generates load: -To analyze traces: - -* Retrieve the front service trace ID from its logs, for example: - -```text -front | DEBG[0003] svc=front request-id=aZtVOM7L trace-id=fcb9bb474db0b095923b110b7c1cdcab +```bash +scripts/load ``` -* Open the Grafana dashboard running on - [http://localhost:3000](http://localhost:3000), click on `Explore` in the left - pane and select `Tempo` in the top dropdown. Enter the trace ID and voila: +### Looking at Telemetry Data -![Tempo Screenshot](./images/tempo.png) +To analyze traces open the SigNoz dashboard running on +[http://localhost:3301](http://localhost:3301). + +![SigNoz Screenshot](./images/signoz.png) ## Instrumentation ### Logging The three services make use of the -[log](https://github.com/goadesign/clue/tree/main/log) package. The package -is initialized with the key / value pair `svc`:``, for example: +[log](https://github.com/goadesign/clue/tree/main/log) package initialized in +`main`: ```go -ctx := log.With(log.Context(context.Background()), "svc", genfront.ServiceName) +ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(log.Span)) ``` +The `log.WithFormat` option enables colors when logging to a terminal while the +`log.Span` option adds the trace and span IDs to each log entry. + The `front` service uses the HTTP middleware to initialize the log context for for every request: @@ -83,107 +83,83 @@ The gRPC services (`locator` and `forecaster`) use the gRPC interceptor returned ```go grpcsvr := grpc.NewServer( - grpcmiddleware.WithUnaryServerChain( - goagrpcmiddleware.UnaryRequestID(), - log.UnaryServerInterceptor(ctx), // <-- - goagrpcmiddleware.UnaryServerLogContext(log.AsGoaMiddlewareLogger), - metrics.UnaryServerInterceptor(ctx, genforecast.ServiceName), - trace.UnaryServerInterceptor(ctx), - )) -``` - -### Tracing - -The example runs a [Grafana agent](https://grafana.com/docs/grafana-cloud/agent/) -configured to listen to OLTP gRPC requests. The agent forwards the traces to -the [Tempo](https://grafana.com/docs/tempo/latest/) service also running locally. - -Each service uses the -[trace](https://github.com/goadesign/clue/tree/main/trace) package to ship -traces to the agent: - -```go -conn, err := grpc.DialContext(ctx, *collectorAddr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock()) -ctx, err = trace.Context(ctx, genfront.ServiceName, trace.WithGRPCExporter(conn)) -``` - -gRPC services use the `trace.UnaryServerInterceptor` to create a span for each -request: - -```go -grpcsvr := grpc.NewServer( - grpcmiddleware.WithUnaryServerChain( - goagrpcmiddleware.UnaryRequestID(), - log.UnaryServerInterceptor(ctx), - goagrpcmiddleware.UnaryServerLogContext(log.AsGoaMiddlewareLogger), - metrics.UnaryServerInterceptor(ctx, genforecast.ServiceName), - trace.UnaryServerInterceptor(ctx), // <-- - )) -``` - -The front service uses the `trace.HTTP` middleware to create a span for each -request: - -```go -handler = trace.HTTP(ctx)(handler) -``` - -HTTP dependency clients use the `trace.Client` middleware to create spans for -each outgoing request: - -```go -c := &http.Client{Transport: trace.Client(ctx, http.DefaultTransport)} + grpc.ChainUnaryInterceptor( + log.UnaryServerInterceptor(ctx), // <-- + debug.UnaryServerInterceptor()), + grpc.StatsHandler(otelgrpc.NewServerHandler())) ``` -gRPC dependency clients use the `trace.UnaryClientInterceptor` interceptor to -create spans for each outgoing request: +### Metrics and Tracing -```go -lcc, err := grpc.DialContext(ctx, *locatorAddr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithUnaryInterceptor(trace.UnaryClientInterceptor(ctx))) -``` - -### Metrics +The example runs a self-hosted deployment of [SigNoz](https://signoz.io/) as backend for instrumentation. Each service sends telemetry data to the OpenTelemetry collector running in the `docker-compose` configuration. The collector then forwards the data to the SigNoz backend. -The `metrics` package provides a set of instrumentation middleware that -collects metrics from HTTP and gRPC servers and sends them to the -[Tempo](https://grafana.com/docs/tempo/latest/) service. - -First the context is initialized with the service name and optional -options: +The collector is configured in the `main` function of each service: ```go -ctx = metrics.Context(ctx, genfront.ServiceName) -``` - -The gRPC services are instrumented with the `metrics.UnaryServerInterceptor` -interceptor: - -```go -grpcsvr := grpc.NewServer( - grpcmiddleware.WithUnaryServerChain( - goagrpcmiddleware.UnaryRequestID(), - log.UnaryServerInterceptor(ctx), - goagrpcmiddleware.UnaryServerLogContext(log.AsGoaMiddlewareLogger), - metrics.UnaryServerInterceptor(ctx), // <-- - trace.UnaryServerInterceptor(ctx), - )) +spanExporter, err := otlptracegrpc.New(ctx, + otlptracegrpc.WithEndpoint(*oteladdr), + otlptracegrpc.WithTLSCredentials(insecure.NewCredentials())) +if err != nil { + log.Fatalf(ctx, err, "failed to initialize tracing") +} +defer func() { + if err := spanExporter.Shutdown(ctx); err != nil { + log.Errorf(ctx, err, "failed to shutdown tracing") + } +}() +metricExporter, err := otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithEndpoint(*oteladdr), + otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials())) +if err != nil { + log.Fatalf(ctx, err, "failed to initialize metrics") +} +defer func() { + if err := metricExporter.Shutdown(ctx); err != nil { + log.Errorf(ctx, err, "failed to shutdown metrics") + } +}() +cfg, err := clue.NewConfig(ctx, + genforecaster.ServiceName, + genforecaster.APIVersion, + metricExporter, + spanExporter, +) +if err != nil { + log.Fatalf(ctx, err, "failed to initialize instrumentation") +} +clue.ConfigureOpenTelemetry(ctx, cfg) ``` -The front service is instrumented with the `metrics.HTTP` middleware: +Clients of downstream services are also instrumented using OpenTelemetry. For +example the following code creates an instrumented gRPC connection and uses it +to create a client to the `Locator` service: ```go -handler = metrics.HTTP(ctx)(handler) +lcc, err := grpc.DialContext(ctx, + *locatorAddr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), // Log requests + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) // Collect metrics and traces +if err != nil { + log.Fatalf(ctx, err, "failed to connect to locator") +} +lc := locator.New(lcc) // Create client using instrumented connection ``` -All the services run a HTTP server that exposes a Prometheus metrics endpoint at -`/metrics`. +The example also showcases the instrumentation of clients to external services. +For example, the `Locator` service demonstrates how the HTTP client used to +create the IP Location service client is instrumented: ```go -http.Handle("/metrics", metrics.Handler(ctx)) +httpc := &http.Client{ + Transport: log.Client( // Log requests + otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { + return otelhttptrace.NewClientTrace(ctx) + }), // Propagate traces + ))} +ipc := ipapi.New(httpc) ``` ### Health Checks diff --git a/example/weather/docker-compose/agent/config/agent.yaml b/example/weather/docker-compose/agent/config/agent.yaml deleted file mode 100644 index 3b90a82c..00000000 --- a/example/weather/docker-compose/agent/config/agent.yaml +++ /dev/null @@ -1,76 +0,0 @@ -server: - log_level: info - http_listen_port: 12345 - graceful_shutdown_timeout: 10s - -metrics: - global: - scrape_interval: 10s - configs: - - name: test - host_filter: false - scrape_configs: - - job_name: front_scrape - static_configs: - - targets: ['host.docker.internal:8085'] - - job_name: locator_scrape - static_configs: - - targets: ['host.docker.internal:8083'] - - job_name: forecast_scrape - static_configs: - - targets: ['host.docker.internal:8081'] - - job_name: local_scrape - static_configs: - - targets: ['127.0.0.1:12345', '127.0.0.1:8889'] - labels: - cluster: 'docker_compose' - container: 'agent' - pod: 'grafana-agent-local' - - job_name: cortex_scrape - static_configs: - - targets: ['cortex:9009'] - labels: - cluster: 'docker_compose' - container: 'cortex' - remote_write: - - url: http://cortex:9009/api/prom/push - -logs: - configs: - - name: default - positions: - filename: /tmp/positions.yaml - clients: - - url: http://loki:3100/loki/api/v1/push - scrape_configs: - - job_name: system - static_configs: - - targets: - - localhost - labels: - job: varlogs - __path__: /var/log/*log - -traces: - configs: - - name: default - receivers: - otlp: - protocols: - grpc: - remote_write: - - endpoint: tempo:4317 - insecure: true - batch: - timeout: 5s - send_batch_size: 100 - automatic_logging: - backend: logs_instance - logs_instance_name: default - spans: true - processes: true - roots: true - spanmetrics: - handler_endpoint: 0.0.0.0:8889 - service_graphs: - enabled: true diff --git a/example/weather/docker-compose/cortex/config/cortex.yaml b/example/weather/docker-compose/cortex/config/cortex.yaml deleted file mode 100644 index dfe8243d..00000000 --- a/example/weather/docker-compose/cortex/config/cortex.yaml +++ /dev/null @@ -1,66 +0,0 @@ -auth_enabled: false - -server: - http_listen_port: 9009 - - # Configure the server to allow messages up to 100MB. - grpc_server_max_recv_msg_size: 104857600 - grpc_server_max_send_msg_size: 104857600 - grpc_server_max_concurrent_streams: 1000 - -distributor: - shard_by_all_labels: true - pool: - health_check_ingesters: true - -ingester_client: - grpc_client_config: - max_recv_msg_size: 104857600 - max_send_msg_size: 104857600 - grpc_compression: gzip - -ingester: - lifecycler: - join_after: 0 - min_ready_duration: 0s - final_sleep: 0s - num_tokens: 512 - - ring: - kvstore: - store: inmemory - replication_factor: 1 - -storage: - engine: blocks - -blocks_storage: - tsdb: - dir: /tmp/cortex/tsdb - bucket_store: - sync_dir: /tmp/cortex/tsdb-sync - - backend: filesystem - filesystem: - dir: /tmp/cortex/blocks - -compactor: - data_dir: /tmp/cortex/compactor - sharding_ring: - kvstore: - store: inmemory - -frontend_worker: - match_max_concurrent: true - -ruler: - enable_api: true - enable_sharding: false - storage: - type: local - local: - directory: /tmp/cortex/rules - -limits: - ingestion_rate: 250000 - ingestion_burst_size: 500000 diff --git a/example/weather/docker-compose/docker-compose-grafana.yaml b/example/weather/docker-compose/docker-compose-grafana.yaml deleted file mode 100644 index b216fcfe..00000000 --- a/example/weather/docker-compose/docker-compose-grafana.yaml +++ /dev/null @@ -1,59 +0,0 @@ -version: "2" -services: - agent: - image: grafana/agent:latest - volumes: - - ./agent/config:/etc/agent-config - - /var/log:/var/log - entrypoint: - - /bin/grafana-agent - - -config.file=/etc/agent-config/agent.yaml - - -prometheus.wal-directory=/tmp/agent/wal - ports: - - "12345:12345" - depends_on: - - tempo - extra_hosts: - - "host.docker.internal:${HOST_GATEWAY}" - - loki: - image: grafana/loki:2.2.1 - ports: - - "3100:3100" - command: -config.file=/etc/loki/local-config.yaml - - cortex: - image: cortexproject/cortex:v1.8.1 - volumes: - - /tmp/cortex:/tmp/cortex - - ./cortex/config:/etc/cortex-config - entrypoint: - - /bin/cortex - - -config.file=/etc/cortex-config/cortex.yaml - ports: - - "9009:9009" - - # tracing backend - tempo: - image: grafana/tempo:latest - volumes: - - /tmp/tempo:/tmp/tempo - - ./tempo/config:/etc/tempo-config - command: - - "-config.file=/etc/tempo-config/tempo.yaml" - ports: - - "4317:4317" - - grafana: - image: grafana/grafana:7.5.4 - entrypoint: - - /usr/share/grafana/bin/grafana-server - - --homepath=/usr/share/grafana - - --config=/etc/grafana-config/grafana.ini - volumes: - - ./grafana/config:/etc/grafana-config - - ./grafana/datasources:/etc/grafana/provisioning/datasources - - ./grafana/dashboards-provisioning:/etc/grafana/provisioning/dashboards - - ./grafana/dashboards:/var/lib/grafana/dashboards - ports: - - "3000:3000" \ No newline at end of file diff --git a/example/weather/docker-compose/grafana/config/grafana.ini b/example/weather/docker-compose/grafana/config/grafana.ini deleted file mode 100644 index ba1078c5..00000000 --- a/example/weather/docker-compose/grafana/config/grafana.ini +++ /dev/null @@ -1,9 +0,0 @@ -[analytics] -reporting_enabled = false -[auth.anonymous] -enabled = true -org_role = Admin -[explore] -enabled = true -[users] -default_theme = dark diff --git a/example/weather/docker-compose/grafana/dashboards-provisioning/dashboards.yaml b/example/weather/docker-compose/grafana/dashboards-provisioning/dashboards.yaml deleted file mode 100644 index c038adf8..00000000 --- a/example/weather/docker-compose/grafana/dashboards-provisioning/dashboards.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: 1 - -providers: -- name: 'dashboards' - orgId: 1 - folder: '' - folderUid: '' - type: file - disableDeletion: true - editable: true - updateIntervalSeconds: 10 - allowUiUpdates: false - options: - path: /var/lib/grafana/dashboards diff --git a/example/weather/docker-compose/grafana/dashboards/agent-operational.json b/example/weather/docker-compose/grafana/dashboards/agent-operational.json deleted file mode 100644 index 6d6c1639..00000000 --- a/example/weather/docker-compose/grafana/dashboards/agent-operational.json +++ /dev/null @@ -1,1189 +0,0 @@ -{ - "annotations": { - "list": [ ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "hideControls": false, - "links": [ ], - "refresh": "30s", - "rows": [ - { - "collapse": false, - "height": "250px", - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 1, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(go_gc_duration_seconds_count{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "GCs", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "go_memstats_heap_inuse_bytes{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Go Heap", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "decbytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 3, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "go_goroutines{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Goroutines", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 4, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(container_cpu_usage_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "CPU", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 5, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "container_memory_working_set_bytes{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "WSS", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 6, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(promtail_custom_bad_words_total{cluster=~\"$cluster\", exported_namespace=~\"$namespace\", exported_job=~\"$job\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{job}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Bad Words", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "General", - "titleSize": "h6" - }, - { - "collapse": false, - "height": "250px", - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 7, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (pod) (rate(container_network_receive_bytes_total{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\"}[5m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "RX by Pod", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 8, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (pod) (rate(container_network_transmit_bytes_total{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\"}[5m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "TX by Pod", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Network", - "titleSize": "h6" - }, - { - "collapse": false, - "height": "250px", - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 9, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "(sum by (pod) (avg_over_time(go_memstats_heap_inuse_bytes{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}[1m])))\n/\n(sum by (pod) (agent_wal_storage_active_series{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Bytes/Series/Pod", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "decbytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 10, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "(sum by (container) (avg_over_time(go_memstats_heap_inuse_bytes{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}[1m])))\n/\n(sum by (container) (agent_wal_storage_active_series{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"}))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{container}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Bytes/Series", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "decbytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 11, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (pod) (agent_wal_storage_active_series{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Series/Pod", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 12, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (instance_group_name) (agent_wal_storage_active_series{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{instance_group_name}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Series/Config", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 13, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 2, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (container) (agent_wal_storage_active_series{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", pod=~\"$pod\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{container}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Series", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Prometheus Read", - "titleSize": "h6" - } - ], - "schemaVersion": 14, - "style": "dark", - "tags": [ - "grafana-agent-mixin" - ], - "templating": { - "list": [ - { - "current": { - "text": "default", - "value": "default" - }, - "hide": 0, - "label": "Data Source", - "name": "datasource", - "options": [ ], - "query": "prometheus", - "refresh": 1, - "regex": "", - "type": "datasource" - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "cluster", - "multi": true, - "name": "cluster", - "options": [ ], - "query": "label_values(agent_build_info, cluster)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "namespace", - "multi": true, - "name": "namespace", - "options": [ ], - "query": "label_values(agent_build_info, namespace)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "container", - "multi": true, - "name": "container", - "options": [ ], - "query": "label_values(agent_build_info, container)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": "grafana-agent-.*", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "pod", - "multi": true, - "name": "pod", - "options": [ ], - "query": "label_values(agent_build_info{container=~\"$container\"}, pod)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Agent Operational", - "uid": "", - "version": 0 -} diff --git a/example/weather/docker-compose/grafana/dashboards/agent-remote-write.json b/example/weather/docker-compose/grafana/dashboards/agent-remote-write.json deleted file mode 100644 index f2227746..00000000 --- a/example/weather/docker-compose/grafana/dashboards/agent-remote-write.json +++ /dev/null @@ -1,1512 +0,0 @@ -{ - "__inputs": [ ], - "__requires": [ ], - "annotations": { - "list": [ ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "hideControls": false, - "id": null, - "links": [ ], - "refresh": "30s", - "rows": [ - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 2, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "(\n prometheus_remote_storage_highest_timestamp_in_seconds{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}\n -\n ignoring(url, remote_name) group_right(pod)\n prometheus_remote_storage_queue_highest_sent_timestamp_seconds{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}\n)\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Highest Timestamp In vs. Highest Timestamp Sent", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 3, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_remote_storage_sent_batch_duration_seconds_sum{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m]) / rate(prometheus_remote_storage_sent_batch_duration_seconds_count{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "mean {{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - }, - { - "expr": "histogram_quantile(0.99, rate(prometheus_remote_storage_sent_batch_duration_seconds_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "p99 {{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "B" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Latency [1m]", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Timestamps", - "titleSize": "h6", - "type": "row" - }, - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 4, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(agent_wal_samples_appended_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Rate in [5m]", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 5, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_remote_storage_succeeded_samples_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m]) or rate(prometheus_remote_storage_samples_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Rate succeeded [5m]", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 6, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_remote_storage_samples_pending{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Pending Samples", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 7, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_remote_storage_samples_dropped_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Dropped Samples", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 8, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_remote_storage_samples_failed_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Failed Samples", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 9, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_remote_storage_samples_retried_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Retried Samples", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Samples", - "titleSize": "h6", - "type": "row" - }, - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 10, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "minSpan": 6, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 12, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_remote_storage_shards{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Current Shards", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 11, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 4, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_remote_storage_shards_max{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Max Shards", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 12, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 4, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_remote_storage_shards_min{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Min Shards", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 13, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 4, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_remote_storage_shards_desired{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Desired Shards", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Shards", - "titleSize": "h6", - "type": "row" - }, - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 14, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_remote_storage_shard_capacity{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Shard Capacity", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Shard Details", - "titleSize": "h6", - "type": "row" - }, - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 15, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "prometheus_wal_watcher_current_segment{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Remote Write Current Segment", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "none", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Segments", - "titleSize": "h6", - "type": "row" - }, - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 16, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_remote_storage_enqueue_retries_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{cluster}}:{{pod}}-{{instance_group_name}}-{{url}}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Enqueue Retries", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Misc. Rates", - "titleSize": "h6", - "type": "row" - } - ], - "schemaVersion": 14, - "style": "dark", - "tags": [ - "grafana-agent-mixin" - ], - "templating": { - "list": [ - { - "hide": 0, - "label": null, - "name": "datasource", - "options": [ ], - "query": "prometheus", - "refresh": 1, - "regex": "", - "type": "datasource" - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "cluster", - "options": [ ], - "query": "label_values(agent_build_info, cluster)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "namespace", - "options": [ ], - "query": "label_values(agent_build_info, namespace)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "container", - "options": [ ], - "query": "label_values(agent_build_info, container)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "pod", - "options": [ ], - "query": "label_values(agent_build_info{container=~\"$container\"}, pod)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "url", - "options": [ ], - "query": "label_values(prometheus_remote_storage_shards{cluster=~\"$cluster\", pod=~\"$pod\"}, url)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Agent Prometheus Remote Write", - "version": 0 -} diff --git a/example/weather/docker-compose/grafana/dashboards/agent-tracing-pipeline.json b/example/weather/docker-compose/grafana/dashboards/agent-tracing-pipeline.json deleted file mode 100644 index 3e9415b9..00000000 --- a/example/weather/docker-compose/grafana/dashboards/agent-tracing-pipeline.json +++ /dev/null @@ -1,1065 +0,0 @@ -{ - "__inputs": [ ], - "__requires": [ ], - "annotations": { - "list": [ ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "hideControls": false, - "id": null, - "links": [ ], - "refresh": "30s", - "rows": [ - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 0, - "fillGradient": 0, - "gridPos": { }, - "id": 2, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(traces_receiver_accepted_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",receiver!=\"otlp/lb\"}[$__rate_interval])\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{ pod }} - {{ receiver }}/{{ transport }}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Accepted spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 0, - "fillGradient": 0, - "gridPos": { }, - "id": 3, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(traces_receiver_refused_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",receiver!=\"otlp/lb\"}[$__rate_interval])\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{ pod }} - {{ receiver }}/{{ transport }}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Refused spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 0, - "fillGradient": 0, - "gridPos": { }, - "id": 4, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(traces_exporter_sent_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",exporter!=\"otlp\"}[$__rate_interval])\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{ pod }} - {{ exporter }}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Exported spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 0, - "fillGradient": 0, - "gridPos": { }, - "id": 5, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(traces_exporter_send_failed_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",exporter!=\"otlp\"}[$__rate_interval])\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{ pod }} - {{ exporter }}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Exported failed spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 6, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(traces_receiver_accepted_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",receiver!=\"otlp/lb\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Accepted", - "refId": "A" - }, - { - "expr": "sum(rate(traces_receiver_refused_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",receiver!=\"otlp/lb\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Refused", - "refId": "B" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Received spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 7, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(traces_exporter_sent_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",exporter!=\"otlp\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Sent", - "refId": "A" - }, - { - "expr": "sum(rate(traces_exporter_send_failed_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",exporter!=\"otlp\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Send failed", - "refId": "B" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Exported spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Write / Read", - "titleSize": "h6", - "type": "row" - }, - { - "collapse": false, - "collapsed": false, - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 8, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "rate(traces_loadbalancer_backend_outcome{cluster=~\"$cluster\",namespace=~\"$namespace\",success=\"true\",container=~\"$container\",pod=~\"$pod\"}[$__rate_interval])\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{ pod }}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Load-balanced spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 0, - "fillGradient": 0, - "gridPos": { }, - "id": 9, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "traces_loadbalancer_num_backends{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\"}\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{ pod }}", - "refId": "A" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Number of peers", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 10, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(traces_receiver_accepted_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",receiver=\"otlp/lb\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Accepted", - "refId": "A" - }, - { - "expr": "sum(rate(traces_receiver_refused_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",receiver=\"otlp/lb\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Refused", - "refId": "B" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Received spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "fillGradient": 0, - "gridPos": { }, - "id": 11, - "interval": "1m", - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 3, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(traces_exporter_sent_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",exporter=\"otlp\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Sent", - "refId": "A" - }, - { - "expr": "sum(rate(traces_exporter_send_failed_spans{cluster=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",pod=~\"$pod\",exporter=\"otlp\"}[$__rate_interval]))\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Send failed", - "refId": "B" - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Exported spans", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Load balancing", - "titleSize": "h6", - "type": "row" - } - ], - "schemaVersion": 14, - "style": "dark", - "tags": [ - "grafana-agent-mixin" - ], - "templating": { - "list": [ - { - "hide": 0, - "label": null, - "name": "datasource", - "options": [ ], - "query": "prometheus", - "refresh": 1, - "regex": "", - "type": "datasource" - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "cluster", - "options": [ ], - "query": "label_values(agent_build_info, cluster)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "namespace", - "options": [ ], - "query": "label_values(agent_build_info, namespace)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "container", - "options": [ ], - "query": "label_values(agent_build_info, container)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": { - "text": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "value": { - "selected": true, - "text": "All", - "value": "$__all" - } - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "pod", - "options": [ ], - "query": "label_values(agent_build_info{container=~\"$container\"}, pod)", - "refresh": 2, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Agent Tracing Pipeline", - "version": 0 -} diff --git a/example/weather/docker-compose/grafana/dashboards/agent.json b/example/weather/docker-compose/grafana/dashboards/agent.json deleted file mode 100644 index 768fccb0..00000000 --- a/example/weather/docker-compose/grafana/dashboards/agent.json +++ /dev/null @@ -1,786 +0,0 @@ -{ - "annotations": { - "list": [ ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "hideControls": false, - "links": [ ], - "refresh": "30s", - "rows": [ - { - "collapse": false, - "height": "250px", - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 1, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 12, - "stack": false, - "steppedLine": false, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "hidden" - }, - { - "alias": "Count", - "colorMode": null, - "colors": [ ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkTargetBlank": false, - "linkTooltip": "Drill down", - "linkUrl": "", - "pattern": "Value #A", - "thresholds": [ ], - "type": "hidden", - "unit": "short" - }, - { - "alias": "Uptime", - "colorMode": null, - "colors": [ ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkTargetBlank": false, - "linkTooltip": "Drill down", - "linkUrl": "", - "pattern": "Value #B", - "thresholds": [ ], - "type": "number", - "unit": "short" - }, - { - "alias": "Container", - "colorMode": null, - "colors": [ ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkTargetBlank": false, - "linkTooltip": "Drill down", - "linkUrl": "", - "pattern": "container", - "thresholds": [ ], - "type": "number", - "unit": "short" - }, - { - "alias": "Pod", - "colorMode": null, - "colors": [ ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkTargetBlank": false, - "linkTooltip": "Drill down", - "linkUrl": "", - "pattern": "pod", - "thresholds": [ ], - "type": "number", - "unit": "short" - }, - { - "alias": "Version", - "colorMode": null, - "colors": [ ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkTargetBlank": false, - "linkTooltip": "Drill down", - "linkUrl": "", - "pattern": "version", - "thresholds": [ ], - "type": "number", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "/.*/", - "thresholds": [ ], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "expr": "count by (pod, container, version) (agent_build_info{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"})", - "format": "table", - "instant": true, - "intervalFactor": 2, - "legendFormat": "", - "refId": "A", - "step": 10 - }, - { - "expr": "max by (pod, container) (time() - process_start_time_seconds{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"})", - "format": "table", - "instant": true, - "intervalFactor": 2, - "legendFormat": "", - "refId": "B", - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Agent Stats", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "transform": "table", - "type": "table", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Agent Stats", - "titleSize": "h6" - }, - { - "collapse": false, - "height": "250px", - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(prometheus_target_sync_length_seconds_sum{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])) by (pod, scrape_job) * 1e3", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}/{{scrape_job}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Target Sync", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "ms", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 10, - "id": 3, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 0, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 6, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (pod) (prometheus_sd_discovered_targets{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Targets", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Prometheus Discovery", - "titleSize": "h6" - }, - { - "collapse": false, - "height": "250px", - "panels": [ - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, - "id": 4, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 4, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(prometheus_target_interval_length_seconds_sum{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])\n/\nrate(prometheus_target_interval_length_seconds_count{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m])\n* 1e3\n", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{pod}} {{interval}} configured", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Average Scrape Interval Duration", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "ms", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 10, - "id": 5, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 0, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 4, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (job) (rate(prometheus_target_scrapes_exceeded_sample_limit_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "exceeded sample limit: {{job}}", - "legendLink": null, - "step": 10 - }, - { - "expr": "sum by (job) (rate(prometheus_target_scrapes_sample_duplicate_timestamp_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "duplicate timestamp: {{job}}", - "legendLink": null, - "step": 10 - }, - { - "expr": "sum by (job) (rate(prometheus_target_scrapes_sample_out_of_bounds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "out of bounds: {{job}}", - "legendLink": null, - "step": 10 - }, - { - "expr": "sum by (job) (rate(prometheus_target_scrapes_sample_out_of_order_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "out of order: {{job}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Scrape failures", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - }, - { - "aliasColors": { }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 10, - "id": 6, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 0, - "links": [ ], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ ], - "spaceLength": 10, - "span": 4, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (job, instance_group_name) (rate(agent_wal_samples_appended_total{cluster=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\"}[5m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{job}} {{instance_group_name}}", - "legendLink": null, - "step": 10 - } - ], - "thresholds": [ ], - "timeFrom": null, - "timeShift": null, - "title": "Appended Samples", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [ ] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ] - } - ], - "repeat": null, - "repeatIteration": null, - "repeatRowId": null, - "showTitle": true, - "title": "Prometheus Retrieval", - "titleSize": "h6" - } - ], - "schemaVersion": 14, - "style": "dark", - "tags": [ - "grafana-agent-mixin" - ], - "templating": { - "list": [ - { - "current": { - "text": "default", - "value": "default" - }, - "hide": 0, - "label": "Data Source", - "name": "datasource", - "options": [ ], - "query": "prometheus", - "refresh": 1, - "regex": "", - "type": "datasource" - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "cluster", - "multi": true, - "name": "cluster", - "options": [ ], - "query": "label_values(agent_build_info, cluster)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "namespace", - "multi": true, - "name": "namespace", - "options": [ ], - "query": "label_values(agent_build_info, namespace)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".+", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "container", - "multi": true, - "name": "container", - "options": [ ], - "query": "label_values(agent_build_info, container)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": "grafana-agent-.*", - "current": { - "selected": true, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "hide": 0, - "includeAll": true, - "label": "pod", - "multi": true, - "name": "pod", - "options": [ ], - "query": "label_values(agent_build_info{container=~\"$container\"}, pod)", - "refresh": 1, - "regex": "", - "sort": 2, - "tagValuesQuery": "", - "tags": [ ], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Agent", - "uid": "", - "version": 0 -} diff --git a/example/weather/docker-compose/grafana/dashboards/template.jsonnet b/example/weather/docker-compose/grafana/dashboards/template.jsonnet deleted file mode 100644 index edc46021..00000000 --- a/example/weather/docker-compose/grafana/dashboards/template.jsonnet +++ /dev/null @@ -1,14 +0,0 @@ -local agentDashboards = import 'grafana-agent-mixin/dashboards.libsonnet'; -local agentDebugging = import 'grafana-agent-mixin/debugging.libsonnet'; - -local result = agentDashboards + agentDebugging { - files: { - [name]: $.grafanaDashboards[name] { - // Use local timezone for local testing - timezone: '', - } - for name in std.objectFields($.grafanaDashboards) - }, -}; - -result.files diff --git a/example/weather/docker-compose/grafana/datasources/datasource.yml b/example/weather/docker-compose/grafana/datasources/datasource.yml deleted file mode 100644 index 1eeb4855..00000000 --- a/example/weather/docker-compose/grafana/datasources/datasource.yml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: 1 - -deleteDatasources: - - name: Cortex - -datasources: -- name: Cortex - type: prometheus - access: proxy - orgId: 1 - url: http://cortex:9009/api/prom - basicAuth: false - isDefault: false - version: 1 - editable: false -- name: Tempo - type: tempo - access: proxy - orgId: 1 - url: http://tempo:3200 - basicAuth: false - isDefault: false - version: 1 - editable: false - apiVersion: 1 - uid: tempo -- name: Loki - type: loki - access: proxy - orgId: 1 - url: http://loki:3100 - basicAuth: false - isDefault: false - version: 1 - editable: false - jsonData: - derivedFields: - - datasourceUid: tempo - matcherRegex: tid=(\w+) - name: TraceID - url: $${__value.raw} - diff --git a/example/weather/docker-compose/tempo/config/tempo.yaml b/example/weather/docker-compose/tempo/config/tempo.yaml deleted file mode 100644 index c00a0248..00000000 --- a/example/weather/docker-compose/tempo/config/tempo.yaml +++ /dev/null @@ -1,18 +0,0 @@ -server: - http_listen_port: 3200 - graceful_shutdown_timeout: 10s - -distributor: - receivers: - otlp: - protocols: - grpc: - log_received_traces: true - -storage: - trace: - backend: local - local: - path: /tmp/tempo/traces - wal: - path: /tmp/tempo/wal diff --git a/example/weather/go.mod b/example/weather/go.mod index d83d0886..06e6e550 100644 --- a/example/weather/go.mod +++ b/example/weather/go.mod @@ -2,59 +2,55 @@ module goa.design/clue/example/weather go 1.21 -replace goa.design/clue => ../../ - require ( github.com/stretchr/testify v1.8.4 - goa.design/clue v0.18.2 - goa.design/goa/v3 v3.14.0 - goa.design/model v1.9.0 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 + goa.design/clue v0.20.0 + goa.design/goa/v3 v3.14.2 + goa.design/model v1.9.1 + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc + google.golang.org/grpc v1.60.1 + google.golang.org/protobuf v1.32.0 ) require ( github.com/AnatolyRugalev/goregen v0.1.0 // indirect - github.com/aws/smithy-go v1.17.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-chi/chi/v5 v5.0.10 // indirect + github.com/go-chi/chi/v5 v5.0.11 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/term v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.15.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + golang.org/x/tools v0.16.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace goa.design/clue => ../../ diff --git a/example/weather/go.sum b/example/weather/go.sum index b1d9273c..ebb8d943 100644 --- a/example/weather/go.sum +++ b/example/weather/go.sum @@ -4,14 +4,10 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/AnatolyRugalev/goregen v0.1.0 h1:xrdXkLaskMnbxW0x4FWNj2yoednv0X2bcTBWpuJGYfE= github.com/AnatolyRugalev/goregen v0.1.0/go.mod h1:sVlY1tjcirqLBRZnCcIq1+7/Lwmqz5g7IK8AStjOVzI= -github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI= -github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -23,13 +19,13 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBF github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= -github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= +github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -38,12 +34,12 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -57,85 +53,83 @@ github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqC github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 h1:gbhw/u49SS3gkPWiYweQNJGm/uJN5GkI/FrosxSHT7A= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -goa.design/goa/v3 v3.14.0 h1:Ymm6hDyxWFiYclVHcMs1cl7vgSHiYLSIweHE74EusoA= -goa.design/goa/v3 v3.14.0/go.mod h1:QYVl5438/92SiqcIzYpIwz10QAocAJeacQu+FO0lTXQ= -goa.design/model v1.9.0 h1:EUERzWyBMnLQzVw6AtYGIM7pLan9IXhcvWxTx/0khOA= -goa.design/model v1.9.0/go.mod h1:OQtQGJ9rp9TNzpk6gSA066538zgPM9duLz9iroG5EJ4= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +goa.design/goa/v3 v3.14.2 h1:V8skSFWRUTddqZnRy65Mdx0SpGmYe9aQhgCQppyvCKA= +goa.design/goa/v3 v3.14.2/go.mod h1:sq7F2y/2/eK/XCc8Ff7Was3u42fRmQXp1pTm8FfnGgo= +goa.design/model v1.9.1 h1:vhY89pjUWW1firAHaccW9MZTiMEEVvtcwN9g8odyywQ= +goa.design/model v1.9.1/go.mod h1:PlrrmVbrBm7gxqoWqburVUudaMd/d3Sdul7/Kr4Ap54= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= -golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= -google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/example/weather/images/signoz.png b/example/weather/images/signoz.png new file mode 100644 index 00000000..0b089a8f Binary files /dev/null and b/example/weather/images/signoz.png differ diff --git a/example/weather/images/tempo.png b/example/weather/images/tempo.png deleted file mode 100644 index 1fa0beff..00000000 Binary files a/example/weather/images/tempo.png and /dev/null differ diff --git a/example/weather/scripts/load b/example/weather/scripts/load new file mode 100755 index 00000000..e3be863c --- /dev/null +++ b/example/weather/scripts/load @@ -0,0 +1,20 @@ +#!/bin/bash + +# Total duration for the script to run: 10 minutes (600 seconds) +DURATION=600 + +# Start time +START_TIME=$(date +%s) + +# End time = Start time + Duration +END_TIME=$((START_TIME + DURATION)) + +# Loop to run until the current time is less than the end time +while [ $(date +%s) -lt $END_TIME ]; do + # Run the command + http localhost:8084/forecast/142.250.68.14 + + # Wait for 1 second before the next iteration + sleep 1 +done + diff --git a/example/weather/scripts/server b/example/weather/scripts/server index fa4cf93e..af5f478a 100755 --- a/example/weather/scripts/server +++ b/example/weather/scripts/server @@ -21,7 +21,7 @@ else export HOST_GATEWAY='host-gateway' fi -docker-compose -f docker-compose/docker-compose-grafana.yaml up -d +docker-compose -f signoz/deploy/docker/clickhouse-setup/docker-compose.yaml up -d overmind start diff --git a/example/weather/scripts/setup b/example/weather/scripts/setup index fa73e992..2de51817 100755 --- a/example/weather/scripts/setup +++ b/example/weather/scripts/setup @@ -31,7 +31,6 @@ if [[ "$CI" == "" ]]; then fi go mod download -go install honnef.co/go/tools/cmd/staticcheck@latest go install goa.design/clue/mock/cmd/cmg@latest go install goa.design/model/cmd/mdl@latest go install goa.design/goa/v3/...@v3 @@ -39,6 +38,13 @@ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0 go install github.com/DarthSim/overmind/v2@latest +# clone SigNoz for docker compose files +if [[ ! -d "signoz" ]]; then + git clone --depth 1 -b main https://github.com/SigNoz/signoz.git + # remove the hotrod example + sed -i.bak '/hotrod:/,$d' signoz/deploy/docker/clickhouse-setup/docker-compose.yaml +fi + ./scripts/build popd diff --git a/example/weather/services/forecaster/cmd/forecaster/main.go b/example/weather/services/forecaster/cmd/forecaster/main.go index e25f79d4..fad4a81f 100644 --- a/example/weather/services/forecaster/cmd/forecaster/main.go +++ b/example/weather/services/forecaster/cmd/forecaster/main.go @@ -6,18 +6,22 @@ import ( "fmt" "net" "net/http" + "net/http/httptrace" "os" "os/signal" "sync" "syscall" "time" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "goa.design/clue/clue" "goa.design/clue/debug" "goa.design/clue/health" "goa.design/clue/log" - "goa.design/clue/metrics" - "goa.design/clue/trace" - goagrpcmiddleware "goa.design/goa/v3/grpc/middleware" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/reflection" @@ -31,11 +35,10 @@ import ( func main() { var ( - grpcaddr = flag.String("grpc-addr", ":8080", "gRPC listen address") - httpaddr = flag.String("http-addr", ":8081", "HTTP listen address (health checks and metrics)") - agentaddr = flag.String("agent-addr", ":4317", "Grafana agent listen address") - debugf = flag.Bool("debug", false, "Enable debug logs") - monitoringEnabled = flag.Bool("monitoring-enabled", true, "monitoring") + grpcaddr = flag.String("grpc-addr", ":8080", "gRPC listen address") + httpaddr = flag.String("http-addr", ":8081", "HTTP listen address (health checks and metrics)") + coladdr = flag.String("otel-addr", ":4317", "OpenTelemetry collector listen address") + debugf = flag.Bool("debug", false, "Enable debug logs") ) flag.Parse() @@ -44,60 +47,70 @@ func main() { if log.IsTerminal() { format = log.FormatTerminal } - ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(trace.Log)) - ctx = log.With(ctx, log.KV{K: "svc", V: genforecaster.ServiceName}) + ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(log.Span)) if *debugf { ctx = log.Context(ctx, log.WithDebug()) log.Debugf(ctx, "debug logs enabled") } - // 2. Setup tracing - if !*monitoringEnabled { - var err error - if ctx, err = trace.Context(ctx, genforecaster.ServiceName, trace.WithDisabled()); err != nil { - log.Error(ctx, err, log.KV{K: "msg", V: "failed to initialize tracing"}) - } - } else { - log.Debugf(ctx, "connecting to Grafana agent %s", *agentaddr) - conn, err := grpc.DialContext(ctx, *agentaddr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock()) - if err != nil { - log.Errorf(ctx, err, "failed to connect to Grafana agent") - os.Exit(1) + // 2. Setup instrumentation + spanExporter, err := otlptracegrpc.New(ctx, + otlptracegrpc.WithEndpoint(*coladdr), + otlptracegrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize tracing") + } + defer func() { + if err := spanExporter.Shutdown(ctx); err != nil { + log.Errorf(ctx, err, "failed to shutdown tracing") } - log.Debugf(ctx, "connected to Grafana agent %s", *agentaddr) - ctx, err = trace.Context(ctx, genforecaster.ServiceName, trace.WithGRPCExporter(conn)) - if err != nil { - log.Errorf(ctx, err, "failed to initialize tracing") - os.Exit(1) + }() + metricExporter, err := otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithEndpoint(*coladdr), + otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize metrics") + } + defer func() { + if err := metricExporter.Shutdown(ctx); err != nil { + log.Errorf(ctx, err, "failed to shutdown metrics") } + }() + cfg, err := clue.NewConfig(ctx, + genforecaster.ServiceName, + genforecaster.APIVersion, + metricExporter, + spanExporter, + ) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize instrumentation") } - - // 3. Setup metrics - ctx = metrics.Context(ctx, genforecaster.ServiceName) - - // 4. Create clients - c := &http.Client{Transport: log.Client(trace.Client(ctx, http.DefaultTransport))} - wc := weathergov.New(c) - - // 5. Create service & endpoints + clue.ConfigureOpenTelemetry(ctx, cfg) + + // 3. Create clients + httpc := &http.Client{ + Transport: log.Client( + otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { + return otelhttptrace.NewClientTrace(ctx) + }), + ))} + wc := weathergov.New(httpc) + + // 4. Create service & endpoints svc := forecaster.New(wc) endpoints := genforecaster.NewEndpoints(svc) endpoints.Use(debug.LogPayloads()) endpoints.Use(log.Endpoint) - // 6. Create transport + // 5. Create transport server := gengrpc.New(endpoints, nil) grpcsvr := grpc.NewServer( grpc.ChainUnaryInterceptor( - goagrpcmiddleware.UnaryRequestID(), - log.UnaryServerInterceptor(ctx), - debug.UnaryServerInterceptor(), - goagrpcmiddleware.UnaryServerLogContext(log.AsGoaMiddlewareLogger), - metrics.UnaryServerInterceptor(ctx), - trace.UnaryServerInterceptor(ctx), - )) + log.UnaryServerInterceptor(ctx), // Add logger to request context and log requests. + debug.UnaryServerInterceptor()), // Enable debug log level control + grpc.StatsHandler(otelgrpc.NewServerHandler())) // Instrument server. genpb.RegisterForecasterServer(grpcsvr, server) reflection.Register(grpcsvr) for svc, info := range grpcsvr.GetServiceInfo() { @@ -106,17 +119,17 @@ func main() { } } - // 7. Setup health check, metrics and debug endpoints - check := log.HTTP(ctx)(health.Handler(health.NewChecker(wc))) + // 6. Setup health check and debug endpoints mux := http.NewServeMux() debug.MountDebugLogEnabler(mux) debug.MountPprofHandlers(mux) + check := health.Handler(health.NewChecker(wc)) + check = log.HTTP(ctx)(check).(http.HandlerFunc) // Log health-check errors mux.Handle("/healthz", check) mux.Handle("/livez", check) - mux.Handle("/metrics", metrics.Handler(ctx)) httpsvr := &http.Server{Addr: *httpaddr, Handler: mux} - // 8. Start gRPC and HTTP servers + // 7. Start gRPC and HTTP servers errc := make(chan error) go func() { c := make(chan os.Signal, 1) diff --git a/example/weather/services/forecaster/design/design.go b/example/weather/services/forecaster/design/design.go index a6e6375a..a6ad9e37 100644 --- a/example/weather/services/forecaster/design/design.go +++ b/example/weather/services/forecaster/design/design.go @@ -9,6 +9,7 @@ import ( var _ = API("Weather Service API", func() { Title("The Weather Service API") Description("A fully instrumented weather service API") + Version("1.0.0") }) var _ = Service("Forecaster", func() { diff --git a/example/weather/services/forecaster/gen/forecaster/client.go b/example/weather/services/forecaster/gen/forecaster/client.go index de3bd3f9..cca41925 100644 --- a/example/weather/services/forecaster/gen/forecaster/client.go +++ b/example/weather/services/forecaster/gen/forecaster/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster client // diff --git a/example/weather/services/forecaster/gen/forecaster/endpoints.go b/example/weather/services/forecaster/gen/forecaster/endpoints.go index e8c28726..c5149312 100644 --- a/example/weather/services/forecaster/gen/forecaster/endpoints.go +++ b/example/weather/services/forecaster/gen/forecaster/endpoints.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster endpoints // diff --git a/example/weather/services/forecaster/gen/forecaster/service.go b/example/weather/services/forecaster/gen/forecaster/service.go index 23c8995d..59e73113 100644 --- a/example/weather/services/forecaster/gen/forecaster/service.go +++ b/example/weather/services/forecaster/gen/forecaster/service.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster service // @@ -23,6 +23,9 @@ type Service interface { // key. const ServiceName = "Forecaster" +// APIVersion is the version of the API as defined in the design. +const APIVersion = "1.0.0" + // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. diff --git a/example/weather/services/forecaster/gen/grpc/cli/weather_service_api/cli.go b/example/weather/services/forecaster/gen/grpc/cli/weather_service_api/cli.go index 3b2a26c1..24ea3ad7 100644 --- a/example/weather/services/forecaster/gen/grpc/cli/weather_service_api/cli.go +++ b/example/weather/services/forecaster/gen/grpc/cli/weather_service_api/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Weather Service API gRPC client CLI support package // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/client/cli.go b/example/weather/services/forecaster/gen/grpc/forecaster/client/cli.go index 586d9ef8..0edca659 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/client/cli.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/client/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC client CLI support package // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/client/client.go b/example/weather/services/forecaster/gen/grpc/forecaster/client/client.go index c05d4235..1cf9d4e5 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/client/client.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/client/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC client // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/client/encode_decode.go b/example/weather/services/forecaster/gen/grpc/forecaster/client/encode_decode.go index 6ffdb13c..3469bc8b 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/client/encode_decode.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/client/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC client encoders and decoders // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/client/types.go b/example/weather/services/forecaster/gen/grpc/forecaster/client/types.go index cab4ad9f..d1e24c5d 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/client/types.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/client/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC client types // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.pb.go b/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.pb.go index 829c2e86..ef302b4e 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.pb.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.pb.go @@ -1,4 +1,4 @@ -// Code generated with goa v3.14.0, DO NOT EDIT. +// Code generated with goa v3.14.2, DO NOT EDIT. // // Forecaster protocol buffer definition // @@ -9,7 +9,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v4.25.1 +// protoc v4.25.0 // source: goagen_forecaster_forecaster.proto package forecasterpb diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.proto b/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.proto index 267db1c6..3157928d 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.proto +++ b/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster.proto @@ -1,4 +1,4 @@ -// Code generated with goa v3.14.0, DO NOT EDIT. +// Code generated with goa v3.14.2, DO NOT EDIT. // // Forecaster protocol buffer definition // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster_grpc.pb.go b/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster_grpc.pb.go index 5e4d5917..4069f7d2 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster_grpc.pb.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/pb/goagen_forecaster_forecaster_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v4.25.1 +// - protoc v4.25.0 // source: goagen_forecaster_forecaster.proto package forecasterpb diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/server/encode_decode.go b/example/weather/services/forecaster/gen/grpc/forecaster/server/encode_decode.go index 8a28011e..2c49d80a 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/server/encode_decode.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/server/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC server encoders and decoders // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/server/server.go b/example/weather/services/forecaster/gen/grpc/forecaster/server/server.go index 48389d80..35aa117c 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/server/server.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/server/server.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC server // diff --git a/example/weather/services/forecaster/gen/grpc/forecaster/server/types.go b/example/weather/services/forecaster/gen/grpc/forecaster/server/types.go index c911dd7d..d345a11b 100644 --- a/example/weather/services/forecaster/gen/grpc/forecaster/server/types.go +++ b/example/weather/services/forecaster/gen/grpc/forecaster/server/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Forecaster gRPC server types // diff --git a/example/weather/services/front/cmd/front/main.go b/example/weather/services/front/cmd/front/main.go index c8d30173..d66a8431 100644 --- a/example/weather/services/front/cmd/front/main.go +++ b/example/weather/services/front/cmd/front/main.go @@ -11,13 +11,15 @@ import ( "syscall" "time" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "goa.design/clue/clue" "goa.design/clue/debug" "goa.design/clue/health" "goa.design/clue/log" - "goa.design/clue/metrics" - "goa.design/clue/trace" goahttp "goa.design/goa/v3/http" - goahttpmiddleware "goa.design/goa/v3/http/middleware" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -37,11 +39,9 @@ func main() { forecasterHealthAddr = flag.String("forecaster-health-addr", ":8081", "Forecaster service health-check address") locatorAddr = flag.String("locator-addr", ":8082", "Locator service address") locatorHealthAddr = flag.String("locator-health-addr", ":8083", "Locator service health-check address") + coladdr = flag.String("otel-addr", ":4317", "OpenTelemtry collector listen address") + debugf = flag.Bool("debug", false, "Enable debug logs") testerAddr = flag.String("tester-addr", ":8090", "Tester service address") - // No testerHealthAddr because we don't want the whole system to die just because tester isn't healthy for some reason - agentaddr = flag.String("agent-addr", ":4317", "Grafana agent listen address") - debugf = flag.Bool("debug", false, "Enable debug logs") - monitoringEnabled = flag.Bool("monitoring-enabled", true, "monitoring") ) flag.Parse() @@ -50,70 +50,68 @@ func main() { if log.IsTerminal() { format = log.FormatTerminal } - ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(trace.Log)) - ctx = log.With(ctx, log.KV{K: "svc", V: genfront.ServiceName}) + ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(log.Span)) if *debugf { ctx = log.Context(ctx, log.WithDebug()) log.Debugf(ctx, "debug logs enabled") } - // 2. Setup tracing - if !*monitoringEnabled { - var err error - if ctx, err = trace.Context(ctx, genfront.ServiceName, trace.WithDisabled()); err != nil { - log.Error(ctx, err, log.KV{K: "msg", V: "failed to initialize tracing"}) - } - } else { - log.Debugf(ctx, "connecting to Grafana agent %s", *agentaddr) - conn, err := grpc.DialContext(ctx, *agentaddr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock()) - if err != nil { - log.Errorf(ctx, err, "failed to connect to Grafana agent") - os.Exit(1) - } - log.Debugf(ctx, "connected to Grafana agent %s", *agentaddr) - ctx, err = trace.Context(ctx, genfront.ServiceName, trace.WithGRPCExporter(conn)) - if err != nil { - log.Errorf(ctx, err, "failed to initialize tracing") - os.Exit(1) + // 2. Setup instrumentation + spanExporter, err := otlptracegrpc.New(ctx, + otlptracegrpc.WithEndpoint(*coladdr), + otlptracegrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize tracing") + } + defer func() { + if err := spanExporter.Shutdown(ctx); err != nil { + log.Errorf(ctx, err, "failed to shutdown tracing") } + }() + metricExporter, err := otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithEndpoint(*coladdr), + otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize metrics") } - - // 3. Setup metrics - mux := goahttp.NewMuxer() - ctx = metrics.Context(ctx, genfront.ServiceName, - metrics.WithRouteResolver(func(r *http.Request) string { - return mux.ResolvePattern(r) - }), + defer func() { + if err := metricExporter.Shutdown(ctx); err != nil { + log.Errorf(ctx, err, "failed to shutdown metrics") + } + }() + cfg, err := clue.NewConfig(ctx, + genfront.ServiceName, + genfront.APIVersion, + metricExporter, + spanExporter, ) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize instrumentation") + } + clue.ConfigureOpenTelemetry(ctx, cfg) // 3. Create clients - lcc, err := grpc.DialContext(ctx, *locatorAddr, + lcc, err := grpc.DialContext(ctx, + *locatorAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithChainUnaryInterceptor( - trace.UnaryClientInterceptor(ctx), - log.UnaryClientInterceptor())) + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { - log.Errorf(ctx, err, "failed to connect to locator") - os.Exit(1) + log.Fatalf(ctx, err, "failed to connect to locator") } lc := locator.New(lcc) fcc, err := grpc.DialContext(ctx, *forecasterAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithChainUnaryInterceptor( - trace.UnaryClientInterceptor(ctx), - log.UnaryClientInterceptor())) + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { - log.Errorf(ctx, err, "failed to connect to forecast") - os.Exit(1) + log.Fatalf(ctx, err, "failed to connect to forecast") } fc := forecaster.New(fcc) tcc, err := grpc.DialContext(ctx, *testerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithChainUnaryInterceptor( - trace.UnaryClientInterceptor(ctx), - log.UnaryClientInterceptor())) + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { log.Errorf(ctx, err, "failed to connect to tester") os.Exit(1) @@ -127,12 +125,12 @@ func main() { endpoints.Use(log.Endpoint) // 5. Create transport - mux.Use(metrics.HTTP(ctx)) + mux := goahttp.NewMuxer() debug.MountDebugLogEnabler(debug.Adapt(mux)) - handler := trace.HTTP(ctx)(mux) // 4. Trace request - handler = goahttpmiddleware.LogContext(log.AsGoaMiddlewareLogger)(handler) // 3. Log request and response - handler = log.HTTP(ctx)(handler) // 2. Add logger to request context (with request ID key) - handler = goahttpmiddleware.RequestID()(handler) // 1. Add request ID to context + debug.MountPprofHandlers(debug.Adapt(mux)) + handler := otelhttp.NewHandler(mux, genfront.ServiceName) // 3. Add OpenTelemetry instrumentation + handler = debug.HTTP()(handler) // 2. Add debug endpoints + handler = log.HTTP(ctx)(handler) // 1. Add logger to request context server := genhttp.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil) genhttp.Mount(mux, server) for _, m := range server.Mounts { @@ -146,10 +144,9 @@ func main() { check := health.Handler(health.NewChecker( health.NewPinger("locator", *locatorHealthAddr), health.NewPinger("forecaster", *forecasterHealthAddr))) - check = log.HTTP(ctx)(check).(http.HandlerFunc) + check = log.HTTP(ctx)(check).(http.HandlerFunc) // Log health-check errors http.Handle("/healthz", check) http.Handle("/livez", check) - http.Handle("/metrics", metrics.Handler(ctx).(http.HandlerFunc)) metricsServer := &http.Server{Addr: *metricsListenAddr} // 7. Start HTTP server diff --git a/example/weather/services/front/design/design.go b/example/weather/services/front/design/design.go index 160c5b5b..3910c31b 100644 --- a/example/weather/services/front/design/design.go +++ b/example/weather/services/front/design/design.go @@ -9,10 +9,12 @@ import ( var _ = API("Weather", func() { Title("Weather Forecast Service API") Description("The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station.") + Version("1.0.0") }) var _ = Service("front", func() { Description("Public HTTP frontend") + Method("forecast", func() { Description("Retrieve weather forecast for given IP") Payload(String, func() { @@ -25,6 +27,7 @@ var _ = Service("front", func() { Response("not_usa", StatusBadRequest) }) }) + Method("test_all", func() { Description("Endpoint for running ALL API Integration Tests for the Weather System, allowing for filtering on included or excluded tests") Payload(func() { @@ -36,6 +39,7 @@ var _ = Service("front", func() { POST("/tester/all") }) }) + Method("test_smoke", func() { Description("Endpoint for running API Integration Tests' Smoke Tests ONLY for the Weather System") Result(TestResults) @@ -44,3 +48,53 @@ var _ = Service("front", func() { }) }) }) + +var TestResult = Type("TestResult", func() { + Description("Test result for a single test") + Field(1, "name", String, "Name of the test", func() { + Example("TestValidIP") + }) + Field(2, "passed", Boolean, "Status of the test", func() { + Example(true) + }) + Field(3, "error", String, "Error message if the test failed", func() { + Example("error getting location for valid ip: %v") + }) + Field(4, "duration", Int64, "Duration of the test in ms", func() { + Example(1234) + }) + Required("name", "passed", "duration") +}) + +var TestCollection = Type("TestCollection", func() { + Description("Collection of test results for grouping by service") + Field(1, "name", String, "Name of the test collection", func() { + Example("Locator Tests") + }) + Field(2, "results", ArrayOf(TestResult), "Test results") + Field(3, "duration", Int64, "Duration of the tests in ms", func() { + Example(1234) + }) + Field(4, "pass_count", Int, "Number of tests that passed", func() { + Example(12) + }) + Field(5, "fail_count", Int, "Number of tests that failed", func() { + Example(1) + }) + Required("name", "duration", "pass_count", "fail_count") +}) + +var TestResults = Type("TestResults", func() { + Description("Test results for the integration tests") + Field(1, "collections", ArrayOf(TestCollection), "Test collections") + Field(2, "duration", Int64, "Duration of the tests in ms", func() { + Example(1234) + }) + Field(3, "pass_count", Int, "Number of tests that passed", func() { + Example(12) + }) + Field(4, "fail_count", Int, "Number of tests that failed", func() { + Example(1) + }) + Required("collections", "duration", "pass_count", "fail_count") +}) diff --git a/example/weather/services/front/design/tester_results.go b/example/weather/services/front/design/tester_results.go deleted file mode 100644 index a002db16..00000000 --- a/example/weather/services/front/design/tester_results.go +++ /dev/null @@ -1,55 +0,0 @@ -package design - -import ( - . "goa.design/goa/v3/dsl" -) - -var TestResult = Type("TestResult", func() { - Description("Test result for a single test") - Field(1, "name", String, "Name of the test", func() { - Example("TestValidIP") - }) - Field(2, "passed", Boolean, "Status of the test", func() { - Example(true) - }) - Field(3, "error", String, "Error message if the test failed", func() { - Example("error getting location for valid ip: %v") - }) - Field(4, "duration", Int64, "Duration of the test in ms", func() { - Example(1234) - }) - Required("name", "passed", "duration") -}) - -var TestCollection = Type("TestCollection", func() { - Description("Collection of test results for grouping by service") - Field(1, "name", String, "Name of the test collection", func() { - Example("Locator Tests") - }) - Field(2, "results", ArrayOf(TestResult), "Test results") - Field(3, "duration", Int64, "Duration of the tests in ms", func() { - Example(1234) - }) - Field(4, "pass_count", Int, "Number of tests that passed", func() { - Example(12) - }) - Field(5, "fail_count", Int, "Number of tests that failed", func() { - Example(1) - }) - Required("name", "duration", "pass_count", "fail_count") -}) - -var TestResults = Type("TestResults", func() { - Description("Test results for the integration tests") - Field(1, "collections", ArrayOf(TestCollection), "Test collections") - Field(2, "duration", Int64, "Duration of the tests in ms", func() { - Example(1234) - }) - Field(3, "pass_count", Int, "Number of tests that passed", func() { - Example(12) - }) - Field(4, "fail_count", Int, "Number of tests that failed", func() { - Example(1) - }) - Required("collections", "duration", "pass_count", "fail_count") -}) diff --git a/example/weather/services/front/gen/front/client.go b/example/weather/services/front/gen/front/client.go index 861d4373..d20f1060 100644 --- a/example/weather/services/front/gen/front/client.go +++ b/example/weather/services/front/gen/front/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front client // diff --git a/example/weather/services/front/gen/front/endpoints.go b/example/weather/services/front/gen/front/endpoints.go index 536a1824..cb01e9dd 100644 --- a/example/weather/services/front/gen/front/endpoints.go +++ b/example/weather/services/front/gen/front/endpoints.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front endpoints // diff --git a/example/weather/services/front/gen/front/service.go b/example/weather/services/front/gen/front/service.go index 142fadd1..b65c1de9 100644 --- a/example/weather/services/front/gen/front/service.go +++ b/example/weather/services/front/gen/front/service.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front service // @@ -31,6 +31,9 @@ type Service interface { // key. const ServiceName = "front" +// APIVersion is the version of the API as defined in the design. +const APIVersion = "1.0.0" + // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. diff --git a/example/weather/services/front/gen/http/cli/weather/cli.go b/example/weather/services/front/gen/http/cli/weather/cli.go index bea39597..3bb61d9b 100644 --- a/example/weather/services/front/gen/http/cli/weather/cli.go +++ b/example/weather/services/front/gen/http/cli/weather/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Weather HTTP client CLI support package // diff --git a/example/weather/services/front/gen/http/front/client/cli.go b/example/weather/services/front/gen/http/front/client/cli.go index 3cfeb481..ee36ef6c 100644 --- a/example/weather/services/front/gen/http/front/client/cli.go +++ b/example/weather/services/front/gen/http/front/client/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front HTTP client CLI support package // diff --git a/example/weather/services/front/gen/http/front/client/client.go b/example/weather/services/front/gen/http/front/client/client.go index 1ea8373f..6b7a0177 100644 --- a/example/weather/services/front/gen/http/front/client/client.go +++ b/example/weather/services/front/gen/http/front/client/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front client HTTP transport // diff --git a/example/weather/services/front/gen/http/front/client/encode_decode.go b/example/weather/services/front/gen/http/front/client/encode_decode.go index e7ef4462..961c13e9 100644 --- a/example/weather/services/front/gen/http/front/client/encode_decode.go +++ b/example/weather/services/front/gen/http/front/client/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front HTTP client encoders and decoders // diff --git a/example/weather/services/front/gen/http/front/client/paths.go b/example/weather/services/front/gen/http/front/client/paths.go index 68df4ad9..aaba626d 100644 --- a/example/weather/services/front/gen/http/front/client/paths.go +++ b/example/weather/services/front/gen/http/front/client/paths.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // HTTP request path constructors for the front service. // diff --git a/example/weather/services/front/gen/http/front/client/types.go b/example/weather/services/front/gen/http/front/client/types.go index dbef249e..86640fba 100644 --- a/example/weather/services/front/gen/http/front/client/types.go +++ b/example/weather/services/front/gen/http/front/client/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front HTTP client types // diff --git a/example/weather/services/front/gen/http/front/server/encode_decode.go b/example/weather/services/front/gen/http/front/server/encode_decode.go index aea1df2b..fe7a0c49 100644 --- a/example/weather/services/front/gen/http/front/server/encode_decode.go +++ b/example/weather/services/front/gen/http/front/server/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front HTTP server encoders and decoders // diff --git a/example/weather/services/front/gen/http/front/server/paths.go b/example/weather/services/front/gen/http/front/server/paths.go index 628000c6..99ae705b 100644 --- a/example/weather/services/front/gen/http/front/server/paths.go +++ b/example/weather/services/front/gen/http/front/server/paths.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // HTTP request path constructors for the front service. // diff --git a/example/weather/services/front/gen/http/front/server/server.go b/example/weather/services/front/gen/http/front/server/server.go index 6170bfe8..cc5b2320 100644 --- a/example/weather/services/front/gen/http/front/server/server.go +++ b/example/weather/services/front/gen/http/front/server/server.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front HTTP server // diff --git a/example/weather/services/front/gen/http/front/server/types.go b/example/weather/services/front/gen/http/front/server/types.go index ab57d794..3f8efc27 100644 --- a/example/weather/services/front/gen/http/front/server/types.go +++ b/example/weather/services/front/gen/http/front/server/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // front HTTP server types // diff --git a/example/weather/services/front/gen/http/openapi.json b/example/weather/services/front/gen/http/openapi.json index 5613fd41..518e482e 100644 --- a/example/weather/services/front/gen/http/openapi.json +++ b/example/weather/services/front/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Weather Forecast Service API","description":"The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station.","version":""},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/forecast/{ip}":{"get":{"tags":["front"],"summary":"forecast front","description":"Retrieve weather forecast for given IP","operationId":"front#forecast","parameters":[{"name":"ip","in":"path","required":true,"type":"string","format":"ip"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/FrontForecastResponseBody","required":["location","periods"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/FrontForecastNotUsaResponseBody"}}},"schemes":["http"]}},"/tester/all":{"post":{"tags":["front"],"summary":"test_all front","description":"Endpoint for running ALL API Integration Tests for the Weather System, allowing for filtering on included or excluded tests","operationId":"front#test_all","parameters":[{"name":"test_all_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/FrontTestAllRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/FrontTestAllResponseBody","required":["collections","duration","pass_count","fail_count"]}}},"schemes":["http"]}},"/tester/smoke":{"post":{"tags":["front"],"summary":"test_smoke front","description":"Endpoint for running API Integration Tests' Smoke Tests ONLY for the Weather System","operationId":"front#test_smoke","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/FrontTestSmokeResponseBody","required":["collections","duration","pass_count","fail_count"]}}},"schemes":["http"]}}},"definitions":{"FrontForecastNotUsaResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"IP address is not in the US (default view)","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"FrontForecastResponseBody":{"title":"FrontForecastResponseBody","type":"object","properties":{"location":{"$ref":"#/definitions/LocationResponseBody"},"periods":{"type":"array","items":{"$ref":"#/definitions/PeriodResponseBody"},"description":"Weather forecast periods","example":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]}},"example":{"location":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"periods":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]},"required":["location","periods"]},"FrontTestAllRequestBody":{"title":"FrontTestAllRequestBody","type":"object","properties":{"exclude":{"type":"array","items":{"type":"string","example":"Eos nihil accusamus reiciendis eligendi amet aut."},"description":"Tests to exclude","example":["Hic aperiam.","Assumenda commodi aut facilis."]},"include":{"type":"array","items":{"type":"string","example":"Corrupti dignissimos id necessitatibus consequatur a laudantium."},"description":"Tests to run","example":["Nihil voluptatum dolorem nam quisquam.","Incidunt ipsa natus."]}},"example":{"exclude":["Asperiores est.","Omnis vel quia ab dolorem qui.","Dicta fuga praesentium.","Est sit quis."],"include":["Aspernatur voluptatem et placeat deserunt.","Temporibus veritatis vel.","Voluptatem ab nulla ad in optio sed.","Tempora tenetur."]}},"FrontTestAllResponseBody":{"title":"FrontTestAllResponseBody","type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/definitions/TestCollectionResponseBody"},"description":"Test collections","example":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}]},"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"}},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12},"required":["collections","duration","pass_count","fail_count"]},"FrontTestSmokeResponseBody":{"title":"FrontTestSmokeResponseBody","type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/definitions/TestCollectionResponseBody"},"description":"Test collections","example":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}]},"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"}},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12},"required":["collections","duration","pass_count","fail_count"]},"LocationResponseBody":{"title":"LocationResponseBody","type":"object","properties":{"city":{"type":"string","description":"City","example":"San Francisco"},"lat":{"type":"number","description":"Latitude","example":37.8267,"format":"double"},"long":{"type":"number","description":"Longitude","example":-122.4233,"format":"double"},"state":{"type":"string","description":"State","example":"CA"}},"description":"Geographical location","example":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"required":["lat","long","city","state"]},"PeriodResponseBody":{"title":"PeriodResponseBody","type":"object","properties":{"endTime":{"type":"string","description":"End time","example":"2020-01-01T00:00:00Z","format":"date-time"},"name":{"type":"string","description":"Period name","example":"Morning"},"startTime":{"type":"string","description":"Start time","example":"2020-01-01T00:00:00Z","format":"date-time"},"summary":{"type":"string","description":"Summary","example":"Clear"},"temperature":{"type":"integer","description":"Temperature","example":70,"format":"int64"},"temperatureUnit":{"type":"string","description":"Temperature unit","example":"F"}},"description":"Weather forecast period","example":{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},"required":["name","startTime","endTime","temperature","temperatureUnit","summary"]},"TestCollectionResponseBody":{"title":"TestCollectionResponseBody","type":"object","properties":{"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"name":{"type":"string","description":"Name of the test collection","example":"Locator Tests"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"},"results":{"type":"array","items":{"$ref":"#/definitions/TestResultResponseBody"},"description":"Test results","example":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}},"description":"Collection of test results for grouping by service","example":{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},"required":["name","duration","pass_count","fail_count"]},"TestResultResponseBody":{"title":"TestResultResponseBody","type":"object","properties":{"duration":{"type":"integer","description":"Duration of the test in ms","example":1234,"format":"int64"},"error":{"type":"string","description":"Error message if the test failed","example":"error getting location for valid ip: %v"},"name":{"type":"string","description":"Name of the test","example":"TestValidIP"},"passed":{"type":"boolean","description":"Status of the test","example":true}},"description":"Test result for a single test","example":{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},"required":["name","passed","duration"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Weather Forecast Service API","description":"The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station.","version":"1.0.0"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/forecast/{ip}":{"get":{"tags":["front"],"summary":"forecast front","description":"Retrieve weather forecast for given IP","operationId":"front#forecast","parameters":[{"name":"ip","in":"path","required":true,"type":"string","format":"ip"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/FrontForecastResponseBody","required":["location","periods"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/FrontForecastNotUsaResponseBody"}}},"schemes":["http"]}},"/tester/all":{"post":{"tags":["front"],"summary":"test_all front","description":"Endpoint for running ALL API Integration Tests for the Weather System, allowing for filtering on included or excluded tests","operationId":"front#test_all","parameters":[{"name":"test_all_request_body","in":"body","required":true,"schema":{"$ref":"#/definitions/FrontTestAllRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/FrontTestAllResponseBody","required":["collections","duration","pass_count","fail_count"]}}},"schemes":["http"]}},"/tester/smoke":{"post":{"tags":["front"],"summary":"test_smoke front","description":"Endpoint for running API Integration Tests' Smoke Tests ONLY for the Weather System","operationId":"front#test_smoke","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/FrontTestSmokeResponseBody","required":["collections","duration","pass_count","fail_count"]}}},"schemes":["http"]}}},"definitions":{"FrontForecastNotUsaResponseBody":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":true}},"description":"IP address is not in the US (default view)","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]},"FrontForecastResponseBody":{"title":"FrontForecastResponseBody","type":"object","properties":{"location":{"$ref":"#/definitions/LocationResponseBody"},"periods":{"type":"array","items":{"$ref":"#/definitions/PeriodResponseBody"},"description":"Weather forecast periods","example":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]}},"example":{"location":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"periods":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]},"required":["location","periods"]},"FrontTestAllRequestBody":{"title":"FrontTestAllRequestBody","type":"object","properties":{"exclude":{"type":"array","items":{"type":"string","example":"Eos nihil accusamus reiciendis eligendi amet aut."},"description":"Tests to exclude","example":["Hic aperiam.","Assumenda commodi aut facilis."]},"include":{"type":"array","items":{"type":"string","example":"Corrupti dignissimos id necessitatibus consequatur a laudantium."},"description":"Tests to run","example":["Nihil voluptatum dolorem nam quisquam.","Incidunt ipsa natus."]}},"example":{"exclude":["Asperiores est.","Omnis vel quia ab dolorem qui.","Dicta fuga praesentium.","Est sit quis."],"include":["Aspernatur voluptatem et placeat deserunt.","Temporibus veritatis vel.","Voluptatem ab nulla ad in optio sed.","Tempora tenetur."]}},"FrontTestAllResponseBody":{"title":"FrontTestAllResponseBody","type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/definitions/TestCollectionResponseBody"},"description":"Test collections","example":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}]},"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"}},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12},"required":["collections","duration","pass_count","fail_count"]},"FrontTestSmokeResponseBody":{"title":"FrontTestSmokeResponseBody","type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/definitions/TestCollectionResponseBody"},"description":"Test collections","example":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}]},"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"}},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12},"required":["collections","duration","pass_count","fail_count"]},"LocationResponseBody":{"title":"LocationResponseBody","type":"object","properties":{"city":{"type":"string","description":"City","example":"San Francisco"},"lat":{"type":"number","description":"Latitude","example":37.8267,"format":"double"},"long":{"type":"number","description":"Longitude","example":-122.4233,"format":"double"},"state":{"type":"string","description":"State","example":"CA"}},"description":"Geographical location","example":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"required":["lat","long","city","state"]},"PeriodResponseBody":{"title":"PeriodResponseBody","type":"object","properties":{"endTime":{"type":"string","description":"End time","example":"2020-01-01T00:00:00Z","format":"date-time"},"name":{"type":"string","description":"Period name","example":"Morning"},"startTime":{"type":"string","description":"Start time","example":"2020-01-01T00:00:00Z","format":"date-time"},"summary":{"type":"string","description":"Summary","example":"Clear"},"temperature":{"type":"integer","description":"Temperature","example":70,"format":"int64"},"temperatureUnit":{"type":"string","description":"Temperature unit","example":"F"}},"description":"Weather forecast period","example":{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},"required":["name","startTime","endTime","temperature","temperatureUnit","summary"]},"TestCollectionResponseBody":{"title":"TestCollectionResponseBody","type":"object","properties":{"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"name":{"type":"string","description":"Name of the test collection","example":"Locator Tests"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"},"results":{"type":"array","items":{"$ref":"#/definitions/TestResultResponseBody"},"description":"Test results","example":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}},"description":"Collection of test results for grouping by service","example":{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},"required":["name","duration","pass_count","fail_count"]},"TestResultResponseBody":{"title":"TestResultResponseBody","type":"object","properties":{"duration":{"type":"integer","description":"Duration of the test in ms","example":1234,"format":"int64"},"error":{"type":"string","description":"Error message if the test failed","example":"error getting location for valid ip: %v"},"name":{"type":"string","description":"Name of the test","example":"TestValidIP"},"passed":{"type":"boolean","description":"Status of the test","example":true}},"description":"Test result for a single test","example":{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},"required":["name","passed","duration"]}}} \ No newline at end of file diff --git a/example/weather/services/front/gen/http/openapi.yaml b/example/weather/services/front/gen/http/openapi.yaml index 81ed6922..4b85cb24 100644 --- a/example/weather/services/front/gen/http/openapi.yaml +++ b/example/weather/services/front/gen/http/openapi.yaml @@ -2,7 +2,7 @@ swagger: "2.0" info: title: Weather Forecast Service API description: The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station. - version: "" + version: 1.0.0 host: localhost:80 consumes: - application/json diff --git a/example/weather/services/front/gen/http/openapi3.json b/example/weather/services/front/gen/http/openapi3.json index f4e14180..27c175de 100644 --- a/example/weather/services/front/gen/http/openapi3.json +++ b/example/weather/services/front/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Weather Forecast Service API","description":"The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station.","version":"1.0"},"servers":[{"url":"http://localhost:80","description":"Default server for Weather"}],"paths":{"/forecast/{ip}":{"get":{"tags":["front"],"summary":"forecast front","description":"Retrieve weather forecast for given IP","operationId":"front#forecast","parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string","example":"137.67.93.10","format":"ip"},"example":"57.127.156.27"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forecast"},"example":{"location":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"periods":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]}}}},"400":{"description":"not_usa: IP address is not in the US","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tester/all":{"post":{"tags":["front"],"summary":"test_all front","description":"Endpoint for running ALL API Integration Tests for the Weather System, allowing for filtering on included or excluded tests","operationId":"front#test_all","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestAllRequestBody"},"example":{"exclude":["Repudiandae enim molestiae.","Ut id iure."],"include":["Dolor sunt maiores.","Asperiores omnis ducimus ad et mollitia."]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestResults"},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12}}}}}}},"/tester/smoke":{"post":{"tags":["front"],"summary":"test_smoke front","description":"Endpoint for running API Integration Tests' Smoke Tests ONLY for the Weather System","operationId":"front#test_smoke","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestResults"},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"IP address is not in the US","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"Forecast":{"type":"object","properties":{"location":{"$ref":"#/components/schemas/Location"},"periods":{"type":"array","items":{"$ref":"#/components/schemas/Period"},"description":"Weather forecast periods","example":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]}},"example":{"location":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"periods":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]},"required":["location","periods"]},"Location":{"type":"object","properties":{"city":{"type":"string","description":"City","example":"San Francisco"},"lat":{"type":"number","description":"Latitude","example":37.8267,"format":"double"},"long":{"type":"number","description":"Longitude","example":-122.4233,"format":"double"},"state":{"type":"string","description":"State","example":"CA"}},"description":"Geographical location","example":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"required":["lat","long","city","state"]},"Period":{"type":"object","properties":{"endTime":{"type":"string","description":"End time","example":"2020-01-01T00:00:00Z","format":"date-time"},"name":{"type":"string","description":"Period name","example":"Morning"},"startTime":{"type":"string","description":"Start time","example":"2020-01-01T00:00:00Z","format":"date-time"},"summary":{"type":"string","description":"Summary","example":"Clear"},"temperature":{"type":"integer","description":"Temperature","example":70,"format":"int64"},"temperatureUnit":{"type":"string","description":"Temperature unit","example":"F"}},"description":"Weather forecast period","example":{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},"required":["name","startTime","endTime","temperature","temperatureUnit","summary"]},"TestAllRequestBody":{"type":"object","properties":{"exclude":{"type":"array","items":{"type":"string","example":"Cupiditate voluptas quod minima."},"description":"Tests to exclude","example":["Vitae ut.","Non officiis asperiores rem."]},"include":{"type":"array","items":{"type":"string","example":"Sed officia cum."},"description":"Tests to run","example":["Temporibus ex quo nihil rem.","Et et itaque."]}},"example":{"exclude":["Architecto minima molestiae ad unde.","Porro possimus iste eius minus nesciunt.","Dolores ducimus."],"include":["Architecto aperiam odit.","Iste eveniet."]}},"TestCollection":{"type":"object","properties":{"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"name":{"type":"string","description":"Name of the test collection","example":"Locator Tests"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/TestResult"},"description":"Test results","example":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}},"description":"Collection of test results for grouping by service","example":{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},"required":["name","duration","pass_count","fail_count"]},"TestResult":{"type":"object","properties":{"duration":{"type":"integer","description":"Duration of the test in ms","example":1234,"format":"int64"},"error":{"type":"string","description":"Error message if the test failed","example":"error getting location for valid ip: %v"},"name":{"type":"string","description":"Name of the test","example":"TestValidIP"},"passed":{"type":"boolean","description":"Status of the test","example":true}},"description":"Test result for a single test","example":{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},"required":["name","passed","duration"]},"TestResults":{"type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/TestCollection"},"description":"Test collections","example":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}]},"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"}},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12},"required":["collections","duration","pass_count","fail_count"]}}},"tags":[{"name":"front","description":"Public HTTP frontend"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Weather Forecast Service API","description":"The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station.","version":"1.0.0"},"servers":[{"url":"http://localhost:80","description":"Default server for Weather"}],"paths":{"/forecast/{ip}":{"get":{"tags":["front"],"summary":"forecast front","description":"Retrieve weather forecast for given IP","operationId":"front#forecast","parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string","example":"137.67.93.10","format":"ip"},"example":"57.127.156.27"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forecast"},"example":{"location":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"periods":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]}}}},"400":{"description":"not_usa: IP address is not in the US","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tester/all":{"post":{"tags":["front"],"summary":"test_all front","description":"Endpoint for running ALL API Integration Tests for the Weather System, allowing for filtering on included or excluded tests","operationId":"front#test_all","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestAllRequestBody"},"example":{"exclude":["Repudiandae enim molestiae.","Ut id iure."],"include":["Dolor sunt maiores.","Asperiores omnis ducimus ad et mollitia."]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestResults"},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12}}}}}}},"/tester/smoke":{"post":{"tags":["front"],"summary":"test_smoke front","description":"Endpoint for running API Integration Tests' Smoke Tests ONLY for the Weather System","operationId":"front#test_smoke","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestResults"},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"description":"IP address is not in the US","example":{"fault":false,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":false},"required":["name","id","message","temporary","timeout","fault"]},"Forecast":{"type":"object","properties":{"location":{"$ref":"#/components/schemas/Location"},"periods":{"type":"array","items":{"$ref":"#/components/schemas/Period"},"description":"Weather forecast periods","example":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]}},"example":{"location":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"periods":[{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"}]},"required":["location","periods"]},"Location":{"type":"object","properties":{"city":{"type":"string","description":"City","example":"San Francisco"},"lat":{"type":"number","description":"Latitude","example":37.8267,"format":"double"},"long":{"type":"number","description":"Longitude","example":-122.4233,"format":"double"},"state":{"type":"string","description":"State","example":"CA"}},"description":"Geographical location","example":{"city":"San Francisco","lat":37.8267,"long":-122.4233,"state":"CA"},"required":["lat","long","city","state"]},"Period":{"type":"object","properties":{"endTime":{"type":"string","description":"End time","example":"2020-01-01T00:00:00Z","format":"date-time"},"name":{"type":"string","description":"Period name","example":"Morning"},"startTime":{"type":"string","description":"Start time","example":"2020-01-01T00:00:00Z","format":"date-time"},"summary":{"type":"string","description":"Summary","example":"Clear"},"temperature":{"type":"integer","description":"Temperature","example":70,"format":"int64"},"temperatureUnit":{"type":"string","description":"Temperature unit","example":"F"}},"description":"Weather forecast period","example":{"endTime":"2020-01-01T00:00:00Z","name":"Morning","startTime":"2020-01-01T00:00:00Z","summary":"Clear","temperature":70,"temperatureUnit":"F"},"required":["name","startTime","endTime","temperature","temperatureUnit","summary"]},"TestAllRequestBody":{"type":"object","properties":{"exclude":{"type":"array","items":{"type":"string","example":"Cupiditate voluptas quod minima."},"description":"Tests to exclude","example":["Vitae ut.","Non officiis asperiores rem."]},"include":{"type":"array","items":{"type":"string","example":"Sed officia cum."},"description":"Tests to run","example":["Temporibus ex quo nihil rem.","Et et itaque."]}},"example":{"exclude":["Architecto minima molestiae ad unde.","Porro possimus iste eius minus nesciunt.","Dolores ducimus."],"include":["Architecto aperiam odit.","Iste eveniet."]}},"TestCollection":{"type":"object","properties":{"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"name":{"type":"string","description":"Name of the test collection","example":"Locator Tests"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/TestResult"},"description":"Test results","example":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}},"description":"Collection of test results for grouping by service","example":{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},"required":["name","duration","pass_count","fail_count"]},"TestResult":{"type":"object","properties":{"duration":{"type":"integer","description":"Duration of the test in ms","example":1234,"format":"int64"},"error":{"type":"string","description":"Error message if the test failed","example":"error getting location for valid ip: %v"},"name":{"type":"string","description":"Name of the test","example":"TestValidIP"},"passed":{"type":"boolean","description":"Status of the test","example":true}},"description":"Test result for a single test","example":{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},"required":["name","passed","duration"]},"TestResults":{"type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/TestCollection"},"description":"Test collections","example":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}]},"duration":{"type":"integer","description":"Duration of the tests in ms","example":1234,"format":"int64"},"fail_count":{"type":"integer","description":"Number of tests that failed","example":1,"format":"int64"},"pass_count":{"type":"integer","description":"Number of tests that passed","example":12,"format":"int64"}},"example":{"collections":[{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]},{"duration":1234,"fail_count":1,"name":"Locator Tests","pass_count":12,"results":[{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true},{"duration":1234,"error":"error getting location for valid ip: %v","name":"TestValidIP","passed":true}]}],"duration":1234,"fail_count":1,"pass_count":12},"required":["collections","duration","pass_count","fail_count"]}}},"tags":[{"name":"front","description":"Public HTTP frontend"}]} \ No newline at end of file diff --git a/example/weather/services/front/gen/http/openapi3.yaml b/example/weather/services/front/gen/http/openapi3.yaml index ff15b025..858866ad 100644 --- a/example/weather/services/front/gen/http/openapi3.yaml +++ b/example/weather/services/front/gen/http/openapi3.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Weather Forecast Service API description: The weather forecast service API produces weather forecasts from US-based IPs. It uses IP location to find the appropriate weather station. - version: "1.0" + version: 1.0.0 servers: - url: http://localhost:80 description: Default server for Weather diff --git a/example/weather/services/locator/cmd/locator/main.go b/example/weather/services/locator/cmd/locator/main.go index 22095635..82872909 100644 --- a/example/weather/services/locator/cmd/locator/main.go +++ b/example/weather/services/locator/cmd/locator/main.go @@ -6,18 +6,22 @@ import ( "fmt" "net" "net/http" + "net/http/httptrace" "os" "os/signal" "sync" "syscall" "time" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "goa.design/clue/clue" "goa.design/clue/debug" "goa.design/clue/health" "goa.design/clue/log" - "goa.design/clue/metrics" - "goa.design/clue/trace" - goagrpcmiddleware "goa.design/goa/v3/grpc/middleware" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/reflection" @@ -31,11 +35,10 @@ import ( func main() { var ( - grpcaddr = flag.String("grpc-addr", ":8082", "gRPC listen address") - httpaddr = flag.String("http-addr", ":8083", "HTTP listen address (health checks and metrics)") - agentaddr = flag.String("agent-addr", ":4317", "Grafana agent listen address") - debugf = flag.Bool("debug", false, "Enable debug logs") - monitoringEnabled = flag.Bool("monitoring-enabled", true, "monitoring") + grpcaddr = flag.String("grpc-addr", ":8082", "gRPC listen address") + httpaddr = flag.String("http-addr", ":8083", "HTTP listen address (health checks and metrics)") + oteladdr = flag.String("otel-addr", ":4317", "OpenTelemetry collector listen address") + debugf = flag.Bool("debug", false, "Enable debug logs") ) flag.Parse() @@ -44,60 +47,72 @@ func main() { if log.IsTerminal() { format = log.FormatTerminal } - ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(trace.Log)) - ctx = log.With(ctx, log.KV{K: "svc", V: genlocator.ServiceName}) + ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(log.Span)) if *debugf { ctx = log.Context(ctx, log.WithDebug()) log.Debugf(ctx, "debug logs enabled") } - // 2. Setup tracing - if !*monitoringEnabled { - var err error - if ctx, err = trace.Context(ctx, genlocator.ServiceName, trace.WithDisabled()); err != nil { - log.Error(ctx, err, log.KV{K: "msg", V: "failed to initialize tracing"}) - } - } else { - log.Debugf(ctx, "connecting to Grafana agent %s", *agentaddr) - conn, err := grpc.DialContext(ctx, *agentaddr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock()) + // 2. Setup instrumentation + spanExporter, err := otlptracegrpc.New(ctx, + otlptracegrpc.WithEndpoint(*oteladdr), + otlptracegrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize tracing") + } + defer func() { + err := spanExporter.Shutdown(ctx) if err != nil { - log.Errorf(ctx, err, "failed to connect to Grafana agent") - os.Exit(1) + log.Errorf(ctx, err, "failed to shutdown tracing") } - log.Debugf(ctx, "connected to Grafana agent %s", *agentaddr) - ctx, err = trace.Context(ctx, genlocator.ServiceName, trace.WithGRPCExporter(conn)) + }() + metricExporter, err := otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithEndpoint(*oteladdr), + otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize metrics") + } + defer func() { + err := metricExporter.Shutdown(ctx) if err != nil { - log.Errorf(ctx, err, "failed to initialize tracing") - os.Exit(1) + log.Errorf(ctx, err, "failed to shutdown metrics") } + }() + cfg, err := clue.NewConfig(ctx, + genlocator.ServiceName, + genlocator.APIVersion, + metricExporter, + spanExporter, + ) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize instrumentation") } - - // 3. Setup metrics - ctx = metrics.Context(ctx, genlocator.ServiceName) - - // 4. Create clients - c := &http.Client{Transport: log.Client(trace.Client(ctx, http.DefaultTransport))} - ipc := ipapi.New(c) - - // 5. Create service & endpoints + clue.ConfigureOpenTelemetry(ctx, cfg) + + // 3. Create clients + httpc := &http.Client{ + Transport: log.Client( + otelhttp.NewTransport( + http.DefaultTransport, + otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { + return otelhttptrace.NewClientTrace(ctx) + }), + ))} + ipc := ipapi.New(httpc) + + // 4. Create service & endpoints svc := locator.New(ipc) endpoints := genlocator.NewEndpoints(svc) endpoints.Use(debug.LogPayloads()) endpoints.Use(log.Endpoint) - // 6. Create transport + // 5. Create transport server := gengrpc.New(endpoints, nil) grpcsvr := grpc.NewServer( grpc.ChainUnaryInterceptor( - goagrpcmiddleware.UnaryRequestID(), - log.UnaryServerInterceptor(ctx), - debug.UnaryServerInterceptor(), - goagrpcmiddleware.UnaryServerLogContext(log.AsGoaMiddlewareLogger), - trace.UnaryServerInterceptor(ctx), - metrics.UnaryServerInterceptor(ctx), - )) + log.UnaryServerInterceptor(ctx), // Add logger to request context and log requests. + debug.UnaryServerInterceptor()), // Enable debug log level control + grpc.StatsHandler(otelgrpc.NewServerHandler())) // Instrument server. genpb.RegisterLocatorServer(grpcsvr, server) reflection.Register(grpcsvr) for svc, info := range grpcsvr.GetServiceInfo() { @@ -106,17 +121,17 @@ func main() { } } - // 7. Setup health check, metrics and debug endpoints - check := log.HTTP(ctx)(health.Handler(health.NewChecker(ipc))) + // 6. Setup health check and debug endpoints mux := http.NewServeMux() debug.MountDebugLogEnabler(mux) debug.MountPprofHandlers(mux) + check := health.Handler(health.NewChecker(ipc)) + check = log.HTTP(ctx)(check).(http.HandlerFunc) // Log health-check errors mux.Handle("/healthz", check) mux.Handle("/livez", check) - mux.Handle("/metrics", metrics.Handler(ctx)) httpsvr := &http.Server{Addr: *httpaddr, Handler: mux} - // 8. Start gRPC and HTTP servers + // 7. Start gRPC and HTTP servers errc := make(chan error) go func() { c := make(chan os.Signal, 1) diff --git a/example/weather/services/locator/design/design.go b/example/weather/services/locator/design/design.go index ad6b1f84..20856a06 100644 --- a/example/weather/services/locator/design/design.go +++ b/example/weather/services/locator/design/design.go @@ -7,6 +7,7 @@ import ( var _ = API("IP Location API", func() { Title("IP Location Service API") Description("A fully instrumented IP location service API") + Version("1.0.0") }) var _ = Service("locator", func() { diff --git a/example/weather/services/locator/gen/grpc/cli/ip_location_api/cli.go b/example/weather/services/locator/gen/grpc/cli/ip_location_api/cli.go index 8763b874..66ffa9af 100644 --- a/example/weather/services/locator/gen/grpc/cli/ip_location_api/cli.go +++ b/example/weather/services/locator/gen/grpc/cli/ip_location_api/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // IP Location API gRPC client CLI support package // diff --git a/example/weather/services/locator/gen/grpc/locator/client/cli.go b/example/weather/services/locator/gen/grpc/locator/client/cli.go index 5d2f177f..31a902b0 100644 --- a/example/weather/services/locator/gen/grpc/locator/client/cli.go +++ b/example/weather/services/locator/gen/grpc/locator/client/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC client CLI support package // diff --git a/example/weather/services/locator/gen/grpc/locator/client/client.go b/example/weather/services/locator/gen/grpc/locator/client/client.go index 6604b57d..f99113f7 100644 --- a/example/weather/services/locator/gen/grpc/locator/client/client.go +++ b/example/weather/services/locator/gen/grpc/locator/client/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC client // diff --git a/example/weather/services/locator/gen/grpc/locator/client/encode_decode.go b/example/weather/services/locator/gen/grpc/locator/client/encode_decode.go index c2540c12..216a8cb5 100644 --- a/example/weather/services/locator/gen/grpc/locator/client/encode_decode.go +++ b/example/weather/services/locator/gen/grpc/locator/client/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC client encoders and decoders // diff --git a/example/weather/services/locator/gen/grpc/locator/client/types.go b/example/weather/services/locator/gen/grpc/locator/client/types.go index 2e747eed..cce8a6f0 100644 --- a/example/weather/services/locator/gen/grpc/locator/client/types.go +++ b/example/weather/services/locator/gen/grpc/locator/client/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC client types // diff --git a/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.pb.go b/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.pb.go index 92fdb0ce..473453b5 100644 --- a/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.pb.go +++ b/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.pb.go @@ -1,4 +1,4 @@ -// Code generated with goa v3.14.0, DO NOT EDIT. +// Code generated with goa v3.14.2, DO NOT EDIT. // // locator protocol buffer definition // @@ -9,7 +9,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v4.25.1 +// protoc v4.25.0 // source: goagen_locator_locator.proto package locatorpb diff --git a/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.proto b/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.proto index 3bcc2db9..b7371287 100644 --- a/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.proto +++ b/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator.proto @@ -1,4 +1,4 @@ -// Code generated with goa v3.14.0, DO NOT EDIT. +// Code generated with goa v3.14.2, DO NOT EDIT. // // locator protocol buffer definition // diff --git a/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator_grpc.pb.go b/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator_grpc.pb.go index 0b00a2c7..8980ca5d 100644 --- a/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator_grpc.pb.go +++ b/example/weather/services/locator/gen/grpc/locator/pb/goagen_locator_locator_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v4.25.1 +// - protoc v4.25.0 // source: goagen_locator_locator.proto package locatorpb diff --git a/example/weather/services/locator/gen/grpc/locator/server/encode_decode.go b/example/weather/services/locator/gen/grpc/locator/server/encode_decode.go index 33a08e1d..cdc5afe4 100644 --- a/example/weather/services/locator/gen/grpc/locator/server/encode_decode.go +++ b/example/weather/services/locator/gen/grpc/locator/server/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC server encoders and decoders // diff --git a/example/weather/services/locator/gen/grpc/locator/server/server.go b/example/weather/services/locator/gen/grpc/locator/server/server.go index 582e34d1..b99f28ea 100644 --- a/example/weather/services/locator/gen/grpc/locator/server/server.go +++ b/example/weather/services/locator/gen/grpc/locator/server/server.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC server // diff --git a/example/weather/services/locator/gen/grpc/locator/server/types.go b/example/weather/services/locator/gen/grpc/locator/server/types.go index 438a092b..1ad3fcb1 100644 --- a/example/weather/services/locator/gen/grpc/locator/server/types.go +++ b/example/weather/services/locator/gen/grpc/locator/server/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator gRPC server types // diff --git a/example/weather/services/locator/gen/locator/client.go b/example/weather/services/locator/gen/locator/client.go index 805c0ae5..9234c426 100644 --- a/example/weather/services/locator/gen/locator/client.go +++ b/example/weather/services/locator/gen/locator/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator client // diff --git a/example/weather/services/locator/gen/locator/endpoints.go b/example/weather/services/locator/gen/locator/endpoints.go index c5ef91ae..458ffe6d 100644 --- a/example/weather/services/locator/gen/locator/endpoints.go +++ b/example/weather/services/locator/gen/locator/endpoints.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator endpoints // diff --git a/example/weather/services/locator/gen/locator/service.go b/example/weather/services/locator/gen/locator/service.go index 08002896..89075b24 100644 --- a/example/weather/services/locator/gen/locator/service.go +++ b/example/weather/services/locator/gen/locator/service.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // locator service // @@ -23,6 +23,9 @@ type Service interface { // key. const ServiceName = "locator" +// APIVersion is the version of the API as defined in the design. +const APIVersion = "1.0.0" + // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. diff --git a/example/weather/services/tester/README.md b/example/weather/services/tester/README.md index daf6dea7..243ce0eb 100644 --- a/example/weather/services/tester/README.md +++ b/example/weather/services/tester/README.md @@ -18,20 +18,20 @@ The Tester service exposes a gRPC API that allows other services (such as the ex ### TestResults return object -`TestResults`: This is the main return object for all test methods in the `tester` service and -it represents the results of the system integration tests. It has four fields: `Collections` -(an array of `TestCollection`), `Duration` (the total duration of all the tests in milliseconds), -`PassCount` (the total number of tests that passed), and `FailCount` (the total number of tests -that failed). - -`TestCollection`: This type represents a collection of test results, typically grouped by service. -It has five fields: `Name` (the name of the test collection), `Results` (an array of `TestResult`), -`Duration` (the total duration of the tests in the `Collection` in milliseconds), `PassCount` (the -number of tests that passed), and `FailCount` (the number of tests that failed). - -`TestResult`: This type represents the result of a single test. It has four fields: `Name` (the name -of the test), `Passed` (a boolean indicating whether the test passed), `Error` (an error message if -the test failed), and `Duration` (the duration of the test in milliseconds). +`TestResults` serves as the key output for the `tester` service's test methods, +encapsulating the system integration test outcomes. It details the collective +test results with fields like `Collections`, an array encompassing various +`TestCollection` instances, the `Duration` for total test time in milliseconds, +and counts of both passed (`PassCount`) and failed tests (`FailCount`). + +Each `TestCollection` within `TestResults` groups test outcomes, often by +service. Its structure includes the collection's Name, an array of individual +`TestResult` items, the total `Duration` for all tests in the collection, and +counts of successful (`PassCount`) and unsuccessful (`FailCount`) tests. + +In `TestResult`, the focus narrows down to individual test performance. This +includes the specific `Name` of the test, a boolean `Passed` status, a potential +`Error` message for failures, and the `Duration` each test takes. ### TestSmoke method diff --git a/example/weather/services/tester/cmd/tester/main.go b/example/weather/services/tester/cmd/tester/main.go index 113d10e3..a760ee63 100644 --- a/example/weather/services/tester/cmd/tester/main.go +++ b/example/weather/services/tester/cmd/tester/main.go @@ -12,12 +12,13 @@ import ( "syscall" "time" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "goa.design/clue/clue" "goa.design/clue/debug" "goa.design/clue/health" "goa.design/clue/log" - "goa.design/clue/metrics" - "goa.design/clue/trace" - goagrpcmiddleware "goa.design/goa/v3/grpc/middleware" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/reflection" @@ -34,13 +35,12 @@ func main() { var ( grpcaddr = flag.String("grpc-addr", ":8090", "gRPC listen address") httpaddr = flag.String("http-addr", ":8091", "HTTP listen address (health checks and metrics)") - agentaddr = flag.String("agent-addr", ":4317", "Grafana agent listen address") + oteladdr = flag.String("otel-addr", ":4317", "OpenTelemetry collector listen address") forecasterAddr = flag.String("forecaster-addr", ":8080", "Forecaster service address") forecasterHealthAddr = flag.String("forecaster-health-addr", ":8081", "Forecaster service health-check address") locatorAddr = flag.String("locator-addr", ":8082", "Locator service address") locatorHealthAddr = flag.String("locator-health-addr", ":8083", "Locator service health-check address") debugf = flag.Bool("debug", false, "Enable debug logs") - monitoringEnabled = flag.Bool("monitoring-enabled", true, "monitoring") ) flag.Parse() @@ -49,78 +49,80 @@ func main() { if log.IsTerminal() { format = log.FormatTerminal } - ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(trace.Log)) - ctx = log.With(ctx, log.KV{K: "svc", V: gentester.ServiceName}) + ctx := log.Context(context.Background(), log.WithFormat(format), log.WithFunc(log.Span)) if *debugf { ctx = log.Context(ctx, log.WithDebug()) log.Debugf(ctx, "debug logs enabled") } - // 2. Setup tracing - if !*monitoringEnabled { - var err error - if ctx, err = trace.Context(ctx, gentester.ServiceName, trace.WithDisabled()); err != nil { - log.Error(ctx, err, log.KV{K: "msg", V: "failed to initialize tracing"}) - } - } else { - log.Debugf(ctx, "connecting to Grafana agent %s", *agentaddr) - conn, err := grpc.DialContext(ctx, *agentaddr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock()) + // 2. Setup isntrumentation + spanExporter, err := otlptracegrpc.New(ctx, + otlptracegrpc.WithEndpoint(*oteladdr), + otlptracegrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize tracing") + } + defer func() { + err := spanExporter.Shutdown(ctx) if err != nil { - log.Errorf(ctx, err, "failed to connect to Grafana agent") - os.Exit(1) + log.Errorf(ctx, err, "failed to shutdown tracing") } - log.Debugf(ctx, "connected to Grafana agent %s", *agentaddr) - ctx, err = trace.Context(ctx, gentester.ServiceName, trace.WithGRPCExporter(conn)) + }() + metricExporter, err := otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithEndpoint(*oteladdr), + otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize metrics") + } + defer func() { + err := metricExporter.Shutdown(ctx) if err != nil { - log.Errorf(ctx, err, "failed to initialize tracing") - os.Exit(1) + log.Errorf(ctx, err, "failed to shutdown metrics") } + }() + cfg, err := clue.NewConfig(ctx, + gentester.ServiceName, + gentester.APIVersion, + metricExporter, + spanExporter, + ) + if err != nil { + log.Fatalf(ctx, err, "failed to initialize instrumentation") } + clue.ConfigureOpenTelemetry(ctx, cfg) - // 3. Setup metrics - ctx = metrics.Context(ctx, gentester.ServiceName) - - // 4. Create clients - lcc, err := grpc.DialContext(ctx, *locatorAddr, + // 3. Create clients + lcc, err := grpc.DialContext(ctx, + *locatorAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithChainUnaryInterceptor( - trace.UnaryClientInterceptor(ctx), - log.UnaryClientInterceptor())) + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { - log.Errorf(ctx, err, "failed to connect to locator") - os.Exit(1) + log.Fatalf(ctx, err, "failed to connect to locator") } lc := locator.New(lcc) fcc, err := grpc.DialContext(ctx, *forecasterAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithChainUnaryInterceptor( - trace.UnaryClientInterceptor(ctx), - log.UnaryClientInterceptor())) + grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { - log.Errorf(ctx, err, "failed to connect to forecast") - os.Exit(1) + log.Fatalf(ctx, err, "failed to connect to forecast") } fc := forecaster.New(fcc) - // 5. Create service & endpoints + // 4. Create service & endpoints svc := tester.New(lc, fc) endpoints := gentester.NewEndpoints(svc) endpoints.Use(debug.LogPayloads()) endpoints.Use(log.Endpoint) - // 6. Create transport + // 5. Create transport server := gengrpc.New(endpoints, nil) grpcsvr := grpc.NewServer( grpc.ChainUnaryInterceptor( - goagrpcmiddleware.UnaryRequestID(), log.UnaryServerInterceptor(ctx), - debug.UnaryServerInterceptor(), - goagrpcmiddleware.UnaryServerLogContext(log.AsGoaMiddlewareLogger), - trace.UnaryServerInterceptor(ctx), - metrics.UnaryServerInterceptor(ctx), - )) + debug.UnaryServerInterceptor()), + grpc.StatsHandler(otelgrpc.NewServerHandler())) genpb.RegisterTesterServer(grpcsvr, server) reflection.Register(grpcsvr) for svc, info := range grpcsvr.GetServiceInfo() { @@ -129,19 +131,18 @@ func main() { } } - // 7. Setup health check, metrics and debug endpoints - check := health.Handler(health.NewChecker( - health.NewPinger("locator", *locatorHealthAddr), - health.NewPinger("forecaster", *forecasterHealthAddr))) + // 6. Setup health check, metrics and debug endpoints mux := http.NewServeMux() debug.MountDebugLogEnabler(mux) debug.MountPprofHandlers(mux) + check := health.Handler(health.NewChecker( + health.NewPinger("locator", *locatorHealthAddr), + health.NewPinger("forecaster", *forecasterHealthAddr))) mux.Handle("/healthz", check) mux.Handle("/livez", check) - mux.Handle("/metrics", metrics.Handler(ctx)) httpsvr := &http.Server{Addr: *httpaddr, Handler: mux} - // 8. Start gRPC and HTTP servers + // 7. Start gRPC and HTTP servers errc := make(chan error) go func() { c := make(chan os.Signal, 1) diff --git a/example/weather/services/tester/gen/grpc/cli/tester_service_api/cli.go b/example/weather/services/tester/gen/grpc/cli/tester_service_api/cli.go index 4bad40b3..c377a24e 100644 --- a/example/weather/services/tester/gen/grpc/cli/tester_service_api/cli.go +++ b/example/weather/services/tester/gen/grpc/cli/tester_service_api/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // Tester Service API gRPC client CLI support package // diff --git a/example/weather/services/tester/gen/grpc/tester/client/cli.go b/example/weather/services/tester/gen/grpc/tester/client/cli.go index 639528d0..541100e3 100644 --- a/example/weather/services/tester/gen/grpc/tester/client/cli.go +++ b/example/weather/services/tester/gen/grpc/tester/client/cli.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC client CLI support package // diff --git a/example/weather/services/tester/gen/grpc/tester/client/client.go b/example/weather/services/tester/gen/grpc/tester/client/client.go index 195e0922..1966b68e 100644 --- a/example/weather/services/tester/gen/grpc/tester/client/client.go +++ b/example/weather/services/tester/gen/grpc/tester/client/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC client // diff --git a/example/weather/services/tester/gen/grpc/tester/client/encode_decode.go b/example/weather/services/tester/gen/grpc/tester/client/encode_decode.go index 62cac956..2907efed 100644 --- a/example/weather/services/tester/gen/grpc/tester/client/encode_decode.go +++ b/example/weather/services/tester/gen/grpc/tester/client/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC client encoders and decoders // diff --git a/example/weather/services/tester/gen/grpc/tester/client/types.go b/example/weather/services/tester/gen/grpc/tester/client/types.go index 4b409045..96552f15 100644 --- a/example/weather/services/tester/gen/grpc/tester/client/types.go +++ b/example/weather/services/tester/gen/grpc/tester/client/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC client types // diff --git a/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.pb.go b/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.pb.go index 16273fed..6c1c5a55 100644 --- a/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.pb.go +++ b/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.pb.go @@ -1,4 +1,4 @@ -// Code generated with goa v3.14.0, DO NOT EDIT. +// Code generated with goa v3.14.2, DO NOT EDIT. // // tester protocol buffer definition // @@ -9,7 +9,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v4.25.1 +// protoc v4.25.0 // source: goagen_tester_tester.proto package weather_testerpb diff --git a/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.proto b/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.proto index 29f1bb52..56bf1064 100644 --- a/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.proto +++ b/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester.proto @@ -1,4 +1,4 @@ -// Code generated with goa v3.14.0, DO NOT EDIT. +// Code generated with goa v3.14.2, DO NOT EDIT. // // tester protocol buffer definition // diff --git a/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester_grpc.pb.go b/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester_grpc.pb.go index 41554e77..9303cc6a 100644 --- a/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester_grpc.pb.go +++ b/example/weather/services/tester/gen/grpc/tester/pb/goagen_tester_tester_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v4.25.1 +// - protoc v4.25.0 // source: goagen_tester_tester.proto package weather_testerpb diff --git a/example/weather/services/tester/gen/grpc/tester/server/encode_decode.go b/example/weather/services/tester/gen/grpc/tester/server/encode_decode.go index 258e3976..47ccf41e 100644 --- a/example/weather/services/tester/gen/grpc/tester/server/encode_decode.go +++ b/example/weather/services/tester/gen/grpc/tester/server/encode_decode.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC server encoders and decoders // diff --git a/example/weather/services/tester/gen/grpc/tester/server/server.go b/example/weather/services/tester/gen/grpc/tester/server/server.go index 6dd4c5cf..9f0a89b2 100644 --- a/example/weather/services/tester/gen/grpc/tester/server/server.go +++ b/example/weather/services/tester/gen/grpc/tester/server/server.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC server // diff --git a/example/weather/services/tester/gen/grpc/tester/server/types.go b/example/weather/services/tester/gen/grpc/tester/server/types.go index dd4b8b9d..5eee7564 100644 --- a/example/weather/services/tester/gen/grpc/tester/server/types.go +++ b/example/weather/services/tester/gen/grpc/tester/server/types.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester gRPC server types // diff --git a/example/weather/services/tester/gen/tester/client.go b/example/weather/services/tester/gen/tester/client.go index 47c861b3..f87f064b 100644 --- a/example/weather/services/tester/gen/tester/client.go +++ b/example/weather/services/tester/gen/tester/client.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester client // diff --git a/example/weather/services/tester/gen/tester/endpoints.go b/example/weather/services/tester/gen/tester/endpoints.go index 5da528ce..4bc0bcb7 100644 --- a/example/weather/services/tester/gen/tester/endpoints.go +++ b/example/weather/services/tester/gen/tester/endpoints.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester endpoints // diff --git a/example/weather/services/tester/gen/tester/service.go b/example/weather/services/tester/gen/tester/service.go index 7e1de8e0..64020e95 100644 --- a/example/weather/services/tester/gen/tester/service.go +++ b/example/weather/services/tester/gen/tester/service.go @@ -1,4 +1,4 @@ -// Code generated by goa v3.14.0, DO NOT EDIT. +// Code generated by goa v3.14.2, DO NOT EDIT. // // tester service // @@ -32,6 +32,9 @@ type Service interface { // key. const ServiceName = "tester" +// APIVersion is the version of the API as defined in the design. +const APIVersion = "0.0.1" + // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. diff --git a/example/weather/services/tester/run_tests.go b/example/weather/services/tester/run_tests.go index 22a1cc7c..eebb07a5 100644 --- a/example/weather/services/tester/run_tests.go +++ b/example/weather/services/tester/run_tests.go @@ -12,9 +12,10 @@ import ( "sync" "time" - gentester "goa.design/clue/example/weather/services/tester/gen/tester" "goa.design/clue/log" "golang.org/x/exp/slices" + + gentester "goa.design/clue/example/weather/services/tester/gen/tester" ) // Ends a test by calculating duration and appending the results to the test collection @@ -40,7 +41,7 @@ func getStackTrace(wg *sync.WaitGroup, m *sync.Mutex) string { outC := make(chan string) go func() { var buf bytes.Buffer - io.Copy(&buf, f) + io.Copy(&buf, f) // nolint: errcheck outC <- buf.String() }() diff --git a/example/weather/services/tester/testing.go b/example/weather/services/tester/testing.go index c4595935..a691a953 100644 --- a/example/weather/services/tester/testing.go +++ b/example/weather/services/tester/testing.go @@ -3,9 +3,10 @@ package tester import ( "context" + "goa.design/clue/log" + "goa.design/clue/example/weather/services/tester/clients/forecaster" "goa.design/clue/example/weather/services/tester/clients/locator" - "goa.design/clue/log" ) type ( diff --git a/go.mod b/go.mod index de7c432e..21bccb97 100644 --- a/go.mod +++ b/go.mod @@ -7,18 +7,17 @@ toolchain go1.21.3 require ( github.com/aws/smithy-go v1.19.0 github.com/go-logfmt/logfmt v0.6.0 - github.com/prometheus/client_golang v1.18.0 - github.com/prometheus/client_model v0.5.0 + github.com/go-logr/logr v1.4.1 github.com/stretchr/testify v1.8.4 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 go.opentelemetry.io/otel v1.21.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 + go.opentelemetry.io/otel/metric v1.21.0 go.opentelemetry.io/otel/sdk v1.21.0 + go.opentelemetry.io/otel/sdk/metric v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 - goa.design/goa/v3 v3.14.1 - golang.org/x/term v0.15.0 + goa.design/goa/v3 v3.14.2 + golang.org/x/term v0.16.0 golang.org/x/tools v0.16.1 google.golang.org/grpc v1.60.1 google.golang.org/protobuf v1.32.0 @@ -26,33 +25,22 @@ require ( require ( github.com/AnatolyRugalev/goregen v0.1.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-chi/chi/v5 v5.0.11 // indirect - github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3cc9e489..2e79ff2e 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,13 @@ -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/AnatolyRugalev/goregen v0.1.0 h1:xrdXkLaskMnbxW0x4FWNj2yoednv0X2bcTBWpuJGYfE= github.com/AnatolyRugalev/goregen v0.1.0/go.mod h1:sVlY1tjcirqLBRZnCcIq1+7/Lwmqz5g7IK8AStjOVzI= github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= @@ -42,8 +27,6 @@ github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -55,18 +38,10 @@ github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqC github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -75,55 +50,39 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -goa.design/goa/v3 v3.14.1 h1:wNvKwCXeEJWmosd2MXaZHjJGm+wtEPCZf5jNXCx3oJo= -goa.design/goa/v3 v3.14.1/go.mod h1:MhHWTSB7X6qVuNvjDTNtr/YQyYi9x1I4zfPtXnCdHtQ= +goa.design/goa/v3 v3.14.2 h1:V8skSFWRUTddqZnRy65Mdx0SpGmYe9aQhgCQppyvCKA= +goa.design/goa/v3 v3.14.2/go.mod h1:sq7F2y/2/eK/XCc8Ff7Was3u42fRmQXp1pTm8FfnGgo= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -131,9 +90,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go.work.sum b/go.work.sum index b52d0fa6..446a792f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,6 +5,7 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= cloud.google.com/go/accessapproval v1.5.0 h1:/nTivgnV/n1CaAeo+ekGexTYUsKEU9jUVkoY5359+3Q= cloud.google.com/go/accessapproval v1.7.1/go.mod h1:JYczztsHRMK7NTXb6Xw+dwbs/WnOJxbo/2mTI+Kgg68= cloud.google.com/go/accessapproval v1.7.3/go.mod h1:4l8+pwIxGTNqSf4T3ds8nLO94NQf0W/KnMNuQ9PbnP8= @@ -17,6 +18,7 @@ cloud.google.com/go/aiplatform v1.27.0 h1:DBi3Jk9XjCJ4pkkLM4NqKgj3ozUL1wq4l+d3/j cloud.google.com/go/aiplatform v1.48.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= cloud.google.com/go/aiplatform v1.51.2/go.mod h1:hCqVYB3mY45w99TmetEoe8eCQEwZEp9WHxeZdcv9phw= cloud.google.com/go/aiplatform v1.54.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/analytics v0.12.0 h1:NKw6PpQi6V1O+KsjuTd+bhip9d0REYu4NevC45vtGp8= cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= cloud.google.com/go/analytics v0.21.5/go.mod h1:BQtOBHWTlJ96axpPPnw5CvGJ6i3Ve/qX2fTxR8qWyr8= @@ -64,6 +66,7 @@ cloud.google.com/go/batch v0.4.0 h1:1jvEBY55OH4Sd2FxEXQfxGExFWov1A/IaRe+Z5Z71Fw= cloud.google.com/go/batch v1.3.1/go.mod h1:VguXeQKXIYaeeIYbuozUmBR13AfL4SJP7IltNPS+A4A= cloud.google.com/go/batch v1.6.1/go.mod h1:urdpD13zPe6YOK+6iZs/8/x2VBRofvblLpx0t57vM98= cloud.google.com/go/batch v1.6.3/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/beyondcorp v0.3.0 h1:w+4kThysgl0JiKshi2MKDCg2NZgOyqOI0wq2eBZyrzA= cloud.google.com/go/beyondcorp v1.0.0/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= cloud.google.com/go/beyondcorp v1.0.2/go.mod h1:m8cpG7caD+5su+1eZr+TSvF6r21NdLJk4f9u4SP2Ntc= @@ -76,10 +79,12 @@ cloud.google.com/go/billing v1.7.0 h1:Xkii76HWELHwBtkQVZvqmSo9GTr0O+tIbRNnMcGdlg cloud.google.com/go/billing v1.16.0/go.mod h1:y8vx09JSSJG02k5QxbycNRrN7FGZB6F3CAcgum7jvGA= cloud.google.com/go/billing v1.17.3/go.mod h1:z83AkoZ7mZwBGT3yTnt6rSGI1OOsHSIi6a5M3mJ8NaU= cloud.google.com/go/billing v1.17.4/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/binaryauthorization v1.4.0 h1:pL70vXWn9TitQYXBWTK2abHl2JHLwkFRjYw6VflRqEA= cloud.google.com/go/binaryauthorization v1.6.1/go.mod h1:TKt4pa8xhowwffiBmbrbcxijJRZED4zrqnwZ1lKH51U= cloud.google.com/go/binaryauthorization v1.7.2/go.mod h1:kFK5fQtxEp97m92ziy+hbu+uKocka1qRRL8MVJIgjv0= cloud.google.com/go/binaryauthorization v1.7.3/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/certificatemanager v1.4.0 h1:tzbR4UHBbgsewMWUD93JHi8EBi/gHBoSAcY1/sThFGk= cloud.google.com/go/certificatemanager v1.7.1/go.mod h1:iW8J3nG6SaRYImIa+wXQ0g8IgoofDFRp5UMzaNk1UqI= cloud.google.com/go/certificatemanager v1.7.3/go.mod h1:T/sZYuC30PTag0TLo28VedIRIj1KPGcOQzjWAptHa00= @@ -110,10 +115,12 @@ cloud.google.com/go/contactcenterinsights v1.4.0 h1:tTQLI/ZvguUf9Hv+36BkG2+/PeC8 cloud.google.com/go/contactcenterinsights v1.10.0/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= cloud.google.com/go/contactcenterinsights v1.11.2/go.mod h1:A9PIR5ov5cRcd28KlDbmmXE8Aay+Gccer2h4wzkYFso= cloud.google.com/go/contactcenterinsights v1.12.0/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/container v1.7.0 h1:nbEK/59GyDRKKlo1SqpohY1TK8LmJ2XNcvS9Gyom2A0= cloud.google.com/go/container v1.24.0/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= cloud.google.com/go/container v1.26.2/go.mod h1:YlO84xCt5xupVbLaMY4s3XNE79MUJ+49VmkInr6HvF4= cloud.google.com/go/container v1.28.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= cloud.google.com/go/containeranalysis v0.6.0 h1:2824iym832ljKdVpCBnpqm5K94YT/uHTVhNF+dRTXPI= cloud.google.com/go/containeranalysis v0.10.1/go.mod h1:Ya2jiILITMY68ZLPaogjmOMNkwsDrWBSTyBubGXO7j0= cloud.google.com/go/containeranalysis v0.11.2/go.mod h1:xibioGBC1MD2j4reTyV1xY1/MvKaz+fyM9ENWhmIeP8= @@ -142,6 +149,7 @@ cloud.google.com/go/dataplex v1.4.0 h1:cNxeA2DiWliQGi21kPRqnVeQ5xFhNoEjPRt1400Pm cloud.google.com/go/dataplex v1.9.0/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= cloud.google.com/go/dataplex v1.10.2/go.mod h1:xdC8URdTrCrZMW6keY779ZT1cTOfV8KEPNsw+LTRT1Y= cloud.google.com/go/dataplex v1.11.2/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataproc v1.8.0 h1:gVOqNmElfa6n/ccG/QDlfurMWwrK3ezvy2b2eDoCmS0= cloud.google.com/go/dataproc/v2 v2.0.1/go.mod h1:7Ez3KRHdFGcfY7GcevBbvozX+zyWGcwLJvvAMwCaoZ4= cloud.google.com/go/dataproc/v2 v2.2.2/go.mod h1:aocQywVmQVF4i8CL740rNI/ZRpsaaC1Wh2++BJ7HEJ4= @@ -161,10 +169,12 @@ cloud.google.com/go/deploy v1.5.0 h1:kI6dxt8Ml0is/x7YZjLveTvR7YPzXAUD/8wQZ2nH5zA cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= cloud.google.com/go/deploy v1.14.1/go.mod h1:N8S0b+aIHSEeSr5ORVoC0+/mOPUysVt8ae4QkZYolAw= cloud.google.com/go/deploy v1.15.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.16.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= cloud.google.com/go/dialogflow v1.19.0 h1:HYHVOkoxQ9bSfNIelSZYNAtUi4CeSrCnROyOsbOqPq8= cloud.google.com/go/dialogflow v1.40.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= cloud.google.com/go/dialogflow v1.44.2/go.mod h1:QzFYndeJhpVPElnFkUXxdlptx0wPnBWLCBT9BvtC3/c= cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= cloud.google.com/go/dlp v1.7.0 h1:9I4BYeJSVKoSKgjr70fLdRDumqcUeVmHV4fd5f9LR6Y= cloud.google.com/go/dlp v1.10.1/go.mod h1:IM8BWz1iJd8njcNcG0+Kyd9OPnqnRNkDV8j42VT5KOI= cloud.google.com/go/dlp v1.10.3/go.mod h1:iUaTc/ln8I+QT6Ai5vmuwfw8fqTk2kaz0FvCwhLCom0= @@ -173,6 +183,7 @@ cloud.google.com/go/documentai v1.10.0 h1:jfq09Fdjtnpnmt/MLyf6A3DM3ynb8B2na0K+vS cloud.google.com/go/documentai v1.22.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= cloud.google.com/go/documentai v1.23.4/go.mod h1:4MYAaEMnADPN1LPN5xboDR5QVB6AgsaxgFdJhitlE2Y= cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/domains v0.7.0 h1:pu3JIgC1rswIqi5romW0JgNO6CTUydLYX8zyjiAvO1c= cloud.google.com/go/domains v0.9.1/go.mod h1:aOp1c0MbejQQ2Pjf1iJvnVyT+z6R6s8pX66KaCSDYfE= cloud.google.com/go/domains v0.9.3/go.mod h1:29k66YNDLDY9LCFKpGFeh6Nj9r62ZKm5EsUJxAl84KU= @@ -226,6 +237,7 @@ cloud.google.com/go/gsuiteaddons v1.6.4/go.mod h1:rxtstw7Fx22uLOXBpsvb9DUbC+fiXs cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/iap v1.5.0 h1:BGEXovwejOCt1zDk8hXq0bOhhRu9haXKWXXXp2B4wBM= @@ -269,6 +281,7 @@ cloud.google.com/go/maps v0.1.0 h1:kLReRbclTgJefw2fcCbdLPLhPj0U6UUWN10ldG8sdOU= cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= cloud.google.com/go/maps v1.5.1/go.mod h1:NPMZw1LJwQZYCfz4y+EIw+SI+24A4bpdFJqdKVr0lt4= cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.2/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= cloud.google.com/go/mediatranslation v0.6.0 h1:qAJzpxmEX+SeND10Y/4868L5wfZpo4Y3BIEnIieP4dk= cloud.google.com/go/mediatranslation v0.8.1/go.mod h1:L/7hBdEYbYHQJhX2sldtTO5SZZ1C1vkapubj0T2aGig= cloud.google.com/go/mediatranslation v0.8.3/go.mod h1:F9OnXTy336rteOEywtY7FOqCk+J43o2RF638hkOQl4Y= @@ -341,6 +354,7 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.5.0 h1:UqzFfb/WvhwXGDF1eQtdHLrmni+ cloud.google.com/go/recaptchaenterprise/v2 v2.7.2/go.mod h1:kR0KjsJS7Jt1YSyWFkseQ756D45kaYNTlDPPaRAvDBU= cloud.google.com/go/recaptchaenterprise/v2 v2.8.2/go.mod h1:kpaDBOpkwD4G0GVMzG1W6Doy1tFFC97XAV3xy+Rd/pw= cloud.google.com/go/recaptchaenterprise/v2 v2.8.4/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recommendationengine v0.6.0 h1:6w+WxPf2LmUEqX0YyvfCoYb8aBYOcbIV25Vg6R0FLGw= cloud.google.com/go/recommendationengine v0.8.1/go.mod h1:MrZihWwtFYWDzE6Hz5nKcNz3gLizXVIDI/o3G1DLcrE= cloud.google.com/go/recommendationengine v0.8.3/go.mod h1:m3b0RZV02BnODE9FeSvGv1qibFo8g0OnmB/RMwYy4V8= @@ -385,6 +399,7 @@ cloud.google.com/go/securitycenter v1.16.0 h1:QTVtk/Reqnx2bVIZtJKm1+mpfmwRwymmNv cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY172h4S8aXyI9/hsQ= cloud.google.com/go/securitycenter v1.24.1/go.mod h1:3h9IdjjHhVMXdQnmqzVnM7b0wMn/1O/U20eWVpMpZjI= cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/servicecontrol v1.5.0 h1:ImIzbOu6y4jL6ob65I++QzvqgFaoAKgHOG+RU9/c4y8= cloud.google.com/go/servicedirectory v1.7.0 h1:f7M8IMcVzO3T425AqlZbP3yLzeipsBHtRza8vVFYMhQ= cloud.google.com/go/servicedirectory v1.11.0/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= @@ -400,6 +415,7 @@ cloud.google.com/go/spanner v1.41.0 h1:NvdTpRwf7DTegbfFdPjAWyD7bOVu0VeMqcvR9aCQC cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSqW4uH9lfUI= cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= cloud.google.com/go/spanner v1.53.0/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= cloud.google.com/go/speech v1.9.0 h1:yK0ocnFH4Wsf0cMdUyndJQ/hPv02oTJOxzi6AgpBy4s= cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= cloud.google.com/go/speech v1.19.2/go.mod h1:2OYFfj+Ch5LWjsaSINuCZsre/789zlcCI3SY4oAi2oI= @@ -480,12 +496,17 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8V github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= @@ -501,527 +522,140 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ= -github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0= -github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/getkin/kin-openapi v0.112.0 h1:lnLXx3bAG53EJVI4E/w0N8i1Y/vUZUEsnrXkgnfn7/Y= -github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= -github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= -github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= +github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/swag v0.19.13 h1:233UVgMy1DlmCYYfOiFpta6e2urloh+sEs5id6lyzog= -github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= -github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= -github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= -github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= -github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= -github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 h1:24NdJ5N6gtrcoeS4JwLMeruKFmg20QdF/5UnX5S/j18= github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71/go.mod h1:ozZLfjiLmXytkIUh200wMeuoQJ4ww06wN+KZtFP6j3g= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqCDBcAhLXoi3TzC27Zad/Vn+gnVQ= +github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= +github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b/go.mod h1:Bj8LjjP0ReT1eKt5QlKjwgi5AFm5mI6O1A2G4ChI0Ag= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= -github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/smartystreets/assertions v1.2.1 h1:bKNHfEv7tSIjZ8JbKaFjzFINljxG4lzZvmHUnElzOIg= -github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= -github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea h1:CyhwejzVGvZ3Q2PSbQ4NRRYn+ZWv5eS1vlaEusT+bAI= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= -go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= -go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 h1:gbhw/u49SS3gkPWiYweQNJGm/uJN5GkI/FrosxSHT7A= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +goa.design/goa/v3 v3.14.2 h1:V8skSFWRUTddqZnRy65Mdx0SpGmYe9aQhgCQppyvCKA= +goa.design/goa/v3 v3.14.2/go.mod h1:sq7F2y/2/eK/XCc8Ff7Was3u42fRmQXp1pTm8FfnGgo= +goa.design/model v1.9.1 h1:vhY89pjUWW1firAHaccW9MZTiMEEVvtcwN9g8odyywQ= +goa.design/model v1.9.1/go.mod h1:PlrrmVbrBm7gxqoWqburVUudaMd/d3Sdul7/Kr4Ap54= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= -google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= -google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= -google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc h1:ijGwO+0vL2hJt5gaygqP2j6PfflOBrRot0IczKbmtio= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= -google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/log/adapt.go b/log/adapt.go index 3c9b86d9..e09410ef 100644 --- a/log/adapt.go +++ b/log/adapt.go @@ -3,8 +3,10 @@ package log import ( "context" "fmt" + "sync" "github.com/aws/smithy-go/logging" + "github.com/go-logr/logr" "goa.design/goa/v3/middleware" ) @@ -19,16 +21,21 @@ type ( context.Context } + // LogrSink returns a logr LogSink compatible logger. + LogrSink struct { + lock sync.Mutex + name string + context.Context + } + // goaLogger is a Goa middleware compatible logger. goaLogger struct { context.Context } ) -// Make Goa use clue's request ID context key. -func init() { - middleware.RequestIDKey = RequestIDKey -} +// NameKey is the key used to log the name of the logger. +const NameKey = "log" // AsGoaMiddlewareLogger creates a Goa middleware compatible logger that can be used when // configuring Goa HTTP or gRPC servers. @@ -76,6 +83,19 @@ func AsAWSLogger(ctx context.Context) *AWSLogger { return &AWSLogger{ctx} } +// ToLogrSink returns a logr.LogSink. +// +// Usage: +// +// import "goa.design/clue/log" +// +// ctx := log.Context(context.Background()) +// sink := log.ToLogrSink(ctx) +// logger := logr.New(sink) +func ToLogrSink(ctx context.Context) *LogrSink { + return &LogrSink{Context: ctx} +} + // Fatal is equivalent to l.Print() followed by a call to os.Exit(1). func (l *StdLogger) Fatal(v ...interface{}) { l.Print(v...) @@ -140,6 +160,53 @@ func (l *AWSLogger) WithContext(ctx context.Context) logging.Logger { return l } +func (l *LogrSink) Init(info logr.RuntimeInfo) {} + +func (l *LogrSink) Enabled(level int) bool { + return true +} + +func (l *LogrSink) Info(level int, msg string, keysAndValues ...interface{}) { + kvs := make([]KV, len(keysAndValues)/2+1) + kvs[0] = KV{K: "msg", V: msg} + for i := 0; i < len(keysAndValues); i += 2 { + kvs[i/2+1] = KV{K: fmt.Sprint(keysAndValues[i]), V: keysAndValues[i+1]} + } + if level == 0 { + Info(l, kvList(kvs)) + } else { + Debug(l, kvList(kvs)) + } +} + +func (l *LogrSink) Error(err error, msg string, keysAndValues ...interface{}) { + kvs := make([]KV, len(keysAndValues)/2+1) + kvs[0] = KV{K: "msg", V: msg} + for i := 0; i < len(keysAndValues); i += 2 { + kvs[i/2+1] = KV{K: fmt.Sprint(keysAndValues[i]), V: keysAndValues[i+1]} + } + Error(l, err, kvList(kvs)) +} + +func (l *LogrSink) WithValues(keysAndValues ...any) logr.LogSink { + kvs := make([]KV, len(keysAndValues)/2) + for i := 0; i < len(keysAndValues); i += 2 { + kvs[i/2] = KV{K: fmt.Sprint(keysAndValues[i]), V: keysAndValues[i+1]} + } + return &LogrSink{Context: With(l, kvList(kvs))} +} + +func (l *LogrSink) WithName(name string) logr.LogSink { + l.lock.Lock() + defer l.lock.Unlock() + cur := l.name + if cur != "" { + cur += "/" + } + l.name = cur + name + return &LogrSink{Context: With(l, KV{NameKey, l.name}), name: l.name} +} + // Log creates a log entry using a sequence of key/value pairs. func (l goaLogger) Log(keyvals ...interface{}) error { n := (len(keyvals) + 1) / 2 diff --git a/log/adapt_test.go b/log/adapt_test.go index 41b47b26..a1f4c645 100644 --- a/log/adapt_test.go +++ b/log/adapt_test.go @@ -3,10 +3,12 @@ package log import ( "bytes" "context" + "errors" "testing" "time" "github.com/aws/smithy-go/logging" + "github.com/go-logr/logr" "github.com/stretchr/testify/assert" ) @@ -131,3 +133,67 @@ func TestAsAWSLogger(t *testing.T) { want = "time=2022-01-09T20:29:45Z level=info msg=\"hello small world\"\n" assert.Equal(t, buf.String(), want) } + +func TestToLogrSink(t *testing.T) { + restore := timeNow + timeNow = func() time.Time { return time.Date(2022, time.January, 9, 20, 29, 45, 0, time.UTC) } + defer func() { timeNow = restore }() + var buf bytes.Buffer + ctx := Context(context.Background(), WithOutput(&buf), WithDebug()) + var sink logr.LogSink = ToLogrSink(ctx) + + sink.Init(logr.RuntimeInfo{}) + assert.True(t, sink.Enabled(0)) + assert.True(t, sink.Enabled(1)) + assert.True(t, sink.Enabled(2)) + assert.True(t, sink.Enabled(3)) + + msg := "hello world" + expected := "time=2022-01-09T20:29:45Z level=info msg=\"hello world\"\n" + expecteddebug := "time=2022-01-09T20:29:45Z level=debug msg=\"hello world\"\n" + expectederr := "time=2022-01-09T20:29:45Z level=error err=error msg=\"hello world\"\n" + empty := "" + + logger := logr.New(sink) + logger.Info(msg) + assert.Equal(t, expected, buf.String()) + + buf.Reset() + logger.V(1).Info(msg) + assert.Equal(t, expecteddebug, buf.String()) + + buf.Reset() + logger.Error(errors.New("error"), msg) + assert.Equal(t, expectederr, buf.String()) + + ctx = Context(context.Background(), WithOutput(&buf)) + sink = ToLogrSink(ctx) + logger = logr.New(sink) + + buf.Reset() + logger.Info(msg) + assert.Equal(t, empty, buf.String()) + + FlushAndDisableBuffering(ctx) + buf.Reset() + logger.Info(msg) + assert.Equal(t, expected, buf.String()) + + buf.Reset() + logger.V(1).Info(msg) + assert.Equal(t, empty, buf.String()) + + sink = sink.WithValues("key", "value") + expectedWithValues := "time=2022-01-09T20:29:45Z level=info key=value msg=\"hello world\"\n" + logger = logr.New(sink) + buf.Reset() + logger.Info(msg) + assert.Equal(t, expectedWithValues, buf.String()) + + sink = sink.WithName("name") + expectedWithName := "time=2022-01-09T20:29:45Z level=info key=value log=name msg=\"hello world\"\n" + logger = logr.New(sink) + buf.Reset() + logger.Info(msg) + assert.Equal(t, expectedWithName, buf.String()) +} diff --git a/log/grpc.go b/log/grpc.go index 5154f4e7..a1f7c5a6 100644 --- a/log/grpc.go +++ b/log/grpc.go @@ -2,10 +2,12 @@ package log import ( "context" + "crypto/rand" + "encoding/base64" + "io" "path" "time" - goamiddleware "goa.design/goa/v3/middleware" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -24,9 +26,13 @@ type ( grpcOptions struct { iserr func(codes.Code) bool disableCallLogging bool + disableCallID bool } ) +// Be nice to tests +var shortID = randShortID + // UnaryServerInterceptor returns a unary interceptor that performs two tasks: // 1. Enriches the request context with the logger specified in logCtx. // 2. Logs details of the unary call, unless the WithDisableCallLogging option is provided. @@ -44,8 +50,8 @@ func UnaryServerInterceptor(logCtx context.Context, opts ...GRPCLogOption) grpc. handler grpc.UnaryHandler, ) (interface{}, error) { ctx = WithContext(ctx, logCtx) - if reqID := ctx.Value(goamiddleware.RequestIDKey); reqID != nil { - ctx = With(ctx, KV{RequestIDKey, reqID}) + if !o.disableCallID { + ctx = With(ctx, KV{RequestIDKey, shortID()}) } if o.disableCallLogging { return handler(ctx, req) @@ -88,8 +94,8 @@ func StreamServerInterceptor(logCtx context.Context, opts ...GRPCLogOption) grpc handler grpc.StreamHandler, ) error { ctx := WithContext(stream.Context(), logCtx) - if reqID := ctx.Value(goamiddleware.RequestIDKey); reqID != nil { - ctx = With(ctx, KV{RequestIDKey, reqID}) + if !o.disableCallID { + ctx = With(ctx, KV{RequestIDKey, shortID()}) } stream = &streamWithContext{stream, ctx} if o.disableCallLogging { @@ -204,6 +210,14 @@ func WithDisableCallLogging() GRPCLogOption { } } +// WithDisableCallID returns a GRPC logger option that disables the +// generation of request IDs. +func WithDisableCallID() GRPCLogOption { + return func(o *grpcOptions) { + o.disableCallID = true + } +} + func defaultGRPCOptions() *grpcOptions { return &grpcOptions{ iserr: func(c codes.Code) bool { @@ -220,3 +234,11 @@ type streamWithContext struct { func (s *streamWithContext) Context() context.Context { return s.ctx } + +// randShortID produces a "unique" 6 bytes long string. +// This algorithm favors simplicity and efficiency over true uniqueness. +func randShortID() string { + b := make([]byte, 6) + io.ReadFull(rand.Reader, b) // nolint: errcheck + return base64.RawURLEncoding.EncodeToString(b) +} diff --git a/log/grpc_test.go b/log/grpc_test.go index 3e824f18..31e37786 100644 --- a/log/grpc_test.go +++ b/log/grpc_test.go @@ -12,8 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "goa.design/clue/internal/testsvc" - grpcmiddleware "goa.design/goa/v3/grpc/middleware" - goamiddleware "goa.design/goa/v3/middleware" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -24,13 +22,13 @@ func TestUnaryServerInterceptor(t *testing.T) { timeNow = func() time.Time { return time.Date(2022, time.January, 9, 20, 29, 45, 0, time.UTC) } defer func() { timeNow = now }() - testRequestID := "test-request-id" - requestIDInterceptor := unaryStaticRequestIDInterceptor(testRequestID) + shortID = func() string { return "test-request-id" } + defer func() { shortID = randShortID }() - prefix := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"test-request-id","msg":"start","grpc.service":"test.Test","grpc.method":"GrpcMethod"}` - logged := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"test-request-id","key1":"value1","key2":"value2"}` - suffix := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"test-request-id","msg":"end","grpc.service":"test.Test","grpc.method":"GrpcMethod","grpc.code":"OK","grpc.time_ms":0}` - errors := `{"time":"2022-01-09T20:29:45Z","level":"error","request-id":"test-request-id","err":"rpc error: code = Unknown desc = test-error","grpc.service":"test.Test","grpc.method":"GrpcMethod","grpc.status":"test-error","grpc.code":"Unknown","grpc.time_ms":0}` + prefix := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","msg":"start","grpc.service":"test.Test","grpc.method":"GrpcMethod"}` + logged := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","key1":"value1","key2":"value2"}` + suffix := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","msg":"end","grpc.service":"test.Test","grpc.method":"GrpcMethod","grpc.code":"OK","grpc.time_ms":0}` + errors := `{"time":"2022-01-09T20:29:45Z","level":"error","request_id":"test-request-id","err":"rpc error: code = Unknown desc = test-error","grpc.service":"test.Test","grpc.method":"GrpcMethod","grpc.status":"test-error","grpc.code":"Unknown","grpc.time_ms":0}` cases := []struct { name string @@ -65,7 +63,7 @@ func TestUnaryServerInterceptor(t *testing.T) { ctx := Context(context.Background(), WithOutput(&buf), WithFormat(FormatJSON)) logInterceptor := UnaryServerInterceptor(ctx, c.options...) cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.ChainUnaryInterceptor(requestIDInterceptor, logInterceptor)), + testsvc.WithServerOptions(grpc.UnaryInterceptor(logInterceptor)), testsvc.WithUnaryFunc(c.method)) _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) @@ -87,13 +85,13 @@ func TestStreamServerTrace(t *testing.T) { timeNow = func() time.Time { return time.Date(2022, time.January, 9, 20, 29, 45, 0, time.UTC) } defer func() { timeNow = now }() - testRequestID := "test-request-id" - requestIDInterceptor := streamStaticRequestIDInterceptor(testRequestID) + shortID = func() string { return "test-request-id" } + defer func() { shortID = randShortID }() - prefix := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"test-request-id","msg":"start","grpc.service":"test.Test","grpc.method":"GrpcStream"}` - logged := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"test-request-id","key1":"value1","key2":"value2"}` - suffix := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"test-request-id","msg":"end","grpc.service":"test.Test","grpc.method":"GrpcStream","grpc.code":"OK","grpc.time_ms":XXX}` - errors := `{"time":"2022-01-09T20:29:45Z","level":"error","request-id":"test-request-id","err":"rpc error: code = Unknown desc = test-error","grpc.service":"test.Test","grpc.method":"GrpcStream","grpc.status":"test-error","grpc.code":"Unknown","grpc.time_ms":XXX}` + prefix := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","msg":"start","grpc.service":"test.Test","grpc.method":"GrpcStream"}` + logged := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","key1":"value1","key2":"value2"}` + suffix := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","msg":"end","grpc.service":"test.Test","grpc.method":"GrpcStream","grpc.code":"OK","grpc.time_ms":XXX}` + errors := `{"time":"2022-01-09T20:29:45Z","level":"error","request_id":"test-request-id","err":"rpc error: code = Unknown desc = test-error","grpc.service":"test.Test","grpc.method":"GrpcStream","grpc.status":"test-error","grpc.code":"Unknown","grpc.time_ms":XXX}` cases := []struct { name string @@ -128,7 +126,7 @@ func TestStreamServerTrace(t *testing.T) { ctx := Context(context.Background(), WithOutput(&buf), WithFormat(FormatJSON)) logInterceptor := StreamServerInterceptor(ctx, c.options...) cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.ChainStreamInterceptor(requestIDInterceptor, logInterceptor)), + testsvc.WithServerOptions(grpc.StreamInterceptor(logInterceptor)), testsvc.WithStreamFunc(c.method)) stream, err := cli.GRPCStream(context.Background()) @@ -156,6 +154,7 @@ func TestUnaryClientInterceptor(t *testing.T) { endLog := `time=2022-01-09T20:29:45Z level=info msg=end grpc.service=test.Test grpc.method=GrpcMethod grpc.code=OK grpc.time_ms=42` errorLog := `time=2022-01-09T20:29:45Z level=error err="rpc error: code = Unknown desc = error" grpc.service=test.Test grpc.method=GrpcMethod grpc.status=error grpc.code=Unknown grpc.time_ms=42` statusLog := `time=2022-01-09T20:29:45Z level=error err="rpc error: code = Unknown desc = error" grpc.service=test.Test grpc.method=GrpcMethod grpc.status=error grpc.code=Unknown grpc.time_ms=42` + cases := []struct { name string noLog bool @@ -219,6 +218,7 @@ func TestUnaryClientInterceptor(t *testing.T) { func TestStreamClientInterceptor(t *testing.T) { startLog := `time=2022-01-09T20:29:45Z level=info msg=start grpc.service=test.Test grpc.method=GrpcStream` endLog := `time=2022-01-09T20:29:45Z level=info msg=end grpc.service=test.Test grpc.method=GrpcStream grpc.code=OK grpc.time_ms=42` + cases := []struct { name string noLog bool @@ -259,8 +259,7 @@ func TestStreamClientInterceptor(t *testing.T) { func logUnaryMethod(ctx context.Context, _ *testsvc.Fields) (*testsvc.Fields, error) { Print(ctx, KV{"key1", "value1"}, KV{"key2", "value2"}) - reqID := ctx.Value(goamiddleware.RequestIDKey).(string) - return &testsvc.Fields{S: &reqID}, nil + return &testsvc.Fields{}, nil } func errorMethod(ctx context.Context, _ *testsvc.Fields) (*testsvc.Fields, error) { @@ -273,8 +272,6 @@ func echoMethod(ctx context.Context, stream testsvc.Stream) (err error) { if err != nil { return err } - reqID := ctx.Value(goamiddleware.RequestIDKey).(string) - f.S = &reqID if err := stream.Send(f); err != nil { return err } @@ -299,18 +296,3 @@ func dummyStreamMethod() func(context.Context, testsvc.Stream) error { return stream.Close() } } - -func unaryStaticRequestIDInterceptor(id string) grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - ctx = context.WithValue(ctx, goamiddleware.RequestIDKey, id) // nolint:staticcheck - return handler(ctx, req) - } -} - -func streamStaticRequestIDInterceptor(id string) grpc.StreamServerInterceptor { - return func(srv interface{}, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - ctx := context.WithValue(stream.Context(), goamiddleware.RequestIDKey, id) // nolint:staticcheck - wss := grpcmiddleware.NewWrappedServerStream(ctx, stream) - return handler(srv, wss) - } -} diff --git a/log/http.go b/log/http.go index 9f3363ac..b8a3f719 100644 --- a/log/http.go +++ b/log/http.go @@ -1,16 +1,16 @@ package log import ( + "bufio" "bytes" "context" + "errors" "fmt" "io" "net" "net/http" "regexp" - goahttpmiddleware "goa.design/goa/v3/http/middleware" - "goa.design/goa/v3/middleware" goa "goa.design/goa/v3/pkg" ) @@ -26,6 +26,7 @@ type ( httpLogOptions struct { pathFilters []*regexp.Regexp disableRequestLogging bool + disableRequestID bool } httpClientOptions struct { @@ -38,6 +39,14 @@ type ( http.RoundTripper options *httpClientOptions } + + // responseCapture is a http.ResponseWriter which captures the response status + // code and content length. + responseCapture struct { + http.ResponseWriter + StatusCode int + ContentLength int + } ) // HTTP returns a HTTP middleware that performs two tasks: @@ -63,8 +72,8 @@ func HTTP(logCtx context.Context, opts ...HTTPLogOption) func(http.Handler) http } } ctx := WithContext(req.Context(), logCtx) - if requestID := req.Context().Value(middleware.RequestIDKey); requestID != nil { - ctx = With(ctx, KV{RequestIDKey, requestID}) + if !options.disableRequestID { + ctx = With(ctx, KV{RequestIDKey, shortID()}) } if options.disableRequestLogging { h.ServeHTTP(w, req.WithContext(ctx)) @@ -75,7 +84,7 @@ func HTTP(logCtx context.Context, opts ...HTTPLogOption) func(http.Handler) http fromKV := KV{K: HTTPFromKey, V: from(req)} Print(ctx, KV{K: MessageKey, V: "start"}, methKV, urlKV, fromKV) - rw := goahttpmiddleware.CaptureResponse(w) + rw := &responseCapture{ResponseWriter: w} started := timeNow() h.ServeHTTP(rw, req.WithContext(ctx)) @@ -146,6 +155,14 @@ func WithDisableRequestLogging() HTTPLogOption { } } +// WithDisableRequestID returns a HTTP middleware option that disables the +// generation of request IDs. +func WithDisableRequestID() HTTPLogOption { + return func(o *httpLogOptions) { + o.disableRequestID = true + } +} + // RoundTrip executes the given HTTP request and logs the request and response. The // request context must be initialized with a clue logger. func (c *client) RoundTrip(req *http.Request) (resp *http.Response, err error) { @@ -179,6 +196,44 @@ func (c *client) RoundTrip(req *http.Request) (resp *http.Response, err error) { return } +// WriteHeader records the value of the status code before writing it. +func (w *responseCapture) WriteHeader(code int) { + w.StatusCode = code + w.ResponseWriter.WriteHeader(code) +} + +// Write computes the written len and stores it in ContentLength. +func (w *responseCapture) Write(b []byte) (int, error) { + n, err := w.ResponseWriter.Write(b) + w.ContentLength += n + return n, err +} + +// Flush implements the http.Flusher interface if the underlying response +// writer supports it. +func (w *responseCapture) Flush() { + if f, ok := w.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + +// Push implements the http.Pusher interface if the underlying response +// writer supports it. +func (w *responseCapture) Push(target string, opts *http.PushOptions) error { + if p, ok := w.ResponseWriter.(http.Pusher); ok { + return p.Push(target, opts) + } + return errors.New("push not supported") +} + +// Hijack supports the http.Hijacker interface. +func (w *responseCapture) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if h, ok := w.ResponseWriter.(http.Hijacker); ok { + return h.Hijack() + } + return nil, nil, fmt.Errorf("response writer does not support hijacking: %T", w.ResponseWriter) +} + // from returns the client address from the request. func from(req *http.Request) string { if f := req.Header.Get("X-Forwarded-For"); f != "" { diff --git a/log/http_test.go b/log/http_test.go index 2907fd45..92d5fc53 100644 --- a/log/http_test.go +++ b/log/http_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/stretchr/testify/assert" - "goa.design/goa/v3/middleware" goa "goa.design/goa/v3/pkg" ) @@ -22,10 +21,12 @@ func TestHTTP(t *testing.T) { defer func() { timeNow = now }() timeSince = func(_ time.Time) time.Duration { return 42 * time.Millisecond } defer func() { timeSince = time.Since }() + shortID = func() string { return "test-request-id" } + defer func() { shortID = randShortID }() - prefix := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"request-id","msg":"start","http.method":"GET","http.url":"http://example.com","http.remote_addr":""}` - entry := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"request-id","key1":"value1","key2":"value2"}` - suffix := `{"time":"2022-01-09T20:29:45Z","level":"info","request-id":"request-id","msg":"end","http.method":"GET","http.url":"http://example.com","http.status":0,"http.time_ms":42,"http.bytes":0}` + prefix := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","msg":"start","http.method":"GET","http.url":"http://example.com","http.remote_addr":""}` + entry := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","key1":"value1","key2":"value2"}` + suffix := `{"time":"2022-01-09T20:29:45Z","level":"info","request_id":"test-request-id","msg":"end","http.method":"GET","http.url":"http://example.com","http.status":0,"http.time_ms":42,"http.bytes":0}` cases := []struct { name string @@ -56,9 +57,7 @@ func TestHTTP(t *testing.T) { handler = HTTP(ctx, c.opt)(handler) - requestIDCtx := context.WithValue(context.Background(), middleware.RequestIDKey, "request-id") //nolint:staticcheck req, _ := http.NewRequest("GET", "http://example.com", nil) - req = req.WithContext(requestIDCtx) handler.ServeHTTP(nil, req) diff --git a/log/keys.go b/log/keys.go index 5f0e5578..0b6c7a2e 100644 --- a/log/keys.go +++ b/log/keys.go @@ -1,9 +1,9 @@ package log var ( - TraceIDKey = "trace-id" - SpanIDKey = "span-id" - RequestIDKey = "request-id" + TraceIDKey = "trace_id" + SpanIDKey = "span_id" + RequestIDKey = "request_id" MessageKey = "msg" ErrorMessageKey = "err" TimestampKey = "time" diff --git a/log/span.go b/log/span.go new file mode 100644 index 00000000..d1ac6cb4 --- /dev/null +++ b/log/span.go @@ -0,0 +1,23 @@ +package log + +import ( + "context" + + "go.opentelemetry.io/otel/trace" +) + +// Log is a log key/value pair generator function that can be used to log trace +// and span IDs. Example: +// +// ctx := log.Context(ctx, WithFunc(trace.Log)) +// log.Printf(ctx, "message") +// +// Output: traceID= spanID= message +func Span(ctx context.Context) (kvs []KV) { + spanContext := trace.SpanFromContext(ctx).SpanContext() + if spanContext.IsValid() { + kvs = append(kvs, KV{K: TraceIDKey, V: spanContext.TraceID().String()}) + kvs = append(kvs, KV{K: SpanIDKey, V: spanContext.SpanID().String()}) + } + return +} diff --git a/log/span_test.go b/log/span_test.go new file mode 100644 index 00000000..1fbb6527 --- /dev/null +++ b/log/span_test.go @@ -0,0 +1,32 @@ +package log + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/trace" +) + +func TestSpan(t *testing.T) { + // Create a mock span context + traceID := trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + spanID := trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8} + spanContext := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }) + + // Create a context with the mock span context + ctx := trace.ContextWithSpanContext(context.Background(), spanContext) + + // Call the Span function + kvs := Span(ctx) + + // Assert that the expected key-value pairs are returned + assert.Equal(t, []KV{ + {K: TraceIDKey, V: "0102030405060708090a0b0c0d0e0f10"}, + {K: SpanIDKey, V: "0102030405060708"}, + }, kvs) +} diff --git a/metrics/README.md b/metrics/README.md deleted file mode 100644 index 117a77d0..00000000 --- a/metrics/README.md +++ /dev/null @@ -1,152 +0,0 @@ -# metrics: Auto Metrics - -[![Build Status](https://github.com/goadesign/clue/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/goadesign/clue/actions?query=branch%3Amain+event%3Apush) -[![Go Reference](https://pkg.go.dev/badge/goa.design/clue/metrics.svg)](https://pkg.go.dev/goa.design/clue/metrics) - -## Overview - -Package `metrics` provides a convenient way to add Prometheus metrics to Goa -services. The following example shows how to use the package. It implements an -illustrative `main` function for a fictional service `svc` implemented in the -package `github.com/repo/services/svc`. The service is assumed to expose both -HTTP and gRPC endpoints. - -```go -package main - -import ( - "context" - - "goa.design/clue/metrics" - "goa.design/clue/log" - goahttp "goa.design/goa/v3/http" - - "github.com/repo/services/svc" - httpsvrgen "github.com/repo/services/svc/gen/http/svc/server" - grpcsvrgen "github.com/repo/services/svc/gen/grpc/svc/server" - svcgen "github.com/repo/services/svc/gen/svc" -) - -func main() { - // Initialize the log context - ctx := log.With(log.Context(context.Background()), "svc", svcgen.ServiceName) - // Create the service (user code) - svc := svc.New(ctx) - // Wrap the service with Goa endpoints - endpoints := svcgen.NewEndpoints(svc) - - // Create HTTP server - mux := goahttp.NewMuxer() - httpsvr := httpsvrgen.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil) - httpsvrgen.Mount(mux, httpsvr) - - // ** Initialize context for metrics ** - ctx = metrics.Context(ctx, svcgen.ServiceName) - - // ** metrics HTTP endpoints ** - handler := metrics.HTTP(ctx)(mux) - - // Create gRPC server - grpcsvr := grpcsvrgen.New(endpoints, nil) - - // ** metrics gRPC endpoints ** - unaryInterceptor := metrics.UnaryServerInterceptor(ctx) - streamInterceptor := metrics.StreamServerInterceptor(ctx) - pbsvr := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor), grpc.StreamInterceptor(streamInterceptor)) - - // ** Mount the /metrics handler used by Prometheus to scrape metrics ** - mux.Handle("GET", "/metrics", metrics.Handler(ctx).ServeHTTP) - - // .... Start the servers .... -} -``` - -The `metrics` functions used to instrument the service are: - -* `HTTP`: creates a middleware that meters an HTTP handler. -* `UnaryServerInterceptor`: creates an interceptor that meters gRPC unary server methods. -* `StreamServerInterceptor`: creates an interceptor that meters gRPC stream server methods. -* `Handler`: creates a HTTP handler that exposes Prometheus metrics. - -## HTTP Metrics - -The middleware returned by the `HTTP` function creates the following metrics: - -* `http_server_duration`: Histogram of HTTP request durations in milliseconds. -* `http_server_active_requests`: Gauge of active HTTP requests. -* `http_server_request_size`: Histogram of HTTP request sizes in bytes. -* `http_server_response_size`: Histogram of HTTP response sizes in bytes. - -All the metrics have the following labels: - -* `goa_service`: The service name as specified in the Goa design. -* `http_verb`: The HTTP verb (`GET`, `POST` etc.). -* `http_host`: The value of the HTTP host header. -* `http_path`: The HTTP path. - -All the metrics but `http_server_active_requests` also have the following -additional labels: - -* `http_status_code`: The HTTP status code. - -## GRPC Metrics - -The `UnaryInterceptor` and `StreamInterceptor` functions create the following -metrics: - -* `rpc_server_duration`: Histogram of unary request durations in milliseconds. -* `rpc_server_active_requests`: Gauge of active unary and stream requests. -* `rpc_server_request_size`: Histogram of message sizes in bytes, per message for streaming RPCs. -* `rpc_server_response_size`: Histogram of response sizes in bytes, per message for streaming RPCs. -* `rpc_server_stream_message_size`: Histogram of message sizes in bytes, per message for streaming RPCs. -* `rpc_server_stream_response_size`: Histogram of response sizes in bytes, per message for streaming RPCs. - -All the metrics have the following labels: - -* `goa_service`: The service name as specified in the Goa design. -* `net_peer.addr`: The peer address. -* `rpc_method`: Full name of RPC method. - -All the metrics but `rpc_server_active_requests`, -`rpc_server_stream_message_size` and `rpc_rpc_server_stream_response_size` also -have the following additional labels: - -* `rpc_status_code`: The response status code. - -## Configuration - -### Histogram Buckets - -The histogram buckets can be specified using the `WithDurationBuckets`, -`WithRequestSizeBuckets` and `WithResponseSizeBuckets` options of the `Context` -function: - -```go -ctx = metrics.Context(ctx, svc.ServiceName, - metrics.WithDurationBuckets([]float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}), - metrics.WithRequestSizeBuckets([]float64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024}), - metrics.WithResponseSizeBuckets([]float64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024})) -``` - -The default bucket upper boundaries are: - -* `10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, +Inf` for request duration. -* `10, 100, 500, 1000, 5000, 10000, 50000, 100000, 1000000, 10000000, +Inf` for request and response size. - -### Prometheus Registry - -By default `metrics` uses the global Prometheus registry to create the -metrics and serve them. A user configured registerer can be specified when -creating the metrics via `WithRegisterer`: - -```go -ctx = metrics.Context(ctx, svc.ServiceName, metrics.WithRegisterer(registerer))(mux) -``` - -A user configured gatherer (used to collect the metrics) and registerer (used to -register metrics for the `/metrics` endpoint) can be specified when creating the -metrics handler via `WithGatherer` and `WithHandlerRegisterer` respectively: - -```go -handler = metrics.Handler(ctx, metrics.WithGatherer(gatherer), metrics.WithHandlerRegisterer(registerer)) -``` diff --git a/metrics/context.go b/metrics/context.go deleted file mode 100644 index b301a4ab..00000000 --- a/metrics/context.go +++ /dev/null @@ -1,239 +0,0 @@ -package metrics - -import ( - "context" - - "github.com/prometheus/client_golang/prometheus" -) - -type ( - // stateBag is stored in the context and used to initialize the - // appropriate metrics for this package HTTP middleware and gRPC - // interceptors. This state is only needed during initialization and is - // not intended to be kept in request contexts. - stateBag struct { - options *options - svc string - httpMetrics *httpMetrics - grpcMetrics *grpcMetrics - } - - // httpMetrics is the set of HTTP Metrics used by this package interceptors. - httpMetrics struct { - // Durations is a histogram of the duration of requests. - Durations *prometheus.HistogramVec - // RequestSizes is a histogram of the size of requests. - RequestSizes *prometheus.HistogramVec - // ResponseSizes is a histogram of the size of responses. - ResponseSizes *prometheus.HistogramVec - // ActiveRequests is a gauge of the number of active requests. - ActiveRequests *prometheus.GaugeVec - } - - // grpcMetrics is the set of gRPC Metrics used by this package interceptors. - grpcMetrics struct { - // Durations is a histogram of the duration of requests. - Durations *prometheus.HistogramVec - // RequestSizes is a histogram of the size of requests. - RequestSizes *prometheus.HistogramVec - // ResponseSizes is a histogram of the size of responses. - ResponseSizes *prometheus.HistogramVec - // ActiveRequests is a gauge of the number of active requests. - ActiveRequests *prometheus.GaugeVec - // StreamMessageSizes is a histogram of the size of messages sent on the stream. - StreamMessageSizes *prometheus.HistogramVec - // StreamResultSizes is a histogram of the size of results sent on the stream. - StreamResultSizes *prometheus.HistogramVec - } - - // Private type used to define context keys. - ctxKey int -) - -const ( - // metricHTTPDuration is the name of the HTTP duration metric. - metricHTTPDuration = "http_server_duration_ms" - // metricHTTPRequestSize is the name of the HTTP request size metric. - metricHTTPRequestSize = "http_server_request_size_bytes" - // metricHTTPResponseSize is the name of the HTTP response size metric. - metricHTTPResponseSize = "http_server_response_size_bytes" - // metricHTTPActiveRequests is the name of the HTTP active requests metric. - metricHTTPActiveRequests = "http_server_active_requests" - // metricRPCDuration is the name of the gRPC request duration metric. - metricRPCDuration = "rpc_server_duration_ms" - // metricRPCActiveRequests is the name of the gRPC active requests metric. - metricRPCActiveRequests = "rpc_server_active_requests" - // metricRPCRequestSize is the name of the gRPC request size metric. - metricRPCRequestSize = "rpc_server_request_size_bytes" - // metricRPCResponseSize is the name of the gRPC response size metric. - metricRPCResponseSize = "rpc_server_response_size_bytes" - // metricRPCStreamMessageSize is the name of the gRPC stream message size metric. - metricRPCStreamMessageSize = "rpc_server_stream_message_size_bytes" - // metricRPCStreamResponseSize is the name of the gRPC stream response size metric. - metricRPCStreamResponseSize = "rpc_server_stream_response_size_bytes" - // labelGoaService is the name of the label containing the Goa service name. - labelGoaService = "goa_service" - // labelHTTPVerb is the name of the label containing the HTTP verb. - labelHTTPVerb = "http_verb" - // labelHTTPHost is the name of the label containing the HTTP host. - labelHTTPHost = "http_host" - // labelHTTPPath is the name of the label containing the HTTP URL path. - labelHTTPPath = "http_path" - // labelHTTPStatusCode is the name of the label containing the HTTP status code. - labelHTTPStatusCode = "http_status_code" - // labelRPCService is the name of the RPC service label. - labelRPCService = "rpc_service" - // labelRPCMethod is the name of the RPC method label. - labelRPCMethod = "rpc_method" - // labelRPCStatusCode is the name of the RPC status code label. - labelRPCStatusCode = "rpc_status_code" -) - -const ( - // Context key used to capture request length. - ctxReqLen ctxKey = iota + 1 - // Context key used to store initialization state bag. - stateBagKey -) - -var ( - // httpLabels is the set of dynamic labels used for all metrics but - // MetricHTTPActiveRequests. - httpLabels = []string{labelHTTPVerb, labelHTTPHost, labelHTTPPath, labelHTTPStatusCode} - - // httpActiveRequestsLabels is the set of dynamic labels used for the - // MetricHTTPActiveRequests metric. - httpActiveRequestsLabels = []string{labelHTTPVerb, labelHTTPHost, labelHTTPPath} - - // rpcLabels is the default set of dynamic metric labels - rpcLabels = []string{labelRPCService, labelRPCMethod, labelRPCStatusCode} - - // NoCode is the set of dynamic labels used for active gRPC requests - // metric and stream message and result size metrics. - rpcNoCodeLabels = []string{labelRPCService, labelRPCMethod} -) - -// Context initializes the given context for the HTTP, UnaryInterceptor and -// StreamInterceptor functions. -func Context(ctx context.Context, svc string, opts ...Option) context.Context { - if m := ctx.Value(stateBagKey); m != nil { - return ctx - } - - options := defaultOptions() - for _, o := range opts { - o(options) - } - - return context.WithValue(ctx, stateBagKey, &stateBag{options: options, svc: svc}) -} - -func (state *stateBag) HTTPMetrics() *httpMetrics { - if state.httpMetrics != nil { - return state.httpMetrics - } - - durations := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricHTTPDuration, - Help: "Histogram of request durations in milliseconds.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.durationBuckets, - }, httpLabels) - state.options.registerer.MustRegister(durations) - - reqSizes := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricHTTPRequestSize, - Help: "Histogram of request sizes in bytes.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.requestSizeBuckets, - }, httpLabels) - state.options.registerer.MustRegister(reqSizes) - - respSizes := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricHTTPResponseSize, - Help: "Histogram of response sizes in bytes.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.responseSizeBuckets, - }, httpLabels) - state.options.registerer.MustRegister(respSizes) - - activeReqs := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: metricHTTPActiveRequests, - Help: "Gauge of active requests.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - }, httpActiveRequestsLabels) - state.options.registerer.MustRegister(activeReqs) - - state.httpMetrics = &httpMetrics{ - Durations: durations, - RequestSizes: reqSizes, - ResponseSizes: respSizes, - ActiveRequests: activeReqs, - } - - return state.httpMetrics -} - -func (state *stateBag) GRPCMetrics() *grpcMetrics { - if state.grpcMetrics != nil { - return state.grpcMetrics - } - - durations := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricRPCDuration, - Help: "Histogram of request durations in milliseconds.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.durationBuckets, - }, rpcLabels) - state.options.registerer.MustRegister(durations) - - reqSizes := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricRPCRequestSize, - Help: "Histogram of request sizes in bytes.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.requestSizeBuckets, - }, rpcLabels) - state.options.registerer.MustRegister(reqSizes) - - respSizes := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricRPCResponseSize, - Help: "Histogram of response sizes in bytes.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.responseSizeBuckets, - }, rpcLabels) - state.options.registerer.MustRegister(respSizes) - - streamMsgSizes := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricRPCStreamMessageSize, - Help: "Histogram of stream message sizes in bytes.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.requestSizeBuckets, - }, rpcNoCodeLabels) - state.options.registerer.MustRegister(streamMsgSizes) - - streamResSizes := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: metricRPCStreamResponseSize, - Help: "Histogram of stream response sizes in bytes.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - Buckets: state.options.responseSizeBuckets, - }, rpcNoCodeLabels) - state.options.registerer.MustRegister(streamResSizes) - - activeReqs := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: metricRPCActiveRequests, - Help: "Gauge of active requests.", - ConstLabels: prometheus.Labels{labelGoaService: state.svc}, - }, rpcNoCodeLabels) - state.options.registerer.MustRegister(activeReqs) - - state.grpcMetrics = &grpcMetrics{ - Durations: durations, - RequestSizes: reqSizes, - ResponseSizes: respSizes, - ActiveRequests: activeReqs, - StreamMessageSizes: streamMsgSizes, - StreamResultSizes: streamResSizes, - } - - return state.grpcMetrics -} diff --git a/metrics/context_test.go b/metrics/context_test.go deleted file mode 100644 index 577dfeda..00000000 --- a/metrics/context_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package metrics - -import ( - "context" - "testing" -) - -func TestContext(t *testing.T) { - svc := "testsvc" - optionList := []Option{WithDurationBuckets([]float64{0, 1, 2})} - optionStruct := defaultOptions() - optionStruct.durationBuckets = []float64{0, 1, 2} - otherOptionList := []Option{WithRequestSizeBuckets([]float64{0, 1, 2})} - ctx := Context(context.Background(), svc, optionList...) - cases := []struct { - name string - ctx context.Context - svc string - options []Option - want stateBag - }{ - {"empty", context.Background(), "", nil, stateBag{options: defaultOptions()}}, - {"with options", context.Background(), "", optionList, stateBag{options: optionStruct}}, - {"with options and service", context.Background(), svc, optionList, stateBag{options: optionStruct, svc: svc}}, - {"with initialized context", ctx, "some-svc", otherOptionList, stateBag{options: optionStruct, svc: svc}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := Context(c.ctx, c.svc, c.options...) - got := ctx.Value(stateBagKey).(*stateBag) - if got.svc != c.want.svc { - t.Errorf("unexpected svc: got %q, want %q", got.svc, c.want.svc) - } - if !sameOptions(got.options, c.want.options) { - t.Errorf("unexpected options: got %v, want %v", got.options, c.want.options) - } - }) - } -} - -func sameOptions(a, b *options) bool { - if a == nil && b == nil { - return true - } - if a == nil || b == nil { - return false - } - if a.durationBuckets != nil && b.durationBuckets != nil { - if len(a.durationBuckets) != len(b.durationBuckets) { - return false - } - for i := range a.durationBuckets { - if a.durationBuckets[i] != b.durationBuckets[i] { - return false - } - } - } - if a.requestSizeBuckets != nil && b.requestSizeBuckets != nil { - if len(a.requestSizeBuckets) != len(b.requestSizeBuckets) { - return false - } - for i := range a.requestSizeBuckets { - if a.requestSizeBuckets[i] != b.requestSizeBuckets[i] { - return false - } - } - } - return true -} diff --git a/metrics/grpc.go b/metrics/grpc.go deleted file mode 100644 index 32834439..00000000 --- a/metrics/grpc.go +++ /dev/null @@ -1,144 +0,0 @@ -package metrics - -import ( - "context" - "strconv" - "strings" - "time" - - "github.com/prometheus/client_golang/prometheus" - "google.golang.org/grpc" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" -) - -type ( - // streamWrapper wraps a grpc.ServerStream with prometheus metrics. - streamWrapper struct { - grpc.ServerStream - labels prometheus.Labels - reqSizes, respSizes *prometheus.HistogramVec - } -) - -// UnaryServerInterceptor creates a gRPC unary server interceptor that metricss the -// requests. The context must have been initialized with metrics.Context. The -// returned interceptor adds the following metrics: -// -// - `grpc.server.duration`: Histogram of request durations in milliseconds. -// - `grpc.server.active_requests`: UpDownCounter of active requests. -// - `grpc.server.request.size`: Histogram of request sizes in bytes. -// - `grpc.server.response.size`: Histogram of response sizes in bytes. -// -// All the metrics have the following labels: -// -// - `goa.method`: The method name as specified in the Goa design. -// - `goa.service`: The service name as specified in the Goa design. -// - `rpc.system`: A stream identifying the remoting system (e.g. `grpc`). -// - `rpc.service`: Name of RPC service. -// - `rpc.method`: Name of RPC method. -// - `rpc.status_code`: The response status code. -// -// Errors collecting or serving metrics are logged to the logger in the context -// if any. -func UnaryServerInterceptor(ctx context.Context) grpc.UnaryServerInterceptor { - b := ctx.Value(stateBagKey) - if b == nil { - panic("initialize context with Context first") - } - metrics := b.(*stateBag).GRPCMetrics() - - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - service, method := parseGRPCFullMethodName(info.FullMethod) - labels := prometheus.Labels{labelRPCMethod: method, labelRPCService: service} - metrics.ActiveRequests.With(labels).Add(1) - defer metrics.ActiveRequests.With(labels).Sub(1) - - now := time.Now() - resp, err := handler(ctx, req) - - st, _ := status.FromError(err) - labels[labelRPCStatusCode] = strconv.Itoa(int(st.Code())) - metrics.Durations.With(labels).Observe(float64(timeSince(now)) / float64(time.Millisecond)) - if msg, ok := req.(proto.Message); ok { - metrics.RequestSizes.With(labels).Observe(float64(proto.Size(msg))) - } - if msg, ok := resp.(proto.Message); ok { - metrics.ResponseSizes.With(labels).Observe(float64(proto.Size(msg))) - } - - return resp, err - } -} - -// StreamServerInterceptor creates a gRPC stream server interceptor that metricss the -// requests. The context must have been initialized with Context. The returned -// interceptor adds the following metrics: -// -// - `grpc.server.active_requests`: UpDownCounter of active requests. -// - `grpc.server.request.size`: Histogram of request sizes in bytes. -// - `grpc.server.response.size`: Histogram of response sizes in bytes. -// -// All the metrics have the following labels: -// -// - `goa.method`: The method name as specified in the Goa design. -// - `goa.service`: The service name as specified in the Goa design. -// - `rpc.system`: A stream identifying the remoting system (e.g. `grpc`). -// - `rpc.service`: Name of RPC service. -// - `rpc.method`: Name of RPC method. -// - `rpc.status_code`: The response status code. -// -// Errors collecting or serving metrics are logged to the logger in the context -// if any. -func StreamServerInterceptor(ctx context.Context) grpc.StreamServerInterceptor { - b := ctx.Value(stateBagKey) - if b == nil { - panic("metrics not found in context, initialize context with Context first") - } - metrics := b.(*stateBag).GRPCMetrics() - - return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - service, method := parseGRPCFullMethodName(info.FullMethod) - labels := prometheus.Labels{labelRPCMethod: method, labelRPCService: service} - metrics.ActiveRequests.With(labels).Add(1) - defer metrics.ActiveRequests.With(labels).Sub(1) - - now := time.Now() - wrapper := streamWrapper{stream, labels, metrics.StreamMessageSizes, metrics.StreamResultSizes} - err := handler(srv, &wrapper) - - st, _ := status.FromError(err) - labels[labelRPCStatusCode] = strconv.Itoa(int(st.Code())) - - metrics.Durations.With(labels).Observe(float64(timeSince(now)) / float64(time.Millisecond)) - - return err - } -} - -func (s *streamWrapper) RecvMsg(m interface{}) error { - if err := s.ServerStream.RecvMsg(m); err != nil { - return err - } - if msg, ok := m.(proto.Message); ok { - s.reqSizes.With(s.labels).Observe(float64(proto.Size(msg))) - } - return nil -} - -func (s *streamWrapper) SendMsg(m interface{}) error { - if msg, ok := m.(proto.Message); ok { - s.respSizes.With(s.labels).Observe(float64(proto.Size(msg))) - } - return s.ServerStream.SendMsg(m) -} - -func parseGRPCFullMethodName(fullMethodName string) (serviceName, methodName string) { - if idx := strings.LastIndex(fullMethodName, "."); idx >= 0 { - fullMethodName = fullMethodName[idx+1:] - } - if idx := strings.LastIndex(fullMethodName, "/"); idx > 0 { - return fullMethodName[:idx], fullMethodName[idx+1:] - } - return fullMethodName, "" -} diff --git a/metrics/grpc_test.go b/metrics/grpc_test.go deleted file mode 100644 index 5e7b97c8..00000000 --- a/metrics/grpc_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package metrics - -import ( - "context" - "strings" - "sync" - "testing" - "time" - - "goa.design/clue/internal/testsvc" - "google.golang.org/grpc" -) - -func TestUnaryServerInterceptorServerDuration(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - d time.Duration - expectedBucketCounts []int - }{ - {"fast", 1 * time.Millisecond, []int{1, 1}}, - {"slow", 100 * time.Millisecond, []int{0, 1}}, - {"very slow", 1000 * time.Millisecond, []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - restore := timeSince - defer func() { timeSince = restore }() - timeSince = func(time.Time) time.Duration { return c.d } - - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithDurationBuckets(buckets)) - uinter := UnaryServerInterceptor(ctx) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.UnaryInterceptor(uinter)), - testsvc.WithUnaryFunc(noopMethod())) - - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - stop() - reg.AssertHistogram(metricRPCDuration, rpcLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestUnaryServerInterceptorRequestSize(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - str string - expectedBucketCounts []int - }{ - {"small", "1", []int{1, 1}}, - {"large", strings.Repeat("1", 100), []int{0, 1}}, - {"very large", strings.Repeat("1", 1000), []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithRequestSizeBuckets(buckets)) - uinter := UnaryServerInterceptor(ctx) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.UnaryInterceptor(uinter)), - testsvc.WithUnaryFunc(noopMethod())) - - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{S: &c.str}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - stop() - reg.AssertHistogram(metricRPCRequestSize, rpcLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestUnaryServerInterceptorResponseSize(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - str string - expectedBucketCounts []int - }{ - {"small", "1", []int{1, 1}}, - {"large", strings.Repeat("1", 100), []int{0, 1}}, - {"very large", strings.Repeat("1", 1000), []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithResponseSizeBuckets(buckets)) - uinter := UnaryServerInterceptor(ctx) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.UnaryInterceptor(uinter)), - testsvc.WithUnaryFunc(stringMethod(c.str))) - - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - stop() - reg.AssertHistogram(metricRPCResponseSize, rpcLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestUnaryServerInterceptorActiveRequests(t *testing.T) { - cases := []struct { - name string - numReqs int - }{ - {"one", 1}, - {"ten", 10}, - {"one thousand", 1000}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg)) - uinter := UnaryServerInterceptor(ctx) - chstop := make(chan struct{}) - var running, done sync.WaitGroup - running.Add(c.numReqs) - done.Add(c.numReqs) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.UnaryInterceptor(uinter)), - testsvc.WithUnaryFunc(waitMethod(&running, &done, chstop))) - - for i := 0; i < c.numReqs; i++ { - go cli.GRPCMethod(context.Background(), &testsvc.Fields{}) // nolint: errcheck - } - - running.Wait() - reg.AssertGauge(metricRPCActiveRequests, rpcNoCodeLabels, c.numReqs) - close(chstop) - done.Wait() - stop() - }) - } -} - -func TestStreamServerInterceptorServerDuration(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - d time.Duration - expectedBucketCounts []int - }{ - {"fast", 1 * time.Millisecond, []int{1, 1}}, - {"slow", 100 * time.Millisecond, []int{0, 1}}, - {"very slow", 1000 * time.Millisecond, []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - restore := timeSince - defer func() { timeSince = restore }() - timeSince = func(time.Time) time.Duration { return c.d } - - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithDurationBuckets(buckets)) - sinter := StreamServerInterceptor(ctx) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.StreamInterceptor(sinter)), - testsvc.WithStreamFunc(echoMethod())) - - stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("unexpected stream error: %v", err) - } - if err := stream.Send(&testsvc.Fields{}); err != nil { - t.Errorf("unexpected send error: %v", err) - } - if _, err := stream.Recv(); err != nil { - t.Errorf("unexpected recv error: %v", err) - } - - stop() - reg.AssertHistogram(metricRPCDuration, rpcLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestStreamServerInterceptorMessageSize(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - str string - expectedBucketCounts []int - }{ - {"small", "1", []int{1, 1}}, - {"large", strings.Repeat("1", 100), []int{0, 1}}, - {"very large", strings.Repeat("1", 1000), []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithRequestSizeBuckets(buckets)) - sinter := StreamServerInterceptor(ctx) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.StreamInterceptor(sinter)), - testsvc.WithStreamFunc(echoMethod())) - - stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("unexpected stream error: %v", err) - } - if err := stream.Send(&testsvc.Fields{S: &c.str}); err != nil { - t.Errorf("unexpected send error: %v", err) - } - if _, err := stream.Recv(); err != nil { - t.Errorf("unexpected recv error: %v", err) - } - - stop() - reg.AssertHistogram(metricRPCStreamMessageSize, rpcNoCodeLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestStreamServerInterceptorResponseSize(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - str string - expectedBucketCounts []int - }{ - {"small", "1", []int{1, 1}}, - {"large", strings.Repeat("1", 100), []int{0, 1}}, - {"very large", strings.Repeat("1", 1000), []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithResponseSizeBuckets(buckets)) - sinter := StreamServerInterceptor(ctx) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.StreamInterceptor(sinter)), - testsvc.WithStreamFunc(echoMethod())) - - stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("unexpected stream error: %v", err) - } - if err := stream.Send(&testsvc.Fields{S: &c.str}); err != nil { - t.Errorf("unexpected send error: %v", err) - } - if _, err := stream.Recv(); err != nil { - t.Errorf("unexpected recv error: %v", err) - } - - stop() - reg.AssertHistogram(metricRPCStreamResponseSize, rpcNoCodeLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestStreamServerInterceptorActiveRequests(t *testing.T) { - cases := []struct { - name string - numReqs int - }{ - {"one", 1}, - {"ten", 10}, - {"one hundred", 100}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg)) - sinter := StreamServerInterceptor(ctx) - chstop := make(chan struct{}) - var running, done sync.WaitGroup - running.Add(c.numReqs) - done.Add(c.numReqs) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.StreamInterceptor(sinter)), - testsvc.WithStreamFunc(recvWaitMethod(&running, &done, chstop))) - - for i := 0; i < c.numReqs; i++ { - stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("unexpected stream error: %v", err) - } - go func() { - if err := stream.Send(&testsvc.Fields{}); err != nil { - t.Errorf("unexpected send error: %v", err) - } - }() - } - running.Wait() - reg.AssertGauge(metricRPCActiveRequests, rpcNoCodeLabels, c.numReqs) - close(chstop) - done.Wait() - stop() - }) - } -} - -func noopMethod() testsvc.UnaryFunc { - return func(_ context.Context, _ *testsvc.Fields) (*testsvc.Fields, error) { - return &testsvc.Fields{}, nil - } -} - -func stringMethod(str string) testsvc.UnaryFunc { - return func(_ context.Context, _ *testsvc.Fields) (*testsvc.Fields, error) { - return &testsvc.Fields{S: &str}, nil - } -} - -func waitMethod(running, done *sync.WaitGroup, stop chan struct{}) testsvc.UnaryFunc { - return func(_ context.Context, _ *testsvc.Fields) (*testsvc.Fields, error) { - running.Done() - defer done.Done() - <-stop - return &testsvc.Fields{}, nil - } -} - -func echoMethod() testsvc.StreamFunc { - return func(_ context.Context, stream testsvc.Stream) (err error) { - f, err := stream.Recv() - if err != nil { - return err - } - if err := stream.Send(f); err != nil { - return err - } - return stream.Close() - } -} - -func recvWaitMethod(running, done *sync.WaitGroup, stop chan struct{}) testsvc.StreamFunc { - return func(_ context.Context, stream testsvc.Stream) (err error) { - running.Done() - defer done.Done() - if _, err := stream.Recv(); err != nil { - return err - } - <-stop - return stream.Close() - } -} diff --git a/metrics/handler.go b/metrics/handler.go deleted file mode 100644 index 259f3d85..00000000 --- a/metrics/handler.go +++ /dev/null @@ -1,61 +0,0 @@ -package metrics - -import ( - "context" - "net/http" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -type ( - // handlerOption is a function that configures the handler. - handlerOption func(*handlerOptions) - - handlerOptions struct { - // registerer is the prometheus registerer. - registerer prometheus.Registerer - // gatherer is the prometheus gatherer. - gatherer prometheus.Gatherer - } -) - -// Handler returns a HTTP handler that collect metrics and serves them using the -// Prometheus export formats. It uses the context logger configured via -// micro/log if any to log errors. By default Handler uses the default -// prometheus registry to gather metrics and to register its own metrics. Use -// options WithGatherer and WithHandlerRegisterer to override the default values. -func Handler(ctx context.Context, opts ...handlerOption) http.Handler { - options := defaultHandlerOptions() - for _, o := range opts { - o(options) - } - return promhttp.InstrumentMetricHandler(options.registerer, promhttp.HandlerFor(options.gatherer, - promhttp.HandlerOpts{ - ErrorLog: logger{ctx}, - Registry: options.registerer, - })) -} - -// WithHandlerRegisterer returns an option that sets the prometheus registerer. -func WithHandlerRegisterer(registerer prometheus.Registerer) handlerOption { - return func(c *handlerOptions) { - c.registerer = registerer - } -} - -// WithGatherer returns an option that sets the prometheus gatherer used to -// collect the metrics. -func WithGatherer(gatherer prometheus.Gatherer) handlerOption { - return func(c *handlerOptions) { - c.gatherer = gatherer - } -} - -// defaultHandlerOptions returns a new HandlerOption struct with default values. -func defaultHandlerOptions() *handlerOptions { - return &handlerOptions{ - registerer: prometheus.DefaultRegisterer, - gatherer: prometheus.DefaultGatherer, - } -} diff --git a/metrics/handler_test.go b/metrics/handler_test.go deleted file mode 100644 index 9486b130..00000000 --- a/metrics/handler_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package metrics - -import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - dto "github.com/prometheus/client_model/go" - "goa.design/clue/log" -) - -func TestHandler(t *testing.T) { - errTest := errors.New("test") - cases := []struct { - name string - err error - expectedLog string - expectedResponse string - }{ - {"no error", nil, "", "test_metric 1"}, - {"error", errTest, errTest.Error(), ""}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - var buf bytes.Buffer - ctx := log.Context(context.Background(), - log.WithOutput(&buf), - log.WithFormat(func(e *log.Entry) []byte { return []byte(e.KeyVals[0].V.(string)) })) - reg := NewTestRegistry(t) - gat := &mockGatherer{err: c.err} - req, _ := http.NewRequest("GET", "/metrics", nil) - w := httptest.NewRecorder() - - Handler(ctx, WithGatherer(gat), WithHandlerRegisterer(reg)).ServeHTTP(w, req) - - resp := w.Result() - body, _ := io.ReadAll(resp.Body) - if c.err != nil { - if !strings.Contains(string(body), c.err.Error()) { - t.Errorf("expected error message %q, got %q", c.err.Error(), string(body)) - } - if w.Code != http.StatusInternalServerError { - t.Errorf("expected status code %d, got %d", http.StatusInternalServerError, w.Code) - } - if !strings.Contains(buf.String(), c.expectedLog) { - t.Errorf("expected log %q, got %q", c.expectedLog, buf.String()) - } - return - } - if buf.Len() > 0 { - t.Errorf("unexpected log message %q", buf.String()) - } - if !strings.Contains(string(body), c.expectedResponse) { - t.Errorf("expected response %q, got %q", c.expectedResponse, string(body)) - } - }) - } -} - -type mockGatherer struct { - err error -} - -func (m *mockGatherer) Gather() ([]*dto.MetricFamily, error) { - var ( - name = "test_metric" - one float64 = 1 - typ = dto.MetricType_COUNTER - ) - if m.err != nil { - return nil, m.err - } - return []*dto.MetricFamily{{ - Name: &name, - Type: &typ, - Metric: []*dto.Metric{{Counter: &dto.Counter{Value: &one}}}, - }}, nil -} diff --git a/metrics/http.go b/metrics/http.go deleted file mode 100644 index 725c25d4..00000000 --- a/metrics/http.go +++ /dev/null @@ -1,118 +0,0 @@ -package metrics - -import ( - "context" - "io" - "net/http" - "strconv" - "time" - - "github.com/prometheus/client_golang/prometheus" - "goa.design/goa/v3/http/middleware" -) - -type ( - // lengthReader is a wrapper around an io.ReadCloser that keeps track of how - // much data has been read. - lengthReader struct { - Source io.ReadCloser - ctx context.Context - } -) - -// Be kind to tests -var timeSince = time.Since - -// HTTP returns a middlware that metricss requests. The context must have -// been initialized with Context. HTTP collects the following metrics: -// -// - `http.server.duration`: Histogram of request durations in milliseconds. -// - `http.server.active_requests`: UpDownCounter of active requests. -// - `http.server.request.size`: Histogram of request sizes in bytes. -// - `http.server.response.size`: Histogram of response sizes in bytes. -// -// All the metrics have the following labels: -// -// - `http.verb`: The HTTP verb (`GET`, `POST` etc.). -// - `http.host`: The value of the HTTP host header. -// - `http.path`: The HTTP path. -// - `http.status_code`: The HTTP status code. -// -// Errors collecting or serving metrics are logged to the logger in the context -// if any. -func HTTP(ctx context.Context) func(http.Handler) http.Handler { - b := ctx.Value(stateBagKey) - if b == nil { - panic("initialize context with Context first") - } - metrics := b.(*stateBag).HTTPMetrics() - resolver := b.(*stateBag).options.resolver - - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var route string - if resolver != nil { - route = resolver(req) - } else { - route = req.URL.Path - } - labels := prometheus.Labels{ - labelHTTPVerb: req.Method, - labelHTTPHost: req.Host, - labelHTTPPath: route, - } - metrics.ActiveRequests.With(labels).Add(1) - defer metrics.ActiveRequests.With(labels).Sub(1) - - now := time.Now() - rw := middleware.CaptureResponse(w) - ctx, body := newLengthReader(req.Body, req.Context()) - req.Body = body - req = req.WithContext(ctx) - - h.ServeHTTP(rw, req) - - labels[labelHTTPStatusCode] = strconv.Itoa(rw.StatusCode) - - reqLength := req.Context().Value(ctxReqLen).(*int) - metrics.Durations.With(labels).Observe(float64(timeSince(now).Milliseconds())) - metrics.RequestSizes.With(labels).Observe(float64(*reqLength)) - metrics.ResponseSizes.With(labels).Observe(float64(rw.ContentLength)) - }) - } -} - -// So we have to do a little dance to get the length of the request body. We -// can't just simply wrap the body and sum up the length on each read because -// otel sets its own wrapper which means we can't cast the request back after -// the call to the next handler. We thus store the computed length in the -// context instead. -func newLengthReader(body io.ReadCloser, ctx context.Context) (context.Context, *lengthReader) { - reqLen := 0 - ctx = context.WithValue(ctx, ctxReqLen, &reqLen) - return ctx, &lengthReader{body, ctx} -} - -func (r *lengthReader) Read(b []byte) (int, error) { - n, err := r.Source.Read(b) - l := r.ctx.Value(ctxReqLen).(*int) - *l += n - - return n, err -} - -func (r *lengthReader) Close() error { - var buf [32]byte - var n int - var err error - for err == nil { - n, err = r.Source.Read(buf[:]) - l := r.ctx.Value(ctxReqLen).(*int) - *l += n - } - closeerr := r.Source.Close() - if err != nil && err != io.EOF { - return err - } - return closeerr -} diff --git a/metrics/http_test.go b/metrics/http_test.go deleted file mode 100644 index 21378d7f..00000000 --- a/metrics/http_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package metrics - -import ( - "context" - "io" - "strings" - "sync" - "testing" - "time" - - "goa.design/clue/internal/testsvc" -) - -func TestHTTPServerDuration(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - d time.Duration - expectedBucketCounts []int - }{ - {"fast", 1 * time.Millisecond, []int{1, 1}}, - {"slow", 100 * time.Millisecond, []int{0, 1}}, - {"very slow", 1000 * time.Millisecond, []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - restore := timeSince - defer func() { timeSince = restore }() - timeSince = func(time.Time) time.Duration { return c.d } - - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithDurationBuckets(buckets)) - middleware := HTTP(ctx) - cli, stop := testsvc.SetupHTTP(t, - testsvc.WithHTTPMiddleware(middleware), - testsvc.WithHTTPFunc(noopMethod())) - _, err := cli.HTTPMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - stop() - reg.AssertHistogram(metricHTTPDuration, httpLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestHTTPRequestSize(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - str string - expectedBucketCounts []int - }{ - {"small", "1", []int{1, 1}}, - {"large", strings.Repeat("1", 100), []int{0, 1}}, - {"very large", strings.Repeat("1", 1000), []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithRequestSizeBuckets(buckets)) - middleware := HTTP(ctx) - cli, stop := testsvc.SetupHTTP(t, - testsvc.WithHTTPMiddleware(middleware), - testsvc.WithHTTPFunc(noopMethod())) - - _, err := cli.HTTPMethod(context.Background(), &testsvc.Fields{S: &c.str}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - stop() - reg.AssertHistogram(metricHTTPRequestSize, httpLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestHTTPResponseSize(t *testing.T) { - buckets := []float64{10, 110} - cases := []struct { - name string - str string - expectedBucketCounts []int - }{ - {"small", "1", []int{1, 1}}, - {"large", strings.Repeat("1", 100), []int{0, 1}}, - {"very large", strings.Repeat("1", 1000), []int{0, 0}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg), WithResponseSizeBuckets(buckets)) - middleware := HTTP(ctx) - cli, stop := testsvc.SetupHTTP(t, - testsvc.WithHTTPMiddleware(middleware), - testsvc.WithHTTPFunc(stringMethod(c.str))) - - _, err := cli.HTTPMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - stop() - reg.AssertHistogram(metricHTTPResponseSize, httpLabels, 1, c.expectedBucketCounts) - }) - } -} - -func TestHTTPActiveRequests(t *testing.T) { - cases := []struct { - name string - numReqs int - }{ - {"one", 1}, - {"ten", 10}, - {"one hundred", 100}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - reg := NewTestRegistry(t) - ctx := Context(context.Background(), "testsvc", WithRegisterer(reg)) - middleware := HTTP(ctx) - chstop := make(chan struct{}) - var running, done sync.WaitGroup - running.Add(c.numReqs) - done.Add(c.numReqs) - cli, stop := testsvc.SetupHTTP(t, - testsvc.WithHTTPMiddleware(middleware), - testsvc.WithHTTPFunc(waitMethod(&running, &done, chstop))) - - for i := 0; i < c.numReqs; i++ { - go func() { - _, err := cli.HTTPMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - }() - } - - running.Wait() - reg.AssertGauge(metricHTTPActiveRequests, httpActiveRequestsLabels, c.numReqs) - close(chstop) - done.Wait() - stop() - }) - } -} - -func TestLengthReader(t *testing.T) { - cases := []struct { - name string - str string - expectedSize int - }{ - {"empty", "", 0}, - {"one", "1", 1}, - {"ten", strings.Repeat("1", 10), 10}, - {"one hundred", strings.Repeat("1", 100), 100}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - r := strings.NewReader(c.str) - ctx, lr := newLengthReader(io.NopCloser(r), context.Background()) - n, err := lr.Read(make([]byte, 100)) - if err != nil && err != io.EOF { - t.Errorf("unexpected error: %v", err) - } - if n != c.expectedSize { - t.Errorf("expected %d bytes, got %d", c.expectedSize, n) - } - length := ctx.Value(ctxReqLen) - if length == nil { - t.Fatal("expected length to be set in context") - } - if *(length.(*int)) != c.expectedSize { - t.Errorf("expected %d bytes, got %d", c.expectedSize, *(length.(*int))) - } - err = lr.Close() - if err != nil { - t.Errorf("unexpected close error: %v", err) - } - }) - } -} diff --git a/metrics/logger.go b/metrics/logger.go deleted file mode 100644 index af716eb3..00000000 --- a/metrics/logger.go +++ /dev/null @@ -1,19 +0,0 @@ -package metrics - -import ( - "context" - "fmt" - - "goa.design/clue/log" -) - -// clue/log to prometheus logger adapter. -type logger struct { - context.Context -} - -// Implements the promhttp.Logger interface. -func (l logger) Println(v ...interface{}) { - msg := fmt.Sprintln(v...) - log.Printf(l, msg) -} diff --git a/metrics/logger_test.go b/metrics/logger_test.go deleted file mode 100644 index 40e75cc4..00000000 --- a/metrics/logger_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package metrics - -import ( - "bytes" - "context" - "testing" - - "goa.design/clue/log" -) - -func TestLogger(t *testing.T) { - data := struct { - foo string - bar int - }{"foo", 1} - cases := []struct { - name string - vals []interface{} - expected string - }{ - {"empty", []interface{}{}, "\n"}, - {"one", []interface{}{"one"}, "one\n"}, - {"many", []interface{}{"one", 2, data}, "one 2 {foo 1}\n"}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - var buf bytes.Buffer - ctx := log.Context(context.Background(), - log.WithOutput(&buf), - log.WithFormat(func(e *log.Entry) []byte { return []byte(e.KeyVals[0].V.(string)) })) - l := logger{ctx} - l.Println(c.vals...) - if buf.String() != c.expected { - t.Errorf("expected %q, got %q", c.expected, buf.String()) - } - }) - } -} diff --git a/metrics/options.go b/metrics/options.go deleted file mode 100644 index 3aee2417..00000000 --- a/metrics/options.go +++ /dev/null @@ -1,88 +0,0 @@ -package metrics - -import ( - "net/http" - - "github.com/prometheus/client_golang/prometheus" -) - -type ( - // Option is a function that configures the metricsation. - Option func(*options) - - // RouteResolver is a function that resolves the route of a request used - // to label metrics. Using a route resolver makes it possible to label - // all routes matching a pattern with the same label. As an example - // services using the github.com/go-chi/chi/v5 muxer can use - // chi.RouteContext(r.Context()).RoutePattern(). - RouteResolver func(r *http.Request) string - - // options contains the configuration for the metrics. - options struct { - // durationBuckets is the buckets for the request duration histogram. - durationBuckets []float64 - // requestSizeBuckets is the buckets for the request size histogram. - requestSizeBuckets []float64 - // responseSizeBuckets is the buckets for the response size histogram. - responseSizeBuckets []float64 - // Prometheus registerer - registerer prometheus.Registerer - // RouteResolver is used to label metrics. - resolver RouteResolver - } -) - -var ( - DefaultDurationBuckets = []float64{10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000} - DefaultRequestSizeBuckets = []float64{10, 100, 500, 1000, 5000, 10000, 50000, 100000, 1000000, 10000000} - DefaultResponseSizeBuckets = []float64{10, 100, 500, 1000, 5000, 10000, 50000, 100000, 1000000, 10000000} -) - -// defaultOptions returns a new options struct with default values. -func defaultOptions() *options { - return &options{ - durationBuckets: DefaultDurationBuckets, - requestSizeBuckets: DefaultRequestSizeBuckets, - responseSizeBuckets: DefaultResponseSizeBuckets, - registerer: prometheus.DefaultRegisterer, - } -} - -// WithRouteResolver returns an option that sets the route resolver used to -// label metrics. The default uses the request path. -func WithRouteResolver(resolver RouteResolver) Option { - return func(o *options) { - o.resolver = resolver - } -} - -// WithDurationBuckets returns an option that sets the duration buckets for the -// request duration histogram. -func WithDurationBuckets(buckets []float64) Option { - return func(c *options) { - c.durationBuckets = buckets - } -} - -// WithRequestSizeBuckets returns an option that sets the request size buckets -// for the request size histogram. -func WithRequestSizeBuckets(buckets []float64) Option { - return func(c *options) { - c.requestSizeBuckets = buckets - } -} - -// WithResponseSizeBuckets returns an option that sets the response size buckets -// for the response size histogram. -func WithResponseSizeBuckets(buckets []float64) Option { - return func(c *options) { - c.responseSizeBuckets = buckets - } -} - -// WithRegisterer returns an option that sets the prometheus registerer. -func WithRegisterer(registerer prometheus.Registerer) Option { - return func(c *options) { - c.registerer = registerer - } -} diff --git a/metrics/options_test.go b/metrics/options_test.go deleted file mode 100644 index 8322f3d1..00000000 --- a/metrics/options_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package metrics - -import ( - "fmt" - "net/http" - "testing" - - "github.com/prometheus/client_golang/prometheus" -) - -func TestOptions(t *testing.T) { - var ( - durationBuckets = []float64{1} - requestSizeBuckets = []float64{1} - responseSizeBuckets = []float64{1} - registerer = NewTestRegistry(t) - resolver = func(_ *http.Request) string { return "test" } - ) - options := defaultOptions() - assertOptions(t, options, DefaultDurationBuckets, DefaultRequestSizeBuckets, DefaultResponseSizeBuckets, prometheus.DefaultRegisterer, nil) - - WithDurationBuckets(durationBuckets)(options) - assertOptions(t, options, durationBuckets, DefaultRequestSizeBuckets, DefaultResponseSizeBuckets, prometheus.DefaultRegisterer, nil) - - WithRequestSizeBuckets(requestSizeBuckets)(options) - assertOptions(t, options, durationBuckets, requestSizeBuckets, DefaultResponseSizeBuckets, prometheus.DefaultRegisterer, nil) - - WithResponseSizeBuckets(responseSizeBuckets)(options) - assertOptions(t, options, durationBuckets, requestSizeBuckets, responseSizeBuckets, prometheus.DefaultRegisterer, nil) - - WithRegisterer(registerer)(options) - assertOptions(t, options, durationBuckets, requestSizeBuckets, responseSizeBuckets, registerer, nil) - - WithRouteResolver(resolver)(options) - assertOptions(t, options, durationBuckets, requestSizeBuckets, responseSizeBuckets, registerer, resolver) -} - -func assertOptions(t *testing.T, options *options, durationBuckets []float64, requestSizeBuckets []float64, responseSizeBuckets []float64, registerer prometheus.Registerer, resolver RouteResolver) { - if !equal(options.durationBuckets, durationBuckets) { - t.Errorf("got %v, expected %v", options.durationBuckets, durationBuckets) - } - if !equal(options.requestSizeBuckets, requestSizeBuckets) { - t.Errorf("got %v, expected %v", options.requestSizeBuckets, requestSizeBuckets) - } - if !equal(options.responseSizeBuckets, responseSizeBuckets) { - t.Errorf("got %v, expected %v", options.responseSizeBuckets, responseSizeBuckets) - } - if options.registerer != registerer { - t.Errorf("got %v, expected %v", options.registerer, registerer) - } - if options.resolver == nil { - if resolver != nil { - t.Errorf("got nil, expected %v", resolver) - } - return - } - if resolver == nil { - t.Errorf("got %v, expected nil", options.resolver) - return - } - if fmt.Sprintf("%+v", options.resolver) != fmt.Sprintf("%+v", resolver) { - t.Errorf("got %v, expected %v", options.resolver, resolver) - } -} - -func equal(a, b []float64) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if a[i] != b[i] { - return false - } - } - return true -} diff --git a/metrics/prom_test.go b/metrics/prom_test.go deleted file mode 100644 index 0bf5d2eb..00000000 --- a/metrics/prom_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package metrics - -import ( - "testing" - - "github.com/prometheus/client_golang/prometheus" - io_prometheus_client "github.com/prometheus/client_model/go" -) - -type Registry struct { - *prometheus.Registry - t *testing.T -} - -var _ prometheus.Registerer = (*Registry)(nil) - -func NewTestRegistry(t *testing.T) *Registry { - return &Registry{prometheus.NewRegistry(), t} -} - -// AssertGauge validates that the gauge with the given name and labels exists -// and has the given value. -func (r *Registry) AssertGauge(name string, labels []string, value int) { - metric := r.findMetric(name, labels) - if metric.Gauge == nil { - r.t.Errorf("gauge %q with labels %v not found", name, labels) - return - } - var val float64 - if metric.Gauge.Value != nil { - val = *metric.Gauge.Value - } - if float64(value) != val { - r.t.Errorf("gauge %q with labels %v has value %v, want %v", name, labels, val, value) - } -} - -// AssertHistogram validates that the histogram with the given name and given -// labels exists and has the given sample count and cumulative counts for the -// given buckets. -func (r *Registry) AssertHistogram(name string, labels []string, sampleCount int, bucketCumulativeCount []int) { - metric := r.findMetric(name, labels) - if metric.Histogram == nil { - r.t.Errorf("histogram %s with labels %v not found", name, labels) - return - } - count := 0 - if metric.Histogram.SampleCount != nil { - count = int(*metric.Histogram.SampleCount) - } - if count != sampleCount { - r.t.Errorf("histogram %s with labels %v has sample count %d, want %d", name, labels, count, sampleCount) - } - if len(metric.Histogram.Bucket) != len(bucketCumulativeCount) { - r.t.Fatalf("histogram %s with labels %v has %d buckets, want %d", name, labels, len(metric.Histogram.Bucket), len(bucketCumulativeCount)) - } - for i, b := range metric.Histogram.Bucket { - count := 0 - if b.CumulativeCount != nil { - count = int(*b.CumulativeCount) - } - if count != bucketCumulativeCount[i] { - r.t.Errorf("histogram %s with labels %v has bucket %d cumulative count %d, want %d", name, labels, i, count, bucketCumulativeCount[i]) - } - } -} - -// findMetric finds a metric in the registry with the given name and labels. -func (r *Registry) findMetric(name string, labels []string) *io_prometheus_client.Metric { - families, err := r.Gather() - if err != nil { - r.t.Errorf("failed to gather metrics: %v", err) - } - var metrics *io_prometheus_client.Metric -loop: - for _, family := range families { - if family.Name == nil || *family.Name != name { - continue - } - for _, m := range family.Metric { - if !hasLabels(m.Label, labels) { - continue - } - metrics = m - break loop - } - } - if metrics == nil { - r.t.Fatalf("histogram %s with labels %v not found", name, labels) - } - return metrics -} - -// hasLabels returns true if the given label names are a subset of the given -// prometheus label names. -func hasLabels(promlabels []*io_prometheus_client.LabelPair, names []string) bool { - for _, lbl := range names { - found := false - for _, plbl := range promlabels { - if plbl.Name == nil { - continue - } - if *plbl.Name == lbl { - found = true - break - } - } - if !found { - return false - } - } - return true -} diff --git a/trace/README.md b/trace/README.md deleted file mode 100644 index a9b395bd..00000000 --- a/trace/README.md +++ /dev/null @@ -1,204 +0,0 @@ -# trace: Simple Request Tracing - -[![Build Status](https://github.com/goadesign/clue/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/goadesign/clue/actions?query=branch%3Amain+event%3Apush) -[![Go Reference](https://pkg.go.dev/badge/goa.design/clue/trace.svg)](https://pkg.go.dev/goa.design/clue/trace) - -## Overview - -Package `trace` provides request tracing functionality that follows the -[OpenTelemetry](https://opentelemetry.io/) specification. The package is -designed to be used in conjunction with [Goa](https://goa.design/). In -particular it is aware of the Goa RequestID -[HTTP](https://github.com/goadesign/goa/blob/v3/http/middleware/requestid.go) -and -[gRPC](https://github.com/goadesign/goa/blob/v3/grpc/middleware/requestid.go) -middlewares and adds both the Goa service name and current request ID to the -span attributes. - -The package uses an adaptive sampler that is configured to sample at a given -maximum number of request per seconds (2 per default). Using a time based rate -rather than e.g. a fixed percentage rate allows the sampler to adapt to the load -of the service. - -## Usage - -The following example shows how to use the package. It implements an -illustrative `main` function for a fictional service `svc` implemented in the -package `github.com/repo/services/svc` - -```go -package main - -import ( - "context" - - "goa.design/clue/log" - "goa.design/clue/trace" - goahttp "goa.design/goa/v3/http" - - "github.com/repo/services/svc" - httpsvrgen "github.com/repo/services/svc/gen/http/svc/server" - grpcsvrgen "github.com/repo/services/svc/gen/grpc/svc/server" - svcgen "github.com/repo/services/svc/gen/svc" -) - -func main() { - // Initialize the log context - ctx := log.With(log.Context(context.Background()), "svc", svcgen.ServiceName) - // Create the service (user code) - svc := svc.New(ctx) - // Wrap the service with Goa endpoints - endpoints := svcgen.NewEndpoints(svc) - - // Create HTTP server - mux := goahttp.NewMuxer() - httpsvr := httpsvrgen.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil) - httpsvrgen.Mount(mux, httpsvr) - - // ** Initialize context for tracing ** - conn, err := grpc.DialContext(ctx, "localhost:6831") // Remote span collector address - if err != nil { - log.Error(ctx, "unable to connect to span collector", "err", err) - os.Exit(1) - } - ctx = trace.Context(ctx, svcgen.ServiceName, trace.WithGRPCExporter(conn)) - - // ** Trace HTTP requests ** - handler := trace.HTTP(ctx)(mux) - - // Create gRPC server - grpcsvr := grpcsvrgen.New(endpoints, nil) - - // ** Trace gRPC requests ** - u := trace.UnaryServerTrace(ctx) - s := trace.StreamServerTrace(ctx) - pbsvr := grpc.NewServer(grpc.UnaryInterceptor(u), grpc.StreamInterceptor(s)) - - // ... -} -``` - -### Making Requests to Downstream Dependencies - -For tracing to work appropriately all clients to downstream dependencies must be -configured using the appropriate trace package function. - -For HTTP dependencies the trace package provides a `Client` function that can be -used to configure a `http.RoundTripper` to trace all requests made through it. -`Client` panics if the context hasn't be initialized with `trace.Context`. - -```go -// Create a tracing HTTP client -c := &http.Client{Transport: trace.Client(ctx, http.DefaultTransport)} -``` - -For gRPC dependencies the trace package provides the `UnaryClientTrace` and -`StreamClientTrace` interceptors that can be used when making gRPC calls. These -functions will create a span for the current request if it is traced. Example: - -```go -// Create a tracing client for gRPC unary calls -conn, err := grpc.Dial(url, grpc.WithUnaryInterceptor(UnaryClientTrace(ctx))) - -// Create a tracing client for gRPC stream calls -conn, err := grpc.Dial(url, grpc.WithStreamInterceptor(StreamClientTrace(ctx))) -``` - -### Creating Additional Spans - -Once configured the trace package automatically creates spans for a sample of -incoming requests. The function `IsTraced` can be used to determine if the -current request is being traced. - -The trace package also provides a `StartSpan` function that can be used to -create a new child span. The caller must also call `EndSpan` when the span is -complete. Both functions do nothing if the current request is not being traced -or the context has not been initialized with `trace.Context`. - -```go -func (s *svc) DoSomething(ctx context.Context, req *svcgen.DoSomethingRequest) (*svcgen.DoSomethingResponse, error) { - // ... - // Create a child span to measure the time taken to run an intensive - // operation. - ctx = trace.StartSpan(ctx, "DoSomethingIntense") - DoSomethingIntense(ctx) - trace.EndSpan(ctx) - // ... -} -``` - -### Adding Attributes to Spans - -Attributes decorate spans and add contextual information to the trace. By default -this package adds the following attributes to HTTP requests: - -* `http.scheme`: The HTTP scheme (`http` or `https`). -* `http.host`: The host name of the request if available in the `Host` field. -* `http.flavor`: The flavor of the request (`1.0`, `1.1` or `2`). -* `http.method`: The HTTP method of the request. -* `http.target`: The URI of the request. -* `http.client_id`: The client IP present in the `X-Forwarded-For` header if any. -* `http.user_agent`: The user agent of the request if any. -* `http.request_content_length`: The length of the request body if any. -* `enduser.id`: The request basic auth username if any. -* `net.transport`: One of `ip_tcp`, `ip_udp`, `ip`, `unix` or `other`. -* `net.peer.ip`, `net.peer.name`, `net.peer.port`: The IP address, port and name - of the remote peer if available in the request `RemoteAddr` field. -* `net.host.ip`, `net.host.name`, `net.host.port`: The IP address, port and name - of the remote host if available in the request `Host` field, the request `Host` - header or the request URL. - -and the following attributes to gRPC requests: - -* `rpc.system`: always set to `grpc`. -* `rpc.service`: The gRPC service name. -* `rpc.method`: The gRPC method name. -* `net.peer.ip`, `net.peer.port`: The IP address and port of the remote peer. - -Service method logic can add attributes when creating new spans via the -`WithAttributes` option. Custom attributes can also be added later on with -`SetSpanAttributes`. `SetSpanAttributes` does nothing if the request is not -traced or the context not initialized with `trace.Context`. - -```go -// Create a child span with attributes -ctx = trace.StartSpan(ctx, "DoSomething", trace.WithAttributes( - "key1", "value1", - "key2", "value2", -)) - -// Add a custom attribute to the current span -trace.SetSpanAttributes(ctx, "custom_attribute", "value") -``` - -### Adding Events - -The `AddEvent` function makes it possible to attach events to a span. Events are -useful to trace operations that are too fast to have their own span. For -example, the completion of an asynchronous operation. Attributes can be added to -the event to add contextual information. `AddEvent` does nothing if the request -is not traced or the context not initialized with `trace.Context`. - -```go -// Add an event to the current span -trace.AddEvent(ctx, "operation completed", "operation_id", operationID, "status", status) -``` - -### Span Status And Error - -The `Succeed`, `Fail` and `RecordError` functions can be used to set the status -and error of a span. The status indicates the success or failure of the -operation. The error is used to record the error that occurred if any. Note -that recording an error does not automatically change the status of the span. -The functions do nothing if the request is not traced or the context not -initialized with `trace.Context`. The default status of a completed span is -success. - -```go -// Set the status of the current span to success (default) -trace.Succeed(ctx) - -// Record an error in the current span and set the status to failure -trace.RecordError(ctx, err) -trace.Fail(ctx, "operation failed") -``` diff --git a/trace/context.go b/trace/context.go deleted file mode 100644 index 018d2313..00000000 --- a/trace/context.go +++ /dev/null @@ -1,108 +0,0 @@ -package trace - -import ( - "context" - "errors" - - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - "go.opentelemetry.io/otel/trace" - "go.opentelemetry.io/otel/trace/noop" -) - -type ( - // ctxKey is a private type used to store the tracer provider in the context. - ctxKey int - - // stateBag tracks the provider, tracer and active span sequence for a request. - stateBag struct { - svc string - provider trace.TracerProvider - propagator propagation.TextMapPropagator - tracer trace.Tracer - spans []trace.Span - } -) - -const ( - // InstrumentationLibraryName is the name of the instrumentation library. - InstrumentationLibraryName = "goa.design/clue" - - // AttributeRequestID is the name of the span attribute that contains the - // request ID. - AttributeRequestID = "request.id" -) - -const ( - // stateKey is used to store the tracing state the context. - stateKey ctxKey = iota + 1 -) - -// Context initializes the context so it can be used to create traces. -func Context(ctx context.Context, svc string, opts ...TraceOption) (context.Context, error) { - options := defaultOptions() - for _, o := range opts { - err := o(ctx, options) - if err != nil { - return nil, err - } - } - - if options.disabled { - return withConfig(ctx, noop.NewTracerProvider(), options.propagator, svc), nil - } - - if options.exporter == nil { - return nil, errors.New("missing exporter, set one with the option 'trace.WithExporter'") - } - - res := options.resource - if res == nil { - res = resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceNameKey.String(svc)) - } - - rootSampler := adaptiveSampler(options.maxSamplingRate, options.sampleSize) - provider := sdktrace.NewTracerProvider( - sdktrace.WithSampler(sdktrace.ParentBased(rootSampler, options.parentSamplerOptions...)), - sdktrace.WithResource(res), - sdktrace.WithBatcher(options.exporter), - ) - return withConfig(ctx, provider, options.propagator, svc), nil -} - -// IsTraced returns true if the current request is traced. -func IsTraced(ctx context.Context) bool { - span := trace.SpanFromContext(ctx) - return span.IsRecording() && span.SpanContext().IsSampled() -} - -// TraceProvider returns the underlying otel trace provider. -func TraceProvider(ctx context.Context) trace.TracerProvider { - sb := ctx.Value(stateKey).(*stateBag) - return sb.provider -} - -// withConfig stores the clue tracing config in the context. -func withConfig(ctx context.Context, provider trace.TracerProvider, propagator propagation.TextMapPropagator, svc string) context.Context { - return context.WithValue(ctx, stateKey, &stateBag{provider: provider, propagator: propagator, svc: svc}) -} - -// withTracing initializes the tracing context, ctx must have been initialized -// with withProvider and the request must be traced by otel. -func withTracing(traceCtx, ctx context.Context) context.Context { - state := traceCtx.Value(stateKey).(*stateBag) - svc := state.svc - provider := state.provider - propagator := state.propagator - tracer := provider.Tracer(InstrumentationLibraryName) - spans := []trace.Span{trace.SpanFromContext(ctx)} - return context.WithValue(ctx, stateKey, &stateBag{ - svc: svc, - provider: provider, - propagator: propagator, - tracer: tracer, - spans: spans, - }) -} diff --git a/trace/context_test.go b/trace/context_test.go deleted file mode 100644 index 0044799a..00000000 --- a/trace/context_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package trace - -import ( - "context" - "testing" - - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" - "go.opentelemetry.io/otel/trace" -) - -func testContext(provider trace.TracerProvider) context.Context { - return withConfig(context.Background(), provider, propagation.TraceContext{}, "test") -} - -func TestContext(t *testing.T) { - // We don't want to test otel, keep it simple... - exporter := tracetest.NewInMemoryExporter() - ctx, err := Context(context.Background(), "test", - WithMaxSamplingRate(3), WithSampleSize(20), WithExporter(exporter), - WithResource(&resource.Resource{}), WithParentSamplerOptions(sdktrace.WithRemoteParentSampled(nil)), - ) - if err != nil { - t.Fatal(err) - } - s := ctx.Value(stateKey) - if s == nil { - t.Fatal("expected state in context") - } - st, ok := s.(*stateBag) - if !ok { - t.Fatalf("got %T, expected *stateBag", s) - } - if st.provider == nil { - t.Error("expected provider in tracing context") - } -} - -func TestDisabled(t *testing.T) { - ctx, err := Context(context.Background(), "test", WithDisabled()) - if err != nil { - t.Fatal(err) - } - s := ctx.Value(stateKey) - if s == nil { - t.Fatal("expected state in context") - } - st, ok := s.(*stateBag) - if !ok { - t.Fatalf("got %T, expected *stateBag", s) - } - if st.provider == nil { - t.Error("expected provider in tracing context") - } - _, span := st.provider.Tracer("test").Start(ctx, "test") - if span.IsRecording() { - t.Error("expected span to be disabled") - } -} - -func TestIsTraced(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - ctx, err := Context(context.Background(), "test", - WithMaxSamplingRate(3), WithSampleSize(20), WithExporter(exporter)) - if err != nil { - t.Fatal(err) - } - if IsTraced(ctx) { - t.Error("expected not traced") - } - ctx, err = Context(ctx, "test", WithMaxSamplingRate(3), WithSampleSize(20), WithExporter(exporter)) - if err != nil { - t.Fatal(err) - } - ctx = withTracing(ctx, context.Background()) - ctx = StartSpan(ctx, "test") - if !IsTraced(ctx) { - t.Error("expected traced") - } - EndSpan(ctx) -} diff --git a/trace/grpc.go b/trace/grpc.go deleted file mode 100644 index 4717c7bd..00000000 --- a/trace/grpc.go +++ /dev/null @@ -1,188 +0,0 @@ -package trace - -import ( - "context" - - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "goa.design/goa/v3/middleware" - "google.golang.org/grpc" - "google.golang.org/grpc/stats" -) - -// NewServerHandler creates a stats.Handler for a gRPC server that uses an -// adaptive sampler for limiting the number of traced requests while -// guraranteeing that a certain number of requests are traced. -// It panics if the context has not been initialized with Context. -// -// Example: -// -// // Connect to remote trace collector. -// conn, err := grpc.DialContext(ctx, collectorAddr) -// if err != nil { -// log.Error(ctx, err) -// os.Exit(1) -// } -// // Initialize context for tracing -// ctx := trace.Context(ctx, svcgen.ServiceName, trace.WithGRPCExporter(conn)) -// // Create stats handler -// handler := trace.NewServerHandler(ctx) -// // Create gRPC server -// grpcServer := grpc.NewServer(grpc.StatsHandler(handler)) -func NewServerHandler(traceCtx context.Context) stats.Handler { - state := traceCtx.Value(stateKey) - if state == nil { - panic(errContextMissing) - } - return otelgrpc.NewServerHandler( - otelgrpc.WithTracerProvider(state.(*stateBag).provider), - otelgrpc.WithPropagators(state.(*stateBag).propagator), - ) -} - -// NewClientHandler creates a stats.Handler for a gRPC client. -// It panics if the context has not been initialized with Context. -func NewClientHandler(traceCtx context.Context) stats.Handler { - state := traceCtx.Value(stateKey) - if state == nil { - panic(errContextMissing) - } - return otelgrpc.NewClientHandler( - otelgrpc.WithTracerProvider(state.(*stateBag).provider), - otelgrpc.WithPropagators(state.(*stateBag).propagator), - ) -} - -// UnaryServerInterceptor returns an OpenTelemetry UnaryServerInterceptor. It -// panics if the context has not been initialized with Context. -// Deprecated: Use NewServerHandler instead. -func UnaryServerInterceptor(traceCtx context.Context) grpc.UnaryServerInterceptor { - state := traceCtx.Value(stateKey) - if state == nil { - panic(errContextMissing) - } - return func( - ctx context.Context, - req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler, - ) (interface{}, error) { - handler = initTracingContextGRPCUnary(traceCtx, handler) - handler = addRequestIDGRPCUnary(handler) - ui := otelgrpc.UnaryServerInterceptor( // - otelgrpc.WithTracerProvider(state.(*stateBag).provider), - otelgrpc.WithPropagators(state.(*stateBag).propagator)) - return ui(ctx, req, info, handler) - } -} - -// StreamServerInterceptor returns an OpenTelemetry StreamServerInterceptor. It -// panics if the context has not been initialized with Context. -// Deprecated: Use NewServerHandler instead. -func StreamServerInterceptor(traceCtx context.Context) grpc.StreamServerInterceptor { - state := traceCtx.Value(stateKey) - if state == nil { - panic(errContextMissing) - } - return func( - srv interface{}, - stream grpc.ServerStream, - info *grpc.StreamServerInfo, - handler grpc.StreamHandler, - ) error { - handler = initTracingContextGRPCStream(traceCtx, handler) - handler = addRequestIDGRPCStream(handler) - si := otelgrpc.StreamServerInterceptor( - otelgrpc.WithTracerProvider(state.(*stateBag).provider), - otelgrpc.WithPropagators(state.(*stateBag).propagator), - ) - return si(srv, stream, info, handler) - } -} - -// UnaryClientInterceptor returns an OpenTelemetry UnaryClientInterceptor. It -// panics if the context has not been initialized with Context. -// Deprecated: Use NewClientHandler instead. -func UnaryClientInterceptor(traceCtx context.Context) grpc.UnaryClientInterceptor { - state := traceCtx.Value(stateKey) - if state == nil { - panic(errContextMissing) - } - return otelgrpc.UnaryClientInterceptor( - otelgrpc.WithTracerProvider(state.(*stateBag).provider), - otelgrpc.WithPropagators(state.(*stateBag).propagator)) -} - -// StreamClientInterceptor returns an OpenTelemetry StreamClientInterceptor. It -// panics if the context has not been initialized with Context. -// Deprecated: Use NewClientHandler instead. -func StreamClientInterceptor(traceCtx context.Context) grpc.StreamClientInterceptor { - state := traceCtx.Value(stateKey) - if state == nil { - panic(errContextMissing) - } - return otelgrpc.StreamClientInterceptor( - otelgrpc.WithTracerProvider(state.(*stateBag).provider), - otelgrpc.WithPropagators(state.(*stateBag).propagator)) -} - -// addRequestIDGRPCUnary is a middleware that adds the request ID to the current span -// attributes. -func addRequestIDGRPCUnary(h grpc.UnaryHandler) grpc.UnaryHandler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - requestID := ctx.Value(middleware.RequestIDKey) - if requestID == nil { - return h(ctx, req) - } - span := trace.SpanFromContext(ctx) - span.SetAttributes(attribute.String(AttributeRequestID, requestID.(string))) - return h(ctx, req) - } -} - -// initTracingContextGRPCUnary is a unary interceptor that initializes the -// tracing context. -func initTracingContextGRPCUnary(traceCtx context.Context, h grpc.UnaryHandler) grpc.UnaryHandler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - if IsTraced(ctx) { - ctx = withTracing(traceCtx, ctx) - } - return h(ctx, req) - } -} - -// addRequestIDGRPCStream is a middleware that adds the request ID to the current span -// attributes. -func addRequestIDGRPCStream(h grpc.StreamHandler) grpc.StreamHandler { - return func(srv interface{}, stream grpc.ServerStream) error { - requestID := stream.Context().Value(middleware.RequestIDKey) - if requestID == nil { - return h(srv, stream) - } - span := trace.SpanFromContext(stream.Context()) - span.SetAttributes(attribute.String(AttributeRequestID, requestID.(string))) - return h(srv, stream) - } -} - -// initTracingContextGRPCStream is a stream interceptor that initializes the -// tracing context. -func initTracingContextGRPCStream(traceCtx context.Context, h grpc.StreamHandler) grpc.StreamHandler { - return func(srv interface{}, stream grpc.ServerStream) error { - if IsTraced(stream.Context()) { - ctx := withTracing(traceCtx, stream.Context()) - stream = &streamWithContext{ctx: ctx, ServerStream: stream} - } - return h(srv, stream) - } -} - -type streamWithContext struct { - grpc.ServerStream - ctx context.Context -} - -func (s *streamWithContext) Context() context.Context { - return s.ctx -} diff --git a/trace/grpc_test.go b/trace/grpc_test.go deleted file mode 100644 index c1028d99..00000000 --- a/trace/grpc_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package trace - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" - "goa.design/clue/internal/testsvc" - "goa.design/goa/v3/grpc/middleware" - "google.golang.org/grpc" -) - -func TestNewServerHandler(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - ctx := testContext(provider) - handler := NewServerHandler(ctx) - require.NotNil(t, handler) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.StatsHandler(handler)), - testsvc.WithUnaryFunc(addEventUnaryMethod)) - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - stop() - spans := exporter.GetSpans() - require.Len(t, spans, 1) - assert.Equal(t, "test.Test/GrpcMethod", spans[0].Name) -} - -func TestNewClientHandler(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - ctx := testContext(provider) - handler := NewClientHandler(ctx) - require.NotNil(t, handler) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithDialOptions(grpc.WithStatsHandler(handler)), - testsvc.WithUnaryFunc(addEventUnaryMethod), - ) - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - stop() - spans := exporter.GetSpans() - require.Len(t, spans, 1) - assert.Equal(t, "test.Test/GrpcMethod", spans[0].Name) -} - -func TestUnaryServerTrace(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - traceInterceptor := UnaryServerInterceptor(testContext(provider)) - requestIDInterceptor := middleware.UnaryRequestID() - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.ChainUnaryInterceptor(requestIDInterceptor, traceInterceptor)), - testsvc.WithUnaryFunc(addEventUnaryMethod)) - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - stop() - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, want 1", len(spans)) - } - found := false - for _, att := range spans[0].Attributes { - if att.Key == AttributeRequestID { - found = true - break - } - } - if !found { - t.Errorf("request ID not in span attributes") - } - events := spans[0].Events - if len(events) != 1 { - t.Fatalf("got %d events, want 1", len(events)) - } - if events[0].Name != "unary method" { - t.Errorf("unexpected event name: %s", events[0].Name) - } -} - -func TestUnaryServerTraceNoRequestID(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - traceInterceptor := UnaryServerInterceptor(testContext(provider)) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.UnaryInterceptor(traceInterceptor)), - testsvc.WithUnaryFunc(addEventUnaryMethod)) - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - stop() - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, want 1", len(spans)) - } -} - -func TestStreamServerTrace(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - traceInterceptor := StreamServerInterceptor(testContext(provider)) - requestIDInterceptor := middleware.StreamRequestID() - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.ChainStreamInterceptor(requestIDInterceptor, traceInterceptor)), - testsvc.WithStreamFunc(echoMethod)) - stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("unexpected stream error: %v", err) - } - if err := stream.Send(&testsvc.Fields{}); err != nil { - t.Errorf("unexpected send error: %v", err) - } - stream.Recv() // nolint: errcheck - stop() - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, want 1", len(spans)) - } - found := false - for _, att := range spans[0].Attributes { - if att.Key == AttributeRequestID { - found = true - break - } - } - if !found { - t.Errorf("request ID not in span attributes") - } -} - -func TestStreamServerTraceNoRequestID(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - traceInterceptor := StreamServerInterceptor(testContext(provider)) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithServerOptions(grpc.StreamInterceptor(traceInterceptor)), - testsvc.WithStreamFunc(echoMethod)) - stream, err := cli.GRPCStream(context.Background()) - if err != nil { - t.Errorf("unexpected stream error: %v", err) - } - if err := stream.Send(&testsvc.Fields{}); err != nil { - t.Errorf("unexpected send error: %v", err) - } - stream.Recv() // nolint: errcheck - stop() - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, want 1", len(spans)) - } -} - -func TestUnaryClientTrace(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - cli, stop := testsvc.SetupGRPC(t, - testsvc.WithDialOptions(grpc.WithUnaryInterceptor(UnaryClientInterceptor(testContext(provider)))), - testsvc.WithUnaryFunc(addEventUnaryMethod)) - _, err := cli.GRPCMethod(context.Background(), &testsvc.Fields{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - stop() - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, want 1", len(spans)) - } -} - -func addEventUnaryMethod(ctx context.Context, _ *testsvc.Fields) (*testsvc.Fields, error) { - AddEvent(ctx, "unary method") - return &testsvc.Fields{}, nil -} - -func echoMethod(_ context.Context, stream testsvc.Stream) (err error) { - f, err := stream.Recv() - if err != nil { - return err - } - if err := stream.Send(f); err != nil { - return err - } - return stream.Close() -} diff --git a/trace/http.go b/trace/http.go deleted file mode 100644 index 3cace953..00000000 --- a/trace/http.go +++ /dev/null @@ -1,70 +0,0 @@ -package trace - -import ( - "context" - "net/http" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -// Message printed by panic when using a method with a non-initialized context. -const errContextMissing = "context not initialized for tracing, use trace.Context to set it up" - -// HTTP returns a tracing middleware that uses an adaptive sampler for limiting -// the number of traced requests while guraranteeing that a certain number of -// requests are traced. -// HTTP panics if the context hasn't been initialized with Context. -// -// Example: -// -// // Connect to remote trace collector. -// conn, err := grpc.DialContext(ctx, collectorAddr) -// if err != nil { -// log.Error(ctx, err) -// os.Exit(1) -// } -// // Initialize context for tracing -// ctx := trace.Context(ctx, svcgen.ServiceName, trace.WithGRPCExporter(conn)) -// // Mount middleware -// handler := trace.HTTP(ctx)(mux) -func HTTP(ctx context.Context) func(http.Handler) http.Handler { - s := ctx.Value(stateKey) - if s == nil { - panic(errContextMissing) - } - return func(h http.Handler) http.Handler { - return otelhttp.NewHandler(h, s.(*stateBag).svc, - otelhttp.WithTracerProvider(s.(*stateBag).provider), - otelhttp.WithPropagators(s.(*stateBag).propagator)) - } -} - -// Client returns a http.RoundTripper that wraps t and creates spans for each -// request. It panics if the context hasn't been initialized with Context. -// -// Example: -// -// // Connect to remote trace collector. -// conn, err := grpc.DialContext(ctx, collectorAddr) -// if err != nil { -// log.Error(ctx, err) -// os.Exit(1) -// } -// // Initialize context for tracing -// ctx := trace.Context(ctx, svcgen.ServiceName, trace.WithGRPCExporter(conn)) -// // Create client -// cli := &http.Client{ -// Transport: trace.Client(ctx, http.DefaultTransport), -// } -// // Use client -// resp, err := cli.Get("http://example.com") -func Client(ctx context.Context, t http.RoundTripper, opts ...otelhttp.Option) http.RoundTripper { - s := ctx.Value(stateKey) - if s == nil { - panic(errContextMissing) - } - opts = append(opts, - otelhttp.WithTracerProvider(s.(*stateBag).provider), - otelhttp.WithPropagators(s.(*stateBag).propagator)) - return otelhttp.NewTransport(t, opts...) -} diff --git a/trace/http_test.go b/trace/http_test.go deleted file mode 100644 index d59e7694..00000000 --- a/trace/http_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package trace - -import ( - "context" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" - - "goa.design/clue/internal/testsvc" -) - -func TestHTTP(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - ctx := testContext(provider) - cli, stop := testsvc.SetupHTTP(t, - testsvc.WithHTTPMiddleware(HTTP(ctx)), - testsvc.WithHTTPFunc(addEventUnaryMethod)) - _, err := cli.HTTPMethod(context.Background(), &testsvc.Fields{}) - assert.NoError(t, err) - stop() - spans := exporter.GetSpans() - require.Len(t, spans, 1) - assert.Equal(t, "test", spans[0].Name) -} - -func TestClient(t *testing.T) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - ctx := testContext(provider) - c := http.Client{Transport: Client(ctx, http.DefaultTransport)} - otelt, ok := c.Transport.(*otelhttp.Transport) - assert.True(t, ok, "got %T, want %T", c.Transport, otelt) -} diff --git a/trace/log.go b/trace/log.go deleted file mode 100644 index 76820c74..00000000 --- a/trace/log.go +++ /dev/null @@ -1,24 +0,0 @@ -package trace - -import ( - "context" - - "goa.design/clue/log" -) - -// Log is a log key/value pair generator function that can be used to log trace -// and span IDs. Example: -// -// ctx := log.Context(ctx, WithFunc(trace.Log)) -// log.Printf(ctx, "message") -// -// Output: traceID= spanID= message -func Log(ctx context.Context) (kvs []log.KV) { - if id := TraceID(ctx); id != "" { - kvs = append(kvs, log.KV{K: log.TraceIDKey, V: id}) - } - if id := SpanID(ctx); id != "" { - kvs = append(kvs, log.KV{K: log.SpanIDKey, V: id}) - } - return -} diff --git a/trace/log_test.go b/trace/log_test.go deleted file mode 100644 index a239aa0a..00000000 --- a/trace/log_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package trace - -import ( - "context" - "testing" - - "goa.design/clue/log" -) - -func TestLog(t *testing.T) { - ctx, _ := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - ctx = StartSpan(ctx, "span") - kvs := Log(ctx) - if len(kvs) != 2 { - t.Fatalf("got %d kvs, expected 2", len(kvs)) - } - if kvs[0].K != log.TraceIDKey { - t.Errorf("got kvs[0].K %q, expected %q", kvs[0].K, log.TraceIDKey) - } - if kvs[0].V != TraceID(ctx) { - t.Errorf("got kvs[0].V %q, expected %q", kvs[0].V, TraceID(ctx)) - } - if kvs[1].K != log.SpanIDKey { - t.Errorf("got kvs[1].K %q, expected %q", kvs[1].K, log.SpanIDKey) - } - if kvs[1].V != SpanID(ctx) { - t.Errorf("got kvs[1].V %q, expected %q", kvs[1].V, SpanID(ctx)) - } -} diff --git a/trace/options.go b/trace/options.go deleted file mode 100644 index 9678da9f..00000000 --- a/trace/options.go +++ /dev/null @@ -1,104 +0,0 @@ -package trace - -import ( - "context" - - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "google.golang.org/grpc" -) - -type ( - options struct { - maxSamplingRate int - sampleSize int - exporter sdktrace.SpanExporter - propagator propagation.TextMapPropagator - parentSamplerOptions []sdktrace.ParentBasedSamplerOption - resource *resource.Resource - disabled bool - } - - // TraceOption is a function that configures a provider. - TraceOption func(ctx context.Context, opts *options) error -) - -// defaultOptions returns the default sampler options. -func defaultOptions() *options { - return &options{ - maxSamplingRate: 2, - sampleSize: 10, - propagator: propagation.TraceContext{}, - } -} - -// WithMaxSamplingRate sets the maximum sampling rate in requests per second. -func WithMaxSamplingRate(rate int) TraceOption { - return func(_ context.Context, opts *options) error { - opts.maxSamplingRate = rate - return nil - } -} - -// WithSampleSize sets the number of requests between two adjustments of the -// sampling rate. -func WithSampleSize(size int) TraceOption { - return func(_ context.Context, opts *options) error { - opts.sampleSize = size - return nil - } -} - -// WithDisabled disables tracing, not for use in production. -func WithDisabled() TraceOption { - return func(_ context.Context, opts *options) error { - opts.disabled = true - return nil - } -} - -// WithExporter sets the exporter to use. -func WithExporter(exporter sdktrace.SpanExporter) TraceOption { - return func(_ context.Context, opts *options) error { - opts.exporter = exporter - return nil - } -} - -// WithParentSamplerOptions to set the options for sdktrace.ParentBased sampler. -func WithParentSamplerOptions(samplerOptions ...sdktrace.ParentBasedSamplerOption) TraceOption { - return func(_ context.Context, opts *options) error { - opts.parentSamplerOptions = samplerOptions - return nil - } -} - -// WithResource sets the underlying opentelemetry resource. -func WithResource(res *resource.Resource) TraceOption { - return func(_ context.Context, opts *options) error { - opts.resource = res - return nil - } -} - -// WithPropagator sets the trace propagator. -func WithPropagator(propagator propagation.TextMapPropagator) TraceOption { - return func(_ context.Context, opts *options) error { - opts.propagator = propagator - return nil - } -} - -// WithGRPCExporter sets the connection to the span exporter. -func WithGRPCExporter(conn *grpc.ClientConn) TraceOption { - return func(ctx context.Context, opts *options) error { - exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) - if err != nil { - return err - } - opts.exporter = exporter - return nil - } -} diff --git a/trace/options_test.go b/trace/options_test.go deleted file mode 100644 index 1a053c0e..00000000 --- a/trace/options_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package trace - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" -) - -func TestOptions(t *testing.T) { - ctx := context.Background() - - options := defaultOptions() - if options.maxSamplingRate != 2 { - t.Errorf("got %d, want 2", options.maxSamplingRate) - } - if options.sampleSize != 10 { - t.Errorf("got %d, want 10", options.sampleSize) - } - assert.NoError(t, WithMaxSamplingRate(3)(ctx, options)) - if options.maxSamplingRate != 3 { - t.Errorf("got %d sampling rate, want 3", options.maxSamplingRate) - } - assert.NoError(t, WithSampleSize(20)(ctx, options)) - if options.sampleSize != 20 { - t.Errorf("got %d sample size, want 20", options.sampleSize) - } - assert.NoError(t, WithDisabled()(ctx, options)) - if !options.disabled { - t.Error("expected disabled to be true") - } - assert.NoError(t, WithExporter(tracetest.NewInMemoryExporter())(ctx, options)) - if options.exporter == nil { - t.Error("got nil exporter, want non-nil") - } - assert.NoError(t, WithResource(&resource.Resource{})(ctx, options)) - if options.resource == nil { - t.Error("got nil resource, want non-nil") - } - assert.NoError(t, WithParentSamplerOptions(sdktrace.WithRemoteParentSampled(nil))(ctx, options)) - if total := len(options.parentSamplerOptions); total != 1 { - t.Errorf("got %d parent sampler options, expected 1", total) - } -} diff --git a/trace/sampler_test.go b/trace/sampler_test.go deleted file mode 100644 index 6ac5c5cd..00000000 --- a/trace/sampler_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package trace - -import ( - "testing" - - sdktrace "go.opentelemetry.io/otel/sdk/trace" -) - -func TestAdaptiveSampler(t *testing.T) { - // We don't need to test Goa, keep it simple... - s := adaptiveSampler(2, 10) - expected := "Adaptive{maxSamplingRate:2,sampleSize:10}" - if s.Description() != expected { - t.Fatalf("got description %q, expected %q", s.Description(), expected) - } - res := s.ShouldSample(sdktrace.SamplingParameters{}) - if res.Decision != sdktrace.RecordAndSample { - t.Error("expected sampling") - } - - s2 := adaptiveSampler(1, 2) - expected = "Adaptive{maxSamplingRate:1,sampleSize:2}" - if s2.Description() != expected { - t.Fatalf("got description %q, expected %q", s2.Description(), expected) - } - res2 := s2.ShouldSample(sdktrace.SamplingParameters{}) - res3 := s2.ShouldSample(sdktrace.SamplingParameters{}) - if res2.Decision != sdktrace.RecordAndSample { - t.Error("expected sampling") - } - if res3.Decision != sdktrace.Drop { - t.Error("expected no sampling") - } -} diff --git a/trace/span.go b/trace/span.go deleted file mode 100644 index 8404ad37..00000000 --- a/trace/span.go +++ /dev/null @@ -1,194 +0,0 @@ -package trace - -import ( - "context" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" -) - -// StartSpan starts a new span with the given name and attributes and stores it -// in the returned context if the request is traced, does nothing otherwise. -// keyvals must be a list of alternating keys and values. -func StartSpan(ctx context.Context, name string, keyvals ...string) context.Context { - s := ctx.Value(stateKey) - if s == nil { - return ctx - } - tracer := s.(*stateBag).tracer - if tracer == nil { - return ctx - } - ctx, span := tracer.Start(ctx, name, trace.WithAttributes(toKeyVal(keyvals)...)) - setActiveSpans(ctx, append(activeSpans(ctx), span)) - return ctx -} - -// End ends the current span if any. -func EndSpan(ctx context.Context) { - spans := activeSpans(ctx) - if len(spans) == 0 { - return - } - spans[len(spans)-1].End() - setActiveSpans(ctx, spans[:len(spans)-1]) -} - -// StartTrace starts a new trace and initializes the context with it. In general -// traces should be managed by HTTP middlewares and gRPC interceptors created -// via the HTTP, UnaryServerInterceptor and StreamServerInterceptor methods. -// This function is intended to be used by code running outside of network -// requests for example workers that initiate request threads. The context must -// be initialized with Context. EndTrace must be called by the client in the -// same goroutine. Not calling EndTrace may cause resource leaks. -func StartTrace(ctx context.Context, name string, keyvals ...string) context.Context { - return createSpan(ctx, trace.SpanKindClient, name, keyvals...) -} - -// ContinueRemoteTrace initializes the tracing context with the given remote -// trace ID and starts a new server span. See StartTrace for usage. -func ContinueRemoteTrace(ctx context.Context, name string, traceID [16]byte, keyvals ...string) context.Context { - sc := trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: traceID, - Remote: true, - }) - return createSpan(trace.ContextWithRemoteSpanContext(ctx, sc), trace.SpanKindServer, name, keyvals...) -} - -// EndTrace ends the last trace started by StartTrace. -func EndTrace(ctx context.Context) { - for _, span := range activeSpans(ctx) { - span.End() - } - setActiveSpans(ctx, nil) -} - -// SetSpanAttributes adds the given attributes to the current span if any. -// keyvals must be a list of alternating keys and values. It overwrites any -// existing attributes with the same key. -func SetSpanAttributes(ctx context.Context, keyvals ...string) { - span := activeSpan(ctx) - if span == nil { - return - } - span.SetAttributes(toKeyVal(keyvals)...) -} - -// AddEvent records an event with the given name and attributes in the current -// span if any. -func AddEvent(ctx context.Context, name string, keyvals ...string) { - span := activeSpan(ctx) - if span == nil { - return - } - kvs := toKeyVal(keyvals) - span.AddEvent(name, trace.WithAttributes(kvs...)) -} - -// Succeed sets the status of the current span to success if any. -func Succeed(ctx context.Context) { - span := activeSpan(ctx) - if span == nil { - return - } - span.SetStatus(codes.Ok, "") -} - -// Fail sets the status of the current span to failed and attaches the failure -// message. -func Fail(ctx context.Context, msg string) { - span := activeSpan(ctx) - if span == nil { - return - } - span.SetStatus(codes.Error, msg) -} - -// RecordError records err as an exception span event for the current span if -// any. An additional call to SetStatus is required if the Status of the Span -// should be set to Error, as this method does not change the Span status. -func RecordError(ctx context.Context, err error) { - span := activeSpan(ctx) - if span == nil { - return - } - span.RecordError(err) -} - -// TraceID returns the trace ID of the current span if any, empty string otherwise. -func TraceID(ctx context.Context) string { - span := activeSpan(ctx) - if span == nil { - return "" - } - return span.SpanContext().TraceID().String() -} - -// SpanID returns the span ID of the current span if any, empty string otherwise. -func SpanID(ctx context.Context) string { - span := activeSpan(ctx) - if span == nil { - return "" - } - return span.SpanContext().SpanID().String() -} - -// createSpan creates a new span with the given name and attributes. -func createSpan(ctx context.Context, kind trace.SpanKind, name string, keyvals ...string) context.Context { - s := ctx.Value(stateKey) - if s == nil { - return ctx - } - bag := s.(*stateBag) - tracer := bag.tracer - if tracer == nil { - tracer = bag.provider.Tracer(InstrumentationLibraryName) - bag.tracer = tracer - } - ctx, span := tracer.Start( - ctx, - name, - trace.WithSpanKind(kind), - trace.WithAttributes(toKeyVal(keyvals)...), - ) - setActiveSpans(ctx, []trace.Span{span}) - return ctx -} - -// activeSpans returns the active spans of the tracing state. -func activeSpans(ctx context.Context) []trace.Span { - s := ctx.Value(stateKey) - if s == nil { - return nil - } - return s.(*stateBag).spans -} - -// setActiveSpans updates the active spans of the tracing state. -func setActiveSpans(ctx context.Context, spans []trace.Span) { - s := ctx.Value(stateKey) - if s == nil { - return - } - s.(*stateBag).spans = spans -} - -func activeSpan(ctx context.Context) trace.Span { - spans := activeSpans(ctx) - if len(spans) == 0 { - return nil - } - return spans[len(spans)-1] -} - -func toKeyVal(kvs []string) []attribute.KeyValue { - if len(kvs)%2 != 0 { - kvs = append(kvs, "") - } - keyvals := make([]attribute.KeyValue, len(kvs)/2) - for i := 0; i < len(kvs); i += 2 { - keyvals[i/2] = attribute.String(kvs[i], kvs[i+1]) - } - return keyvals -} diff --git a/trace/span_test.go b/trace/span_test.go deleted file mode 100644 index 3b0528dc..00000000 --- a/trace/span_test.go +++ /dev/null @@ -1,383 +0,0 @@ -package trace - -import ( - "context" - "errors" - "testing" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/propagation" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" - "go.opentelemetry.io/otel/trace" -) - -func newTestTracingContext() (context.Context, *tracetest.InMemoryExporter) { - exporter := tracetest.NewInMemoryExporter() - provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) - ctx := withConfig(context.Background(), provider, propagation.TraceContext{}, "test") - return ctx, exporter -} - -func TestStartEndSpan(t *testing.T) { - // Make sure StartSpan and EndSpan do not panic - StartSpan(context.Background(), "noop") - EndSpan(context.Background()) - - // Setup - ctx, exporter := newTestTracingContext() - - // Create span - ctx = withTracing(ctx, context.Background()) - ctx = StartSpan(ctx, "span") - if !trace.SpanFromContext(ctx).SpanContext().IsValid() { - t.Error("expected valid span") - } - if !trace.SpanFromContext(ctx).IsRecording() { - t.Error("expected recording span") - } - if !trace.SpanFromContext(ctx).SpanContext().IsSampled() { - t.Error("expected sampled span") - } - EndSpan(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if spans[0].Name != "span" { - t.Errorf("got span name %q, expected %q", spans[0].Name, "span") - } - var zeroID trace.TraceID - if spans[0].Parent.TraceID() != zeroID { - t.Errorf("got parent traceID %s, expected %s", spans[0].Parent.TraceID(), zeroID) - } -} - -func TestChildSpan(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create spans - ctx = StartSpan(ctx, "parent") - ctx = StartSpan(ctx, "child") - EndSpan(ctx) - EndSpan(ctx) - - // Verify ) - spans := exporter.GetSpans() // returns spans in reverse order - if len(spans) != 2 { - t.Fatalf("got %d spans, expected 2", len(spans)) - } - if spans[1].Name != "parent" { - t.Errorf("got span name %q, expected %q", spans[1].Name, "parent") - } - var zeroID trace.TraceID - if spans[1].Parent.TraceID() != zeroID { - t.Errorf("got parent span %v, expected %v", spans[1].Parent.TraceID(), zeroID) - } - if spans[0].Name != "child" { - t.Errorf("got span name %q, expected %q", spans[0].Name, "child") - } - if spans[0].Parent.TraceID() != spans[1].SpanContext.TraceID() { - t.Errorf("got parent span %v, expected %v", spans[0].Parent, spans[1]) - } -} - -func TestStartEndTrace(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - - // Create trace - ctx = StartTrace(ctx, "trace") - EndTrace(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if spans[0].Name != "trace" { - t.Errorf("got span name %q, expected %q", spans[0].Name, "span") - } - var zeroID trace.TraceID - if spans[0].Parent.TraceID() != zeroID { - t.Errorf("got parent traceID %s, expected %s", spans[0].Parent.TraceID(), zeroID) - } -} - -func TestContinueRemoteTrace(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - - // Create span and set status - traceID := [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10} - ctx = ContinueRemoteTrace(ctx, "trace", traceID) - EndTrace(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if spans[0].Name != "trace" { - t.Errorf("got span name %q, expected %q", spans[0].Name, "span") - } - if spans[0].Parent.TraceID() != traceID { - t.Errorf("got parent traceID %s, expected %s", spans[0].Parent.TraceID(), traceID) - } -} - -func TestSetSpanAttributesNoContext(t *testing.T) { - SetSpanAttributes(context.Background(), "key", "value") -} - -func TestSetSpanAttributes(t *testing.T) { - cases := []struct { - name string - keyvals []string - expected map[string]string - }{ - {"nil", nil, nil}, - {"empty", []string{}, nil}, - {"one", []string{"key", "value"}, map[string]string{"key": "value"}}, - {"invalid", []string{"key", "value", "key2"}, map[string]string{"key": "value", "key2": ""}}, - {"override", []string{"key", "value", "key", "value2"}, map[string]string{"key": "value2"}}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and set attributes - ctx = StartSpan(ctx, "span") - SetSpanAttributes(ctx, c.keyvals...) - EndSpan(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if len(spans[0].Attributes) != len(c.expected) { - t.Errorf("got %d attributes, expected %d", len(spans[0].Attributes), len(c.expected)) - } - for k, v := range c.expected { - found := false - for _, att := range spans[0].Attributes { - if att.Key == attribute.Key(k) && att.Value.AsString() == v { - found = true - break - } - } - if !found { - t.Errorf("attribute %s not found", k) - } - } - }) - } -} - -func TestAddEventNoContext(t *testing.T) { - AddEvent(context.Background(), "event") -} - -func TestAddEvent(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and add event - ctx = StartSpan(ctx, "span") - AddEvent(ctx, "event", "key", "value") - EndSpan(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if len(spans[0].Events) != 1 { - t.Errorf("got %d events, expected 1", len(spans[0].Events)) - } - if spans[0].Events[0].Name != "event" { - t.Errorf("got event name %q, expected %q", spans[0].Events[0].Name, "event") - } - if len(spans[0].Events[0].Attributes) != 1 { - t.Fatalf("got %d attributes, expected 1", len(spans[0].Events[0].Attributes)) - } - if spans[0].Events[0].Attributes[0].Key != "key" { - t.Errorf("got event attribute key %q, expected %q", spans[0].Events[0].Attributes[0].Key, "key") - } - if spans[0].Events[0].Attributes[0].Value.AsString() != "value" { - t.Errorf("got event attribute value %q, expected %q", spans[0].Events[0].Attributes[0].Value.AsString(), "value") - } -} - -func TestFailNoContext(t *testing.T) { - Fail(context.Background(), "desc") -} - -func TestFail(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and set status - ctx = StartSpan(ctx, "span") - Fail(ctx, "desc") - EndSpan(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if spans[0].Status.Code != codes.Error { - t.Errorf("got status code %d, expected %d", spans[0].Status.Code, codes.Error) - } - if spans[0].Status.Description != "desc" { - t.Errorf("got status description %q, expected %q", spans[0].Status.Description, "desc") - } -} - -func TestSucceedNoContext(t *testing.T) { - Succeed(context.Background()) -} - -func TestSucceed(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and set status - ctx = StartSpan(ctx, "span") - Fail(ctx, "desc") - Succeed(ctx) - EndSpan(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if spans[0].Status.Code != codes.Ok { - t.Errorf("got status code %d, expected %d", spans[0].Status.Code, codes.Ok) - } -} - -func TestRecordErrorNoContext(t *testing.T) { - RecordError(context.Background(), errors.New("err")) -} - -func TestRecordError(t *testing.T) { - // Setup - ctx, exporter := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and add event - ctx = StartSpan(ctx, "span") - recordedErr := errors.New("recorded error") - RecordError(ctx, recordedErr) - EndSpan(ctx) - - // Verify - spans := exporter.GetSpans() - if len(spans) != 1 { - t.Fatalf("got %d spans, expected 1", len(spans)) - } - if len(spans[0].Events) != 1 { - t.Errorf("got %d events, expected 1", len(spans[0].Events)) - } - if spans[0].Events[0].Name != "exception" { - t.Errorf("got event name %q, expected %q", spans[0].Events[0].Name, "exception") - } - if spans[0].Events[0].Attributes[0].Key != "exception.type" { - t.Errorf("got event attribute key %q, expected %q", spans[0].Events[0].Attributes[0].Key, "exception.type") - } - if spans[0].Events[0].Attributes[0].Value.AsString() != "*errors.errorString" { - t.Errorf("got event attribute value %q, expected %q", spans[0].Events[0].Attributes[0].Value.AsString(), "*errors.errorString") - } -} - -func TestTraceIDNoContext(t *testing.T) { - tid := TraceID(context.Background()) - if tid != "" { - t.Errorf("got trace ID %q, expected empty", tid) - } -} - -func TestTraceID(t *testing.T) { - // Setup - ctx, _ := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and validate - ctx = StartSpan(ctx, "span") - if trace.SpanFromContext(ctx).SpanContext().TraceID().String() != TraceID(ctx) { - t.Errorf("got trace ID %q, expected %q", trace.SpanFromContext(ctx).SpanContext().TraceID().String(), TraceID(ctx)) - } - EndSpan(ctx) -} - -func TestSpanIDNoContext(t *testing.T) { - sid := SpanID(context.Background()) - if sid != "" { - t.Errorf("got span ID %q, expected empty", sid) - } -} - -func TestSpanID(t *testing.T) { - // Setup - ctx, _ := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create span and add event - ctx = StartSpan(ctx, "span") - if trace.SpanFromContext(ctx).SpanContext().SpanID().String() != SpanID(ctx) { - t.Errorf("got span ID %q, expected %q", trace.SpanFromContext(ctx).SpanContext().SpanID().String(), SpanID(ctx)) - } - EndSpan(ctx) -} - -func TestSetActiveSpansNoContext(t *testing.T) { - setActiveSpans(context.Background(), nil) -} - -func TestActiveSpans(t *testing.T) { - // Setup - ctx, _ := newTestTracingContext() - ctx = withTracing(ctx, context.Background()) - - // Create out-of-state span - ctx, span := ctx.Value(stateKey).(*stateBag).tracer.Start(ctx, "span") - - // Make sure it's not in state - spans := activeSpans(ctx) - if len(spans) != 1 { - t.Fatalf("got %d active spans, expected 1", len(spans)) - } - - // Manually add it to state - setActiveSpans(ctx, []trace.Span{span}) - spans = activeSpans(ctx) - if len(spans) != 1 { - t.Fatalf("got %d active spans, expected 1", len(spans)) - } - if spans[0] != span { - t.Errorf("got active span %#v, expected %#v", spans[0], span) - } - - // End span - EndSpan(ctx) - - // Make sure it's not in state anymore - spans = activeSpans(ctx) - if len(spans) != 0 { - t.Fatalf("got %d active spans, expected 0", len(spans)) - } -}