Openstatus www.openstatus.dev
at main 123 lines 3.1 kB view raw
1package server 2 3import ( 4 "context" 5 "fmt" 6 "log/slog" 7 "net/http" 8 "os" 9 "strconv" 10 "time" 11 12 "github.com/jmoiron/sqlx" 13 _ "github.com/joho/godotenv/autoload" 14 "go.opentelemetry.io/contrib/bridges/otelslog" 15 "go.opentelemetry.io/otel/attribute" 16 "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" 17 "go.opentelemetry.io/otel/log/global" 18 sdklog "go.opentelemetry.io/otel/sdk/log" 19 "go.opentelemetry.io/otel/sdk/resource" 20 semconv "go.opentelemetry.io/otel/semconv/v1.21.0" 21 22 "github.com/openstatushq/openstatus/apps/private-location/internal/database" 23) 24 25type Server struct { 26 port int 27 db *sqlx.DB 28 logger *slog.Logger 29 logProvider *sdklog.LoggerProvider 30} 31 32// NewServer returns an HTTP server and a cleanup function to shutdown the log provider. 33func NewServer() (*http.Server, func(context.Context)) { 34 portStr := os.Getenv("PORT") 35 if portStr == "" { 36 portStr = "8080" 37 } 38 port, err := strconv.Atoi(portStr) 39 if err != nil { 40 fmt.Fprintf(os.Stderr, "invalid PORT value %q: %v\n", portStr, err) 41 os.Exit(1) 42 } 43 44 logger, logProvider := setupLogger() 45 46 newServer := &Server{ 47 port: port, 48 db: database.New(), 49 logger: logger, 50 logProvider: logProvider, 51 } 52 53 // Declare Server config 54 server := &http.Server{ 55 Addr: fmt.Sprintf(":%d", newServer.port), 56 Handler: newServer.RegisterRoutes(), 57 IdleTimeout: time.Minute, 58 ReadTimeout: 10 * time.Second, 59 WriteTimeout: 30 * time.Second, 60 } 61 62 // Return cleanup function for graceful shutdown 63 cleanup := func(ctx context.Context) { 64 if logProvider != nil { 65 logProvider.Shutdown(ctx) 66 } 67 database.Close() 68 } 69 70 return server, cleanup 71} 72 73func setupLogger() (*slog.Logger, *sdklog.LoggerProvider) { 74 ctx := context.Background() 75 76 axiomToken := env("AXIOM_TOKEN", "") 77 axiomDataset := env("AXIOM_DATASET", "dev") 78 79 // If no Axiom token, return a standard logger 80 if axiomToken == "" { 81 logger := slog.Default() 82 return logger, nil 83 } 84 85 res := resource.NewWithAttributes( 86 semconv.SchemaURL, 87 semconv.ServiceNameKey.String("openstatus-private-location"), 88 semconv.ServiceVersionKey.String("1.0.0"), 89 attribute.String("environment", "production"), 90 ) 91 92 // Set up OTLP log exporter for Axiom 93 exporter, err := otlploghttp.New(ctx, 94 otlploghttp.WithEndpointURL("https://eu-central-1.aws.edge.axiom.co/v1/logs"), 95 otlploghttp.WithHeaders(map[string]string{ 96 "Authorization": "Bearer " + axiomToken, 97 "X-Axiom-Dataset": axiomDataset, 98 }), 99 ) 100 if err != nil { 101 fmt.Fprintf(os.Stderr, "failed to create OTLP exporter: %v\n", err) 102 return slog.Default(), nil 103 } 104 105 // Create log provider with resource and batch processor 106 logProvider := sdklog.NewLoggerProvider( 107 sdklog.WithResource(res), 108 sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)), 109 ) 110 111 global.SetLoggerProvider(logProvider) 112 logger := otelslog.NewLogger("openstatus-private-location") 113 slog.SetDefault(logger) 114 115 return logger, logProvider 116} 117 118func env(key, fallback string) string { 119 if value, ok := os.LookupEnv(key); ok { 120 return value 121 } 122 return fallback 123}