// This template contains a simple // app with OTEL bootstrap that will create an // HTTP server configured by environment that exports // spans and metrics to an OTEL collector if configured // to do so. Will also stand up a prometheus metrics // endpoint. // // Configuration and logger stored in context // Reference implementation of the provided packages package main import ( "context" "flag" "fmt" "os" "os/signal" "sync" "time" "github.com/rs/zerolog" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "golang.org/x/sys/unix" "gitea.libretechconsulting.com/rmcguire/go-app/pkg/app" "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" "gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/config" "gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/demo" "gitea.libretechconsulting.com/rmcguire/go-server-with-otel/pkg/service" ) const ( terminationGracePeriod = 30 * time.Second appName = "demo-app" ) var ( // TODO: Register your service.AppService implementers here // This should be the only change necessary here // NOTE: At least one service needs to add a dial option // for transport credentials, otherwise you will have to do it here appServices = []service.AppService{ &demo.DemoService{}, } flagSchema bool ) func main() { ctx, cncl := signal.NotifyContext(context.Background(), os.Interrupt, unix.SIGTERM) defer cncl() // Load configuration and setup logging. The go-app framework // will handle loading config and environment into our demo // app config struct which embeds app.AooConfig ctx, serviceConfig := app.MustLoadConfigInto(ctx, &config.ServiceConfig{}) // Print schema if that's all we have to do if flagSchema { printSchema() os.Exit(1) } // Prepare app app := &app.App{ AppContext: ctx, } app.InitOTEL() tracer := otel.GetTracer(ctx, appName) ctx, span := tracer.Start(ctx, appName+".startup", trace.WithAttributes( attribute.Int("appServices", len(appServices)), )) log := zerolog.Ctx(ctx) log.Debug().Any("mergedConfig", serviceConfig).Msg("App configuration prepared") // Prepare services var err error sdFuncs := make([]service.ShutdownFunc, len(appServices)) for i, svc := range appServices { sdFuncs[i], err = svc.Init(ctx, serviceConfig) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) log.Fatal().Err(err).Send() } } span.AddEvent("app services initialized") // Merge all service implementations and add to app grpcSvcs, httpSvcs := service.MergeServices(ctx, appServices...) app.GRPC = grpcSvcs app.HTTP = httpSvcs // Launch app app.MustRun() span.AddEvent("app started") span.SetStatus(codes.Ok, "") span.End() // Wait for app to complete and then perform internal shutdown <-app.Done() log.Info().Int("numServices", len(appServices)).Msg("shutting down app services") sdCtx, cncl := context.WithTimeout(context.Background(), terminationGracePeriod) defer cncl() // Call registered shutdown funcs from all services var sdWg sync.WaitGroup for _, sdFunc := range sdFuncs { sdWg.Add(1) go func() { defer sdWg.Done() svcName, err := sdFunc(sdCtx) log.Err(err).Str("service", svcName).Msg("terminated") }() } sdWg.Wait() } // flag.Parse will be called by go-app func init() { flag.BoolVar(&flagSchema, "schema", false, "generate json schema and exit") } func printSchema() { bytes, err := app.CustomSchema(&config.ServiceConfig{}) if err != nil { panic(err) } fmt.Println(string(bytes)) os.Exit(0) }