mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-12 16:23:03 +03:00
204 lines
5.6 KiB
Go
204 lines
5.6 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
sloggin "github.com/gin-contrib/slog"
|
|
|
|
"github.com/lmittmann/tint"
|
|
"github.com/mattn/go-isatty"
|
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
|
"go.opentelemetry.io/contrib/exporters/autoexport"
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
"go.opentelemetry.io/otel"
|
|
globallog "go.opentelemetry.io/otel/log/global"
|
|
metricnoop "go.opentelemetry.io/otel/metric/noop"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
sdklog "go.opentelemetry.io/otel/sdk/log"
|
|
"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.30.0"
|
|
tracenoop "go.opentelemetry.io/otel/trace/noop"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
|
)
|
|
|
|
func defaultResource() (*resource.Resource, error) {
|
|
return resource.Merge(
|
|
resource.Default(),
|
|
resource.NewSchemaless(
|
|
semconv.ServiceName(common.Name),
|
|
semconv.ServiceVersion(common.Version),
|
|
),
|
|
)
|
|
}
|
|
|
|
func initObservability(ctx context.Context, metrics, traces bool) (shutdownFns []utils.Service, httpClient *http.Client, err error) {
|
|
resource, err := defaultResource()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create OpenTelemetry resource: %w", err)
|
|
}
|
|
|
|
shutdownFns = make([]utils.Service, 0, 2)
|
|
|
|
httpClient = &http.Client{}
|
|
defaultTransport, ok := http.DefaultTransport.(*http.Transport)
|
|
if !ok {
|
|
// Indicates a development-time error
|
|
panic("Default transport is not of type *http.Transport")
|
|
}
|
|
httpClient.Transport = defaultTransport.Clone()
|
|
|
|
// Logging
|
|
err = initOtelLogging(ctx, resource)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Tracing
|
|
tracingShutdownFn, err := initOtelTracing(ctx, traces, resource, httpClient)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
} else if tracingShutdownFn != nil {
|
|
shutdownFns = append(shutdownFns, tracingShutdownFn)
|
|
}
|
|
|
|
// Metrics
|
|
metricsShutdownFn, err := initOtelMetrics(ctx, metrics, resource)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
} else if metricsShutdownFn != nil {
|
|
shutdownFns = append(shutdownFns, metricsShutdownFn)
|
|
}
|
|
|
|
return shutdownFns, httpClient, nil
|
|
}
|
|
|
|
func initOtelLogging(ctx context.Context, resource *resource.Resource) error {
|
|
// If the env var OTEL_LOGS_EXPORTER is empty, we set it to "none", for autoexport to work
|
|
if os.Getenv("OTEL_LOGS_EXPORTER") == "" {
|
|
os.Setenv("OTEL_LOGS_EXPORTER", "none")
|
|
}
|
|
exp, err := autoexport.NewLogExporter(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize OpenTelemetry log exporter: %w", err)
|
|
}
|
|
|
|
level, _ := sloggin.ParseLevel(common.EnvConfig.LogLevel)
|
|
|
|
// Create the handler
|
|
var handler slog.Handler
|
|
if common.EnvConfig.LogJSON {
|
|
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: level,
|
|
})
|
|
} else {
|
|
handler = tint.NewHandler(os.Stdout, &tint.Options{
|
|
TimeFormat: time.Stamp,
|
|
Level: level,
|
|
NoColor: !isatty.IsTerminal(os.Stdout.Fd()),
|
|
})
|
|
}
|
|
|
|
// Create the logger provider
|
|
provider := sdklog.NewLoggerProvider(
|
|
sdklog.WithProcessor(
|
|
sdklog.NewBatchProcessor(exp),
|
|
),
|
|
sdklog.WithResource(resource),
|
|
)
|
|
|
|
// Set the logger provider globally
|
|
globallog.SetLoggerProvider(provider)
|
|
|
|
// Wrap the handler in a "fanout" one
|
|
handler = utils.LogFanoutHandler{
|
|
handler,
|
|
otelslog.NewHandler(common.Name, otelslog.WithLoggerProvider(provider)),
|
|
}
|
|
|
|
// Set the default slog to send logs to OTel and add the app name
|
|
log := slog.New(handler).
|
|
With(slog.String("app", common.Name)).
|
|
With(slog.String("version", common.Version))
|
|
slog.SetDefault(log)
|
|
|
|
return nil
|
|
}
|
|
|
|
func initOtelTracing(ctx context.Context, traces bool, resource *resource.Resource, httpClient *http.Client) (shutdownFn utils.Service, err error) {
|
|
if !traces {
|
|
otel.SetTracerProvider(tracenoop.NewTracerProvider())
|
|
return nil, nil
|
|
}
|
|
|
|
tr, err := autoexport.NewSpanExporter(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize OpenTelemetry span exporter: %w", err)
|
|
}
|
|
tp := sdktrace.NewTracerProvider(
|
|
sdktrace.WithResource(resource),
|
|
sdktrace.WithBatcher(tr),
|
|
)
|
|
|
|
otel.SetTracerProvider(tp)
|
|
otel.SetTextMapPropagator(
|
|
propagation.NewCompositeTextMapPropagator(
|
|
propagation.TraceContext{},
|
|
propagation.Baggage{},
|
|
),
|
|
)
|
|
|
|
shutdownFn = func(shutdownCtx context.Context) error { //nolint:contextcheck
|
|
tpCtx, tpCancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
|
defer tpCancel()
|
|
shutdownErr := tp.Shutdown(tpCtx)
|
|
if shutdownErr != nil {
|
|
return fmt.Errorf("failed to gracefully shut down traces exporter: %w", shutdownErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Add tracing to the HTTP client
|
|
httpClient.Transport = otelhttp.NewTransport(httpClient.Transport)
|
|
|
|
return shutdownFn, nil
|
|
}
|
|
|
|
func initOtelMetrics(ctx context.Context, metrics bool, resource *resource.Resource) (shutdownFn utils.Service, err error) {
|
|
if !metrics {
|
|
otel.SetMeterProvider(metricnoop.NewMeterProvider())
|
|
return nil, nil
|
|
}
|
|
|
|
mr, err := autoexport.NewMetricReader(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize OpenTelemetry metric reader: %w", err)
|
|
}
|
|
|
|
mp := metric.NewMeterProvider(
|
|
metric.WithResource(resource),
|
|
metric.WithReader(mr),
|
|
)
|
|
otel.SetMeterProvider(mp)
|
|
|
|
shutdownFn = func(shutdownCtx context.Context) error { //nolint:contextcheck
|
|
mpCtx, mpCancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
|
defer mpCancel()
|
|
shutdownErr := mp.Shutdown(mpCtx)
|
|
if shutdownErr != nil {
|
|
return fmt.Errorf("failed to gracefully shut down metrics exporter: %w", shutdownErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return shutdownFn, nil
|
|
}
|