2025-01-03 22:13:15 +00:00
|
|
|
// 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.
|
2025-01-04 02:09:40 +00:00
|
|
|
//
|
|
|
|
// Configuration and logger stored in context
|
2025-01-04 05:06:49 +00:00
|
|
|
// Reference implementation of the provided packages
|
2025-01-03 22:13:15 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-01-04 05:06:49 +00:00
|
|
|
"net/http"
|
2025-01-03 22:13:15 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2025-01-04 05:06:49 +00:00
|
|
|
"sync"
|
2025-01-04 02:49:59 +00:00
|
|
|
"time"
|
2025-01-03 22:13:15 +00:00
|
|
|
|
2025-01-04 02:09:40 +00:00
|
|
|
"github.com/rs/zerolog"
|
2025-01-04 02:49:59 +00:00
|
|
|
"go.opentelemetry.io/otel/codes"
|
2025-01-04 05:06:49 +00:00
|
|
|
"go.opentelemetry.io/otel/trace"
|
2025-01-03 22:13:15 +00:00
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
|
|
|
"gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/config"
|
2025-01-04 02:09:40 +00:00
|
|
|
"gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/logging"
|
2025-01-04 05:06:49 +00:00
|
|
|
"gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/otel"
|
|
|
|
"gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/srv"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
cfg *config.AppConfig
|
|
|
|
l *zerolog.Logger
|
|
|
|
tracer trace.Tracer
|
2025-01-03 22:13:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
ctx, cncl := signal.NotifyContext(context.Background(), os.Interrupt, unix.SIGTERM)
|
|
|
|
defer cncl()
|
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
shutdownFuncs := make([]func(context.Context) error, 0)
|
2025-01-03 22:13:15 +00:00
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
// Load configuration and setup logging
|
|
|
|
ctx = setupConfigAndLogging(ctx)
|
2025-01-04 02:49:59 +00:00
|
|
|
|
|
|
|
// Set up OTEL
|
2025-01-04 05:06:49 +00:00
|
|
|
ctx, otelShutdown := otel.Init(ctx)
|
|
|
|
shutdownFuncs = append(shutdownFuncs, otelShutdown)
|
|
|
|
tracer = otel.MustTracerFromCtx(ctx)
|
2025-01-04 02:49:59 +00:00
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
// Start App
|
|
|
|
ctx, initSpan := tracer.Start(ctx, "init")
|
2025-01-04 02:49:59 +00:00
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
// Start HTTP Server
|
|
|
|
dummyFuncs := []srv.HTTPFunc{
|
|
|
|
{
|
|
|
|
Path: "/dummy",
|
|
|
|
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Write([]byte("hello world"))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
httpShutdown, httpDone := srv.MustInitHTTPServer(ctx, dummyFuncs...)
|
|
|
|
shutdownFuncs = append(shutdownFuncs, httpShutdown)
|
2025-01-04 02:49:59 +00:00
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
// Startup Complete
|
2025-01-04 02:09:40 +00:00
|
|
|
l.Info().
|
|
|
|
Str("name", cfg.Name).
|
|
|
|
Str("version", cfg.Version).
|
|
|
|
Str("logLevel", cfg.Logging.Level).
|
|
|
|
Msg("app initialized")
|
2025-01-04 02:49:59 +00:00
|
|
|
initSpan.SetStatus(codes.Ok, "")
|
|
|
|
initSpan.End()
|
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
// Wait for signal
|
|
|
|
select {
|
|
|
|
case <-httpDone:
|
|
|
|
l.Warn().Msg("shutting down early on http server done")
|
|
|
|
case <-ctx.Done():
|
|
|
|
l.Warn().Str("reason", ctx.Err().Error()).
|
|
|
|
Msg("shutting down on context done")
|
|
|
|
}
|
|
|
|
shutdown(shutdownFuncs...)
|
2025-01-03 22:13:15 +00:00
|
|
|
}
|
|
|
|
|
2025-01-04 05:06:49 +00:00
|
|
|
func shutdown(shutdownFuncs ...func(context.Context) error) {
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
doneCtx, cncl := context.WithTimeout(context.Background(), 15*time.Second)
|
|
|
|
defer func() {
|
|
|
|
if doneCtx.Err() == context.DeadlineExceeded {
|
|
|
|
l.Err(doneCtx.Err()).
|
|
|
|
Dur("shutdownTime", time.Since(now)).
|
|
|
|
Msg("app shutdown aborted")
|
|
|
|
} else {
|
|
|
|
l.Info().
|
|
|
|
Int("shutdownFuncsCalled", len(shutdownFuncs)).
|
|
|
|
Dur("shutdownTime", time.Since(now)).
|
|
|
|
Msg("app shutdown normally")
|
|
|
|
}
|
|
|
|
cncl()
|
|
|
|
}()
|
|
|
|
|
|
|
|
doneCtx, span := tracer.Start(doneCtx, "shutdown")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(len(shutdownFuncs))
|
|
|
|
|
|
|
|
for _, f := range shutdownFuncs {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
err := f(doneCtx)
|
|
|
|
if err != nil {
|
|
|
|
span.SetStatus(codes.Error, "shutdown failed")
|
|
|
|
span.RecordError(err)
|
|
|
|
l.Err(err).Send()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupConfigAndLogging(ctx context.Context) context.Context {
|
|
|
|
ctx, err := config.LoadConfig(ctx)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg = config.MustFromCtx(ctx)
|
|
|
|
ctx = logging.MustInitLogging(ctx)
|
|
|
|
l = zerolog.Ctx(ctx)
|
|
|
|
|
|
|
|
l.Trace().Any("config", *cfg).Send()
|
|
|
|
return ctx
|
|
|
|
}
|