package main import ( "context" "log/slog" "net/http" "os" "connectrpc.com/connect" connectcors "connectrpc.com/cors" "connectrpc.com/grpcreflect" "connectrpc.com/validate" "git.kocoder.xyz/vt/shortener/internal/config" "git.kocoder.xyz/vt/shortener/internal/database" "git.kocoder.xyz/vt/shortener/internal/proto/shorten/v1/shortenv1connect" "git.kocoder.xyz/vt/shortener/internal/service" "git.kocoder.xyz/vt/shortener/internal/telemetry" _ "github.com/lib/pq" "github.com/rs/cors" "github.com/uptrace/opentelemetry-go-extra/otelsql" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/attribute" ) func main() { // Fallback logger for early startup errors slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) conf := config.Read() ctx := context.Background() shutdown, err := telemetry.Init(ctx, conf.OTLP_ENDPOINT, "shortener-server") if err != nil { slog.Error("Failed to initialize telemetry", slog.String("err", err.Error())) } defer func() { if err := shutdown(ctx); err != nil { slog.Error("Error shutting down telemetry", slog.String("err", err.Error())) } }() // Connect to DB and wrap with otelsql for database tracing db, err := otelsql.Open("postgres", conf.DB_URL) if err != nil { slog.Error("failed to connect to database", slog.String("err", err.Error())) os.Exit(1) } defer db.Close() otelsql.ReportDBStatsMetrics(db, otelsql.WithAttributes( attribute.String("db.system", "postgresql"), )) dbQueries := database.New(db) srv := service.NewServer(conf, dbQueries) mux := http.NewServeMux() path, handler := shortenv1connect.NewShortenServiceHandler( service.NewShortenerService(conf, dbQueries), connect.WithInterceptors(validate.NewInterceptor()), ) mux.Handle(path, handler) reflector := grpcreflect.NewStaticReflector(shortenv1connect.ShortenServiceName) mux.Handle(grpcreflect.NewHandlerV1(reflector)) mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) mux.Handle("/", srv) c := cors.New(cors.Options{ AllowedOrigins: conf.ALLOWED_ORIGINS, AllowedMethods: connectcors.AllowedMethods(), AllowedHeaders: connectcors.AllowedHeaders(), ExposedHeaders: connectcors.ExposedHeaders(), MaxAge: 7200, }) corsMux := c.Handler(mux) // Wrap the server with OpenTelemetry HTTP handler for tracing & metrics instrumentedHandler := otelhttp.NewHandler(corsMux, "shortener-server2") p := new(http.Protocols) p.SetHTTP1(true) // Use h2c so we can serve HTTP/2 without TLS. p.SetUnencryptedHTTP2(true) s := http.Server{ Addr: conf.LISTEN_ON, Handler: instrumentedHandler, Protocols: p, } slog.Info("Starting server on " + conf.LISTEN_ON) if err := s.ListenAndServe(); err != nil { slog.Error("Server failed", slog.String("err", err.Error())) } }