From 13d7dd9f2820dbaa2f6443f8661e9cb1ffb7bb75 Mon Sep 17 00:00:00 2001 From: Lukas Malkmus Date: Thu, 7 Dec 2023 15:30:17 +0100 Subject: [PATCH] feat(otel): metrics --- .github/workflows/test_examples.yaml | 16 ++- axiom/otel/config.go | 112 +++++++++++++++++++++ axiom/otel/doc.go | 30 +++--- axiom/otel/metric.go | 108 ++++++++++++++++++++ axiom/otel/metric_integration_test.go | 53 ++++++++++ axiom/otel/metric_test.go | 54 ++++++++++ axiom/otel/otel.go | 25 +++++ axiom/otel/trace.go | 44 +------- axiom/otel/trace_config.go | 78 -------------- axiom/otel/trace_integration_test.go | 10 +- axiom/otel/trace_test.go | 8 +- examples/README.md | 4 +- examples/otelinstrument/main.go | 2 + examples/otelmetric/main.go | 54 ++++++++++ examples/{oteltraces => oteltrace}/main.go | 0 go.mod | 12 +++ go.sum | 30 ++++++ 17 files changed, 492 insertions(+), 148 deletions(-) create mode 100644 axiom/otel/config.go create mode 100644 axiom/otel/metric.go create mode 100644 axiom/otel/metric_integration_test.go create mode 100644 axiom/otel/metric_test.go create mode 100644 axiom/otel/otel.go delete mode 100644 axiom/otel/trace_config.go create mode 100644 examples/otelmetric/main.go rename examples/{oteltraces => oteltrace}/main.go (100%) diff --git a/.github/workflows/test_examples.yaml b/.github/workflows/test_examples.yaml index e79ebbd3..c3b6fc74 100644 --- a/.github/workflows/test_examples.yaml +++ b/.github/workflows/test_examples.yaml @@ -37,7 +37,8 @@ jobs: # - ingesthackernews - logrus - otelinstrument - - oteltraces + - otelmetric + - oteltrace - query - querylegacy # HINT(lukasmalkmus): This test would require Go 1.21 (but uses Go @@ -65,10 +66,13 @@ jobs: axiom dataset info $AXIOM_DATASET -f=json | jq -e 'any( .numEvents ; . == 3 )' - example: otelinstrument verify: | - axiom dataset info _traces -f=json | jq -e 'any( .numEvents ; . >= 1 )' - - example: oteltraces + axiom dataset info $AXIOM_DATASET -f=json | jq -e 'any( .numEvents ; . == 1 )' + - example: otelmetric + verify: | + axiom dataset info $AXIOM_DATASET -f=json | jq -e 'any( .numEvents ; . > 3 )' + - example: oteltrace verify: | - axiom dataset info _traces -f=json | jq -e 'any( .numEvents ; . >= 1 )' + axiom dataset info $AXIOM_DATASET -f=json | jq -e 'any( .numEvents ; . == 1 )' - example: query setup: | echo '[{"mood":"hyped","msg":"This is awesome!"}]' >> logs.json @@ -107,6 +111,10 @@ jobs: run: ${{ matrix.setup }} - name: Run example run: go run ./examples/${{ matrix.example }}/main.go + timeout-minutes: 5 + # We have some long running examples so cancel the step after a while. + # We still validate the example output in the next step. + continue-on-error: true - name: Verify example if: matrix.verify run: ${{ matrix.verify }} diff --git a/axiom/otel/config.go b/axiom/otel/config.go new file mode 100644 index 00000000..1307b7f7 --- /dev/null +++ b/axiom/otel/config.go @@ -0,0 +1,112 @@ +package otel + +import ( + "time" + + "github.com/axiomhq/axiom-go/internal/config" +) + +const ( + defaultMetricAPIEndpoint = "/v1/metrics" + defaultTraceAPIEndpoint = "/v1/traces" +) + +// Config is the configuration for OpenTelemetry components initialized by this +// helper package. This type is exported for convenience but an [Option] is +// naturally applied by one or more "Set"-prefixed functions. +type Config struct { + config.Config + + // APIEndpoint is the endpoint to use for an exporter. + APIEndpoint string + // Timeout is the timeout for an exporters underlying [http.Client]. + Timeout time.Duration + // NoEnv disables the use of "AXIOM_*" environment variables. + NoEnv bool +} + +func defaultMetricConfig() Config { + return Config{ + Config: config.Default(), + APIEndpoint: defaultMetricAPIEndpoint, + } +} + +func defaultTraceConfig() Config { + return Config{ + Config: config.Default(), + APIEndpoint: defaultTraceAPIEndpoint, + } +} + +// An Option modifies the behaviour of OpenTelemetry exporters. Nonetheless, +// the official "OTEL_*" environment variables are preferred over the options or +// "AXIOM_*" environment variables. +type Option func(c *Config) error + +// SetURL sets the base URL used by the client. +// +// Can also be specified using the "AXIOM_URL" environment variable. +func SetURL(baseURL string) Option { + return func(c *Config) error { return c.Options(config.SetURL(baseURL)) } +} + +// SetToken specifies the authentication token used by the client. +// +// Can also be specified using the "AXIOM_TOKEN" environment variable. +func SetToken(token string) Option { + return func(c *Config) error { return c.Options(config.SetToken(token)) } +} + +// SetOrganizationID specifies the organization ID used by the client. +// +// Can also be specified using the "AXIOM_ORG_ID" environment variable. +func SetOrganizationID(organizationID string) Option { + return func(c *Config) error { return c.Options(config.SetOrganizationID(organizationID)) } +} + +// SetAPIEndpoint specifies the api endpoint used by the client. +func SetAPIEndpoint(path string) Option { + return func(c *Config) error { + c.APIEndpoint = path + return nil + } +} + +// SetTimeout specifies the http timeout used by the client. +func SetTimeout(timeout time.Duration) Option { + return func(c *Config) error { + c.Timeout = timeout + return nil + } +} + +// SetNoEnv prevents the client from deriving its configuration from the +// environment (by auto reading "AXIOM_*" environment variables). +func SetNoEnv() Option { + return func(c *Config) error { + c.NoEnv = true + return nil + } +} + +func populateAndValidateConfig(base *Config, options ...Option) error { + // Apply supplied options. + for _, option := range options { + if option == nil { + continue + } else if err := option(base); err != nil { + return err + } + } + + // Make sure to populate remaining fields from the environment, if not + // explicitly disabled. + if !base.NoEnv { + if err := base.IncorporateEnvironment(); err != nil { + return err + } + } + + return base.Validate() +} diff --git a/axiom/otel/doc.go b/axiom/otel/doc.go index e9cc2e04..dc0754a6 100644 --- a/axiom/otel/doc.go +++ b/axiom/otel/doc.go @@ -4,23 +4,25 @@ // // import "github.com/axiomhq/axiom-go/axiom/otel" // -// Different levels of helpers are available, from just setting up tracing to -// getting access to lower level components to costumize tracing or integrate -// with existing OpenTelemetry setups: -// -// - [InitTracing]: Initializes OpenTelemetry and sets the global tracer -// prodiver so the official OpenTelemetry Go SDK can be used to get a tracer -// and instrument code. Sane defaults for the tracer provider are applied. -// - [TracerProvider]: Configures and returns a new OpenTelemetry tracer -// provider but does not set it as the global tracer provider. -// - [TraceExporter]: Configures and returns a new OpenTelemetry trace -// exporter. This sets up the exporter that sends traces to Axiom but allows -// for a more advanced setup of the tracer provider. +// Different levels of helpers are available, from just setting up +// instrumentation to getting access to lower level components to costumize +// instrumentation or integrate with existing OpenTelemetry setups: +// +// - [InitMetrics]/[InitTracing]: Initializes OpenTelemetry and sets the +// global meter/tracer prodiver so the official OpenTelemetry Go SDK can be +// used to get a meter/tracer and instrument code. Sane defaults for the +// providers are applied. +// - [MeterProvider]/[TracerProvider]: Configures and returns a new +// OpenTelemetry meter/tracer provider but does not set it as the global +// meter/tracer provider. +// - [MetricExporter]/[TraceExporter]: Configures and returns a new +// OpenTelemetry metric/trace exporter. This sets up the exporter that sends +// metrics/traces to Axiom but allows for a more advanced setup of the +// meter/tracer provider. // // If you wish for traces to propagate beyond the current process, you need to // set the global propagator to the OpenTelemetry trace context propagator. This -// can be done -// by calling: +// can be done by calling: // // import ( // "go.opentelemetry.io/otel" diff --git a/axiom/otel/metric.go b/axiom/otel/metric.go new file mode 100644 index 00000000..3631d91a --- /dev/null +++ b/axiom/otel/metric.go @@ -0,0 +1,108 @@ +package otel + +import ( + "context" + "fmt" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" +) + +// MetricExporter configures and returns a new exporter for OpenTelemetry spans. +func MetricExporter(ctx context.Context, dataset string, options ...Option) (metric.Exporter, error) { + config := defaultMetricConfig() + + if err := populateAndValidateConfig(&config, options...); err != nil { + return nil, err + } + + u, err := config.BaseURL().Parse(config.APIEndpoint) + if err != nil { + return nil, fmt.Errorf("parse exporter url: %w", err) + } + + opts := []otlpmetrichttp.Option{ + otlpmetrichttp.WithEndpoint(u.Host), + } + if u.Path != "" { + opts = append(opts, otlpmetrichttp.WithURLPath(u.Path)) + } + if u.Scheme == "http" { + opts = append(opts, otlpmetrichttp.WithInsecure()) + } + if config.Timeout > 0 { + opts = append(opts, otlpmetrichttp.WithTimeout(config.Timeout)) + } + + headers := make(map[string]string) + if config.Token() != "" { + headers["Authorization"] = "Bearer " + config.Token() + } + if config.OrganizationID() != "" { + headers["X-Axiom-Org-Id"] = config.OrganizationID() + } + if dataset != "" { + headers["X-Axiom-Dataset"] = dataset + } + if len(headers) > 0 { + opts = append(opts, otlpmetrichttp.WithHeaders(headers)) + } + + return otlpmetrichttp.New(ctx, opts...) +} + +// MeterProvider configures and returns a new OpenTelemetry meter provider. +func MeterProvider(ctx context.Context, dataset, serviceName, serviceVersion string, options ...Option) (*metric.MeterProvider, error) { + exporter, err := MetricExporter(ctx, dataset, options...) + if err != nil { + return nil, err + } + + rs, err := resource.Merge(resource.Default(), resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(serviceName), + semconv.ServiceVersionKey.String(serviceVersion), + UserAgentAttribute(), + )) + if err != nil { + return nil, err + } + + opts := []metric.Option{ + metric.WithReader(metric.NewPeriodicReader( + exporter, + metric.WithInterval(time.Second*5), // FIXME(lukasmalkmus): Just for testing! + metric.WithTimeout(time.Second*5), // FIXME(lukasmalkmus): Just for testing! + )), + metric.WithResource(rs), + } + + return metric.NewMeterProvider(opts...), nil +} + +// InitMetrics initializes OpenTelemetry metrics with the given service name, +// version and options. If initialization succeeds, the returned cleanup +// function must be called to shut down the meter provider and flush any +// remaining datapoints. The error returned by the cleanup function must be +// checked, as well. +func InitMetrics(ctx context.Context, dataset, serviceName, serviceVersion string, options ...Option) (func() error, error) { + meterProvider, err := MeterProvider(ctx, dataset, serviceName, serviceVersion, options...) + if err != nil { + return nil, err + } + + otel.SetMeterProvider(meterProvider) + + closeFunc := func() error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return meterProvider.Shutdown(ctx) + } + + return closeFunc, nil +} diff --git a/axiom/otel/metric_integration_test.go b/axiom/otel/metric_integration_test.go new file mode 100644 index 00000000..d119c395 --- /dev/null +++ b/axiom/otel/metric_integration_test.go @@ -0,0 +1,53 @@ +//go:build integration + +package otel_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + + "github.com/axiomhq/axiom-go/axiom" + axiotel "github.com/axiomhq/axiom-go/axiom/otel" +) + +func TestMetricsIntegration(t *testing.T) { + ctx := context.Background() + + datasetSuffix := os.Getenv("AXIOM_DATASET_SUFFIX") + if datasetSuffix == "" { + datasetSuffix = "local" + } + dataset := fmt.Sprintf("test-axiom-go-otel-metric-%s", datasetSuffix) + + client, err := axiom.NewClient() + require.NoError(t, err) + + _, err = client.Datasets.Create(ctx, axiom.DatasetCreateRequest{ + Name: dataset, + Description: "This is a test dataset for otel metric integration tests.", + }) + require.NoError(t, err) + + t.Cleanup(func() { + err = client.Datasets.Delete(ctx, dataset) + require.NoError(t, err) + }) + + stop, err := axiotel.InitMetrics(ctx, dataset, "axiom-go-otel-test-metric", "v1.0.0") + require.NoError(t, err) + require.NotNil(t, stop) + + t.Cleanup(func() { require.NoError(t, stop()) }) + + meter := otel.Meter("main") + + counter, err := meter.Int64Counter("test") + require.NoError(t, err) + + counter.Add(ctx, 1) +} diff --git a/axiom/otel/metric_test.go b/axiom/otel/metric_test.go new file mode 100644 index 00000000..85c10f6e --- /dev/null +++ b/axiom/otel/metric_test.go @@ -0,0 +1,54 @@ +package otel_test + +import ( + "context" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + + axiotel "github.com/axiomhq/axiom-go/axiom/otel" +) + +func TestMetrics(t *testing.T) { + var handlerCalled uint32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddUint32(&handlerCalled, 1) + + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/v1/metrics", r.URL.Path) + assert.Equal(t, "application/x-protobuf", r.Header.Get("Content-Type")) + assert.Equal(t, "metric-test-dataset", r.Header.Get("X-Axiom-Dataset")) + + w.WriteHeader(http.StatusNoContent) + })) + t.Cleanup(srv.Close) + + ctx := context.Background() + + stop, err := axiotel.InitMetrics(ctx, "metric-test-dataset", "axiom-go-otel-metrics-test", "v1.0.0", + axiotel.SetURL(srv.URL), + axiotel.SetToken("xaat-test-token"), + axiotel.SetNoEnv(), + ) + require.NoError(t, err) + require.NotNil(t, stop) + + t.Cleanup(func() { _ = stop() }) + + meter := otel.Meter("main") + + counter, err := meter.Int64Counter("test") + require.NoError(t, err) + + counter.Add(ctx, 1) + + // Stop meter which flushes all metrics. + require.NoError(t, stop()) + + assert.EqualValues(t, 1, atomic.LoadUint32(&handlerCalled)) +} diff --git a/axiom/otel/otel.go b/axiom/otel/otel.go new file mode 100644 index 00000000..29e01dad --- /dev/null +++ b/axiom/otel/otel.go @@ -0,0 +1,25 @@ +package otel + +import ( + "fmt" + + "go.opentelemetry.io/otel/attribute" + // Keep in sync with https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/resource/builtin.go#L25. + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + + "github.com/axiomhq/axiom-go/internal/version" +) + +var userAgent string + +func init() { + userAgent = "axiom-go" + if v := version.Get(); v != "" { + userAgent += fmt.Sprintf("/%s", v) + } +} + +// UserAgentAttribute returns a new OpenTelemetry axiom-go user agent attribute. +func UserAgentAttribute() attribute.KeyValue { + return semconv.UserAgentOriginal(userAgent) +} diff --git a/axiom/otel/trace.go b/axiom/otel/trace.go index 782871a7..7fbd2792 100644 --- a/axiom/otel/trace.go +++ b/axiom/otel/trace.go @@ -6,54 +6,18 @@ import ( "time" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" - - // Keep in sync with https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/resource/builtin.go#L25. semconv "go.opentelemetry.io/otel/semconv/v1.21.0" - - "github.com/axiomhq/axiom-go/internal/version" ) -var userAgent string - -func init() { - userAgent = "axiom-go" - if v := version.Get(); v != "" { - userAgent += fmt.Sprintf("/%s", v) - } -} - -// UserAgentAttribute returns a new OpenTelemetry axiom-go user agent attribute. -func UserAgentAttribute() attribute.KeyValue { - return semconv.UserAgentOriginal(userAgent) -} - // TraceExporter configures and returns a new exporter for OpenTelemetry spans. -func TraceExporter(ctx context.Context, dataset string, options ...TraceOption) (trace.SpanExporter, error) { +func TraceExporter(ctx context.Context, dataset string, options ...Option) (trace.SpanExporter, error) { config := defaultTraceConfig() - // Apply supplied options. - for _, option := range options { - if option == nil { - continue - } else if err := option(&config); err != nil { - return nil, err - } - } - - // Make sure to populate remaining fields from the environment, if not - // explicitly disabled. - if !config.NoEnv { - if err := config.IncorporateEnvironment(); err != nil { - return nil, err - } - } - - if err := config.Validate(); err != nil { + if err := populateAndValidateConfig(&config, options...); err != nil { return nil, err } @@ -93,7 +57,7 @@ func TraceExporter(ctx context.Context, dataset string, options ...TraceOption) } // TracerProvider configures and returns a new OpenTelemetry tracer provider. -func TracerProvider(ctx context.Context, dataset, serviceName, serviceVersion string, options ...TraceOption) (*trace.TracerProvider, error) { +func TracerProvider(ctx context.Context, dataset, serviceName, serviceVersion string, options ...Option) (*trace.TracerProvider, error) { exporter, err := TraceExporter(ctx, dataset, options...) if err != nil { return nil, err @@ -122,7 +86,7 @@ func TracerProvider(ctx context.Context, dataset, serviceName, serviceVersion st // function must be called to shut down the tracer provider and flush any // remaining spans. The error returned by the cleanup function must be checked, // as well. -func InitTracing(ctx context.Context, dataset, serviceName, serviceVersion string, options ...TraceOption) (func() error, error) { +func InitTracing(ctx context.Context, dataset, serviceName, serviceVersion string, options ...Option) (func() error, error) { tracerProvider, err := TracerProvider(ctx, dataset, serviceName, serviceVersion, options...) if err != nil { return nil, err diff --git a/axiom/otel/trace_config.go b/axiom/otel/trace_config.go deleted file mode 100644 index 91448cae..00000000 --- a/axiom/otel/trace_config.go +++ /dev/null @@ -1,78 +0,0 @@ -package otel - -import ( - "time" - - "github.com/axiomhq/axiom-go/internal/config" -) - -const defaultTraceAPIEndpoint = "/v1/traces" - -type traceConfig struct { - config.Config - - // APIEndpoint is the endpoint to use for the trace exporter. - APIEndpoint string - // Timeout is the timeout for the trace exporters underlying [http.Client]. - Timeout time.Duration - // NoEnv disables the use of "AXIOM_*" environment variables. - NoEnv bool -} - -func defaultTraceConfig() traceConfig { - return traceConfig{ - Config: config.Default(), - APIEndpoint: defaultTraceAPIEndpoint, - } -} - -// A TraceOption modifies the behaviour of OpenTelemetry traces. Nonetheless, -// the official "OTEL_*" environment variables are preferred over the options or -// "AXIOM_*" environment variables. -type TraceOption func(c *traceConfig) error - -// SetURL sets the base URL used by the client. -// -// Can also be specified using the "AXIOM_URL" environment variable. -func SetURL(baseURL string) TraceOption { - return func(c *traceConfig) error { return c.Options(config.SetURL(baseURL)) } -} - -// SetToken specifies the authentication token used by the client. -// -// Can also be specified using the "AXIOM_TOKEN" environment variable. -func SetToken(token string) TraceOption { - return func(c *traceConfig) error { return c.Options(config.SetToken(token)) } -} - -// SetOrganizationID specifies the organization ID used by the client. -// -// Can also be specified using the "AXIOM_ORG_ID" environment variable. -func SetOrganizationID(organizationID string) TraceOption { - return func(c *traceConfig) error { return c.Options(config.SetOrganizationID(organizationID)) } -} - -// SetAPIEndpoint specifies the api endpoint used by the client. -func SetAPIEndpoint(path string) TraceOption { - return func(c *traceConfig) error { - c.APIEndpoint = path - return nil - } -} - -// SetTimeout specifies the http timeout used by the client. -func SetTimeout(timeout time.Duration) TraceOption { - return func(c *traceConfig) error { - c.Timeout = timeout - return nil - } -} - -// SetNoEnv prevents the client from deriving its configuration from the -// environment (by auto reading "AXIOM_*" environment variables). -func SetNoEnv() TraceOption { - return func(c *traceConfig) error { - c.NoEnv = true - return nil - } -} diff --git a/axiom/otel/trace_integration_test.go b/axiom/otel/trace_integration_test.go index 0175db41..7fe0fdd0 100644 --- a/axiom/otel/trace_integration_test.go +++ b/axiom/otel/trace_integration_test.go @@ -24,14 +24,14 @@ func TestTracingIntegration(t *testing.T) { if datasetSuffix == "" { datasetSuffix = "local" } - dataset := fmt.Sprintf("test-axiom-go-otel-%s", datasetSuffix) + dataset := fmt.Sprintf("test-axiom-go-otel-trace-%s", datasetSuffix) client, err := axiom.NewClient() require.NoError(t, err) _, err = client.Datasets.Create(ctx, axiom.DatasetCreateRequest{ Name: dataset, - Description: "This is a test dataset for datasets integration tests.", + Description: "This is a test dataset for otel trace integration tests.", }) require.NoError(t, err) @@ -40,13 +40,11 @@ func TestTracingIntegration(t *testing.T) { require.NoError(t, err) }) - stop, err := axiotel.InitTracing(ctx, dataset, "axiom-go-otel-test", "v1.0.0") + stop, err := axiotel.InitTracing(ctx, dataset, "axiom-go-otel-test-trace", "v1.0.0") require.NoError(t, err) require.NotNil(t, stop) - t.Cleanup(func() { - require.NoError(t, stop()) - }) + t.Cleanup(func() { require.NoError(t, stop()) }) bar := func(ctx context.Context) { tr := otel.Tracer("bar") diff --git a/axiom/otel/trace_test.go b/axiom/otel/trace_test.go index e6551bd6..4bf194a4 100644 --- a/axiom/otel/trace_test.go +++ b/axiom/otel/trace_test.go @@ -24,7 +24,7 @@ func TestTracing(t *testing.T) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/v1/traces", r.URL.Path) assert.Equal(t, "application/x-protobuf", r.Header.Get("Content-Type")) - assert.Equal(t, "test-dataset", r.Header.Get("X-Axiom-Dataset")) + assert.Equal(t, "trace-test-dataset", r.Header.Get("X-Axiom-Dataset")) w.WriteHeader(http.StatusNoContent) })) @@ -32,7 +32,7 @@ func TestTracing(t *testing.T) { ctx := context.Background() - stop, err := axiotel.InitTracing(ctx, "test-dataset", "axiom-go-otel-test", "v1.0.0", + stop, err := axiotel.InitTracing(ctx, "trace-test-dataset", "axiom-go-otel-traces-test", "v1.0.0", axiotel.SetURL(srv.URL), axiotel.SetToken("xaat-test-token"), axiotel.SetNoEnv(), @@ -40,9 +40,7 @@ func TestTracing(t *testing.T) { require.NoError(t, err) require.NotNil(t, stop) - t.Cleanup(func() { - _ = stop() - }) + t.Cleanup(func() { _ = stop() }) bar := func(ctx context.Context) { tr := otel.Tracer("bar") diff --git a/examples/README.md b/examples/README.md index 540d3463..cf621de8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -65,5 +65,7 @@ To quickstart, export the environment variables below. - [otelinstrument](otelinstrument/main.go): How to instrument the Axiom Go client using OpenTelemetry. -- [oteltraces](oteltraces/main.go): How to ship traces to Axiom using the +- [otelmetric](otelmetric/main.go): How to ship metrics to Axiom using the + OpenTelemetry Go SDK and the Axiom SDKs `otel` helper package. +- [oteltrace](oteltrace/main.go): How to ship traces to Axiom using the OpenTelemetry Go SDK and the Axiom SDKs `otel` helper package. diff --git a/examples/otelinstrument/main.go b/examples/otelinstrument/main.go index c923379f..e26ae33d 100644 --- a/examples/otelinstrument/main.go +++ b/examples/otelinstrument/main.go @@ -23,6 +23,7 @@ func main() { } // 1. Initialize OpenTelemetry. + // // Note: You can setup OpenTelemetry however you like! This example uses the // helper package axiom/otel to initialize OpenTelemetry with Axiom // configured as a backend for convenience. @@ -43,6 +44,7 @@ func main() { } // 3. Use the client as usual ⚡ + // // This will send traces to the configured OpenTelemetry collector (in this // case Axiom itself). user, err := client.Users.Current(ctx) diff --git a/examples/otelmetric/main.go b/examples/otelmetric/main.go new file mode 100644 index 00000000..b2e55e68 --- /dev/null +++ b/examples/otelmetric/main.go @@ -0,0 +1,54 @@ +// The purpose of this example is to show how to send OpenTelemetry metrics to +// Axiom. +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + + "go.opentelemetry.io/contrib/instrumentation/host" + "go.opentelemetry.io/contrib/instrumentation/runtime" + + axiotel "github.com/axiomhq/axiom-go/axiom/otel" +) + +func main() { + // Export "AXIOM_DATASET" in addition to the required environment variables. + + ctx, stop := signal.NotifyContext(context.Background(), + os.Interrupt, + syscall.SIGTERM, + ) + defer stop() + + dataset := os.Getenv("AXIOM_DATASET") + if dataset == "" { + log.Fatal("AXIOM_DATASET is required") + } + + // 1. Initialize OpenTelemetry. + shutdown, err := axiotel.InitMetrics(ctx, dataset, "axiom-otel-example", "v1.0.0") + if err != nil { + log.Fatal(err) + } + defer func() { + if shutdownErr := shutdown(); shutdownErr != nil { + log.Fatal(shutdownErr) + } + }() + + // 2. Instrument ⚡ + // + // For manual instrumentation, refer to the documentation of the + // OpenTelemetry Go SDK. + if err = runtime.Start(); err != nil { + log.Fatal(err) + } else if err = host.Start(); err != nil { + log.Fatal(err) + } + + <-ctx.Done() +} diff --git a/examples/oteltraces/main.go b/examples/oteltrace/main.go similarity index 100% rename from examples/oteltraces/main.go rename to examples/oteltrace/main.go diff --git a/go.mod b/go.mod index fbf21b2d..b7c2e867 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,16 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 github.com/tidwall/sjson v1.2.5 + go.opentelemetry.io/contrib/instrumentation/host 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/contrib/instrumentation/runtime v0.46.1 go.opentelemetry.io/otel v1.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp 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 go.uber.org/zap v1.26.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa @@ -76,6 +80,7 @@ require ( github.com/go-critic/go-critic v0.9.0 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.1.0 // indirect @@ -124,6 +129,7 @@ require ( github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.1 // indirect github.com/lufeee/execinquery v1.2.1 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/macabu/inamedparam v0.1.2 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect @@ -149,6 +155,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.4.5 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.13.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect @@ -165,6 +172,8 @@ require ( github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect github.com/securego/gosec/v2 v2.18.2 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/shirou/gopsutil/v3 v3.23.10 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/sivchari/tenv v1.7.1 // indirect @@ -188,6 +197,8 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect @@ -197,6 +208,7 @@ require ( github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.2.0 // indirect github.com/ykadowak/zerologlint v0.1.3 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect gitlab.com/bosi/decorder v0.4.1 // indirect go-simpler.org/sloglint v0.1.2 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect diff --git a/go.sum b/go.sum index 6ea21264..c3a90747 100644 --- a/go.sum +++ b/go.sum @@ -187,6 +187,8 @@ 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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -391,6 +393,8 @@ github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/macabu/inamedparam v0.1.2 h1:RR5cnayM6Q7cDhQol32DE2BGAPGMnffJ31LFE+UklaU= github.com/macabu/inamedparam v0.1.2/go.mod h1:Xg25QvY7IBRl1KLPV9Rbml8JOMZtF/iAkNkmV7eQgjw= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -474,6 +478,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.4.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI= github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -532,6 +538,12 @@ github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6c github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil/v3 v3.23.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM= +github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -616,6 +628,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= @@ -641,6 +657,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E= @@ -652,12 +670,18 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/host v0.46.1 h1:jLPv7OPP2CROWQ8PaUx3zONn5S4HjCJnH1syT3fnEEc= +go.opentelemetry.io/contrib/instrumentation/host v0.46.1/go.mod h1:7PhaLiZ6K9zbeZNxOdr+DB8tzxWsrjVa9BcCMGuMPeA= 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/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc= +go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0= 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/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= 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/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= @@ -666,6 +690,8 @@ go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ3 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= @@ -828,6 +854,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w 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-20190916202348-b4ddaad3f8a3/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= @@ -851,6 +878,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w 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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -878,6 +906,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=