Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.
/ server Public archive

HTTP Server, Router, Middleware + Telemetry

License

Notifications You must be signed in to change notification settings

x-ethr/server

Repository files navigation

server - HTTP Routing, Logging & Telemetry

Task-Board

  • Complete Documentation
  • Middleware Unit-Testing

Documentation

Official godoc documentation (with examples) can be found at the Package Registry.

Usage

Add Package Dependency
go get -u github.com/x-ethr/server
Import & Implement

main.go

package main

import (
    "context"
    "errors"
    "flag"
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "path/filepath"
    "runtime"
    "time"

    "github.com/x-ethr/server"
    "github.com/x-ethr/server/logging"
    "github.com/x-ethr/server/middleware"
    "github.com/x-ethr/server/middleware/name"
    "github.com/x-ethr/server/middleware/servername"
    "github.com/x-ethr/server/middleware/timeout"
    "github.com/x-ethr/server/middleware/tracing"
    "github.com/x-ethr/server/middleware/versioning"
    "github.com/x-ethr/server/telemetry"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "go.opentelemetry.io/otel"

    "authentication-service/internal/api/login"
    "authentication-service/internal/api/metadata"
    "authentication-service/internal/api/refresh"
    "authentication-service/internal/api/registration"
)

// header is a dynamically linked string value - defaults to "server" - which represents the server name.
var header string = "server"

// service is a dynamically linked string value - defaults to "service" - which represents the service name.
var service string = "service"

// version is a dynamically linked string value - defaults to "development" - which represents the service's version.
var version string = "development" // production builds have version dynamically linked

// ctx, cancel represent the server's runtime context and cancellation handler.
var ctx, cancel = context.WithCancel(context.Background())

// port represents a cli flag that sets the server listening port
var port = flag.String("port", "8080", "Server Listening Port.")

var logger *slog.Logger

var (
    tracer = otel.Tracer(service)
)

func main() {
    middlewares := server.Middleware()

    middlewares.Add(middleware.New().Path().Middleware)
    middlewares.Add(middleware.New().Envoy().Middleware)
    middlewares.Add(middleware.New().Timeout().Configuration(func(options *timeout.Settings) { options.Timeout = 30 * time.Second }).Middleware)
    middlewares.Add(middleware.New().Server().Configuration(func(options *servername.Settings) { options.Server = header }).Middleware)
    middlewares.Add(middleware.New().Service().Configuration(func(options *name.Settings) { options.Service = service }).Middleware)
    middlewares.Add(middleware.New().Version().Configuration(func(options *versioning.Settings) { options.Version.Service = version }).Middleware)
    middlewares.Add(middleware.New().Telemetry().Middleware)

    middlewares.Add(middleware.New().Tracer().Configuration(func(options *tracing.Settings) { options.Tracer = tracer }).Middleware)

    mux := http.NewServeMux()

    mux.Handle("GET /", otelhttp.WithRouteTag("/", metadata.Handler))

    mux.Handle("POST /register", otelhttp.WithRouteTag("/register", registration.Handler))
    mux.Handle("POST /refresh", otelhttp.WithRouteTag("/refresh", refresh.Handler))
    mux.Handle("POST /login", otelhttp.WithRouteTag("/login", login.Handler))

    mux.HandleFunc("GET /health", server.Health)

    // Start the HTTP server
    slog.Info("Starting Server ...", slog.String("local", fmt.Sprintf("http://localhost:%s", *(port))))

    api := server.Server(ctx, mux, middlewares, *port)

    // Issue Cancellation Handler
    server.Interrupt(ctx, cancel, api)

    // Telemetry Setup
    shutdown, e := telemetry.Setup(ctx, service, version, func(options *telemetry.Settings) {
        if version == "development" && os.Getenv("CI") == "" {
            options.Zipkin.Enabled = false

            options.Tracer.Local = true
            options.Metrics.Local = true
            options.Logs.Local = true
        }
    })

    if e != nil {
        panic(e)
    }

    defer func() {
        e = errors.Join(e, shutdown(ctx))
    }()

    // <-- Blocking
    if e := api.ListenAndServe(); e != nil && !(errors.Is(e, http.ErrServerClosed)) {
        slog.ErrorContext(ctx, "Error During Server's Listen & Serve Call ...", slog.String("error", e.Error()))

        os.Exit(100)
    }

    // --> Exit
    {
        slog.InfoContext(ctx, "Graceful Shutdown Complete")

        // Waiter
        <-ctx.Done()
    }
}

func init() {
    flag.Parse()

    level := slog.Level(-8)
    if os.Getenv("CI") == "true" {
        level = slog.LevelDebug
    }

    logging.Level(level)
    slog.SetLogLoggerLevel(level)
    if service == "service" && os.Getenv("CI") != "true" {
        _, file, _, ok := runtime.Caller(0)
        if ok {
            service = filepath.Base(filepath.Dir(file))
        }
    }

    handler := logging.Logger(func(o *logging.Options) { o.Service = service })
    logger = slog.New(handler)
    slog.SetDefault(logger)
}

Contributions

See the Contributing Guide for additional details on getting started.