diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go new file mode 100644 index 0000000..32eef42 --- /dev/null +++ b/internal/telemetry/telemetry.go @@ -0,0 +1,101 @@ +package telemetry + +import ( + "context" + "errors" + "log/slog" + "strings" + + "go.opentelemetry.io/contrib/bridges/otelslog" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/log/global" + sdklog "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/sdk/trace" +) + +func newResource(serviceName string) *resource.Resource { + return resource.NewWithAttributes( + resource.Default().SchemaURL(), + attribute.String("service.name", serviceName), + ) +} + +// Init sets up OpenTelemetry tracing, metrics, and logs via OTLP Push. +// It returns a shutdown function that should be deferred in main. +func Init(ctx context.Context, endpoint string, serviceName string) (func(context.Context) error, error) { + res := newResource(serviceName) + + var shutdownFuncs []func(context.Context) error + shutdown := func(ctx context.Context) error { + var err error + for _, fn := range shutdownFuncs { + if fnErr := fn(ctx); fnErr != nil { + err = errors.Join(err, fnErr) + } + } + return err + } + + var traceOpts []otlptracehttp.Option + var metricOpts []otlpmetrichttp.Option + var logOpts []otlploghttp.Option + + if endpoint != "" { + endpoint = strings.TrimSuffix(endpoint, "/") + traceOpts = append(traceOpts, otlptracehttp.WithEndpointURL(endpoint+"/v1/traces")) + metricOpts = append(metricOpts, otlpmetrichttp.WithEndpointURL(endpoint+"/v1/metrics")) + logOpts = append(logOpts, otlploghttp.WithEndpointURL(endpoint+"/v1/logs")) + } else { + // Default to insecure localhost if no endpoint is specified + traceOpts = append(traceOpts, otlptracehttp.WithInsecure()) + metricOpts = append(metricOpts, otlpmetrichttp.WithInsecure()) + logOpts = append(logOpts, otlploghttp.WithInsecure()) + } + + // Trace setup + traceExporter, err := otlptracehttp.New(ctx, traceOpts...) + if err != nil { + return shutdown, err + } + tp := trace.NewTracerProvider( + trace.WithBatcher(traceExporter), + trace.WithResource(res), + ) + otel.SetTracerProvider(tp) + shutdownFuncs = append(shutdownFuncs, tp.Shutdown) + + // Metrics setup + metricExporter, err := otlpmetrichttp.New(ctx, metricOpts...) + if err != nil { + return shutdown, err + } + mp := metric.NewMeterProvider( + metric.WithReader(metric.NewPeriodicReader(metricExporter)), + metric.WithResource(res), + ) + otel.SetMeterProvider(mp) + shutdownFuncs = append(shutdownFuncs, mp.Shutdown) + + // Logs setup + logExporter, err := otlploghttp.New(ctx, logOpts...) + if err != nil { + return shutdown, err + } + lp := sdklog.NewLoggerProvider( + sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)), + sdklog.WithResource(res), + ) + global.SetLoggerProvider(lp) + shutdownFuncs = append(shutdownFuncs, lp.Shutdown) + + // Route slog to OpenTelemetry + slog.SetDefault(otelslog.NewLogger(serviceName)) + + return shutdown, nil +} diff --git a/k6.js b/k6.js new file mode 100644 index 0000000..9b1b700 --- /dev/null +++ b/k6.js @@ -0,0 +1,12 @@ +import http from "k6/http"; +import { sleep } from "k6"; + +export const options = { + iterations: 100, +}; + +export default function () { + http.get("http://localhost:3001/abcdefghij"); + + sleep(0.2); +}