app

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 18, 2024 License: BSD-3-Clause Imports: 25 Imported by: 2

README

go-app

PkgGoDev Go Report Card License

A very opinionated app with life cycle and graceful shutdown.

Quickstart

Here is an example of a program that does nothing but it compiles and runs with all app features:

package main

import (
	"context"

	"github.com/arquivei/go-app"
)

var (
	version = "v0.0.0-dev"
	cfg     struct {
		app.Config
	}
)

func main() {
	defer app.Recover()
	app.Bootstrap(version, &cfg)
	app.RunAndWait(func(ctx context.Context) error {
		<-ctx.Done()
		return ctx.Err()
	})
}

Check all available options with go run ./ -h

Supported Fields:
FIELD                             FLAG                               ENV                               DEFAULT
-----                             -----                              -----                             -------
App.Log.Level                     -app-log-level                     APP_LOG_LEVEL                     info
App.Log.Human                     -app-log-human                     APP_LOG_HUMAN
App.AdminServer.Enabled           -app-adminserver-enabled           APP_ADMINSERVER_ENABLED           true
App.AdminServer.Addr              -app-adminserver-addr              APP_ADMINSERVER_ADDR              localhost:9000
App.AdminServer.With.DebugURLs    -app-adminserver-with-debugurls    APP_ADMINSERVER_WITH_DEBUGURLS    true
App.AdminServer.With.Metrics      -app-adminserver-with-metrics      APP_ADMINSERVER_WITH_METRICS      true
App.AdminServer.With.Probes       -app-adminserver-with-probes       APP_ADMINSERVER_WITH_PROBES       true
App.Shutdown.GracePeriod          -app-shutdown-graceperiod          APP_SHUTDOWN_GRACEPERIOD          3s
App.Shutdown.Timeout              -app-shutdown-timeout              APP_SHUTDOWN_TIMEOUT              5s

There is a special option to print out the default configuration in env or yaml format: go run . -app-config-output=env.

APP_LOG_LEVEL=info
APP_LOG_HUMAN=
APP_ADMINSERVER_ENABLED=true
APP_ADMINSERVER_ADDR=localhost:9000
APP_ADMINSERVER_WITH_DEBUGURLS=true
APP_ADMINSERVER_WITH_METRICS=true
APP_ADMINSERVER_WITH_PROBES=true
APP_SHUTDOWN_GRACEPERIOD=3s
APP_SHUTDOWN_TIMEOUT=5s
APP_CONFIG_OUTPUT=

Version can be overwritten in compile time using -ldflags:

-ldflags="-X main.version=v0.0.1"

More information on the presentation slides.

Use the present tool to render the slides or you can check it online at https://go-talks.appspot.com/github.com/arquivei/go-app/docs/presentation.slide

Minimal dependencies

  • prometheus/client_golang: Metrics
  • rs/zerolog: Structured log in JSON format
  • stretchr/testify: Better unit testing asserts

Getting Started

These instructions will give you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on deploying the project on a live system.

Prerequisites

Requirements for the software and other tools to build, test and push:

Linting

Please run golangci-lint run before submitting code.

Godoc

To read the godoc documentation run:

godoc -http=localhost:6060

and open http://localhost:6060 on your browser.

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use Semantic Versioning for versioning. For the versions available, see the tags on this repository.

License

This project is licensed under the BSD 3-Clause - see the LICENSE.txt file for details.

Documentation

Overview

Package app provides an application framework that manages the live cycle of a running application.

An app could be an HTTP server (or any kind of server), a worker or a simple program. Independently of the kind of the app, it always exposes an admin port at 9000 by default which serves metrics, debug information and kubernetes probes.

An app is also capable of graceful shutdown on receiving signals of terminating by itself.

The recommended way to use the `app` package is to rely on the 'default app'. The 'default app' is a global app that can be accessed by public functions on the app package (the app package can be seen as the application)

There is a running example on `app/examples/servefiles`

Basics

The first thing you should do bootstrap the application. This will initialize the configuration, reading it from the commandline or environment, initialize zerolog (based on the Config) and initialize the default global app.

app.Bootstrap(version, &cfg)

After the bootstrap, the app will already be exposing the admin port and the readiness probe will be returning error, indicating that the application is not yet ready to receive requests. But the healthiness probe will be returning success, indicating the app is alive.

Then you should start initializing all the program dependencies. Because the application is not yet ready, kubernetes will refrain from sending requests (that would fail at this point). Also we already have some metrics and the debug handlers.

During this phase, you will probably want to register your shutdown handlers.

app.RegisterShutdownHandler(
	&app.ShutdownHandler{
		Name:     "http_server",
		Priority: app.ShutdownPriority(100),
		Handler:  httpServer.Shutdown,
		Policy:   app.ErrorPolicyAbort,
	},
)

They are executed in order by priority. The Highest priority first (in case of the same priority, don't assume any order).

Finally you can run the application by calling RunAndWait:

app.RunAndWait(func(ctx context.Context) error {...})

At this point the application will run until the given function returns or it receives an termination signal.

Updating From Previous Version

This package is a major overhaul over `arquivei/foundationkit/app`. One of the main breaking changes is how the Config struct is used. Now, all app related configuration is inside an App field and new configuration were added. Now the Config struct is expected to be embedded in your application's configuration:

type config struct {
	// App is the app specific configuration
	app.Config

	// Programs can have any configuration the want.
	HTTP struct {
		Port string `default:"8000"`
	}
	Dir string `default:"."`
}

All the initialization now occurs on the Bootstrap functions and you need to initialize logs manually anymore.

RunAndWait was also changed in a couple of ways. Now it does not return anything and will panic if called incorrectly. The error returned by the MainLoopFunc is handled my logging it and triggering a graceful shutdown. The MainLoopFunc was changed to receve a context. The context will be canceled when Shutdown is called.

Using Probes

A Probe is a boolean that indicates if something is OK or not. There are two groups of probes in an app: The Healthiness an Readiness groups. Kubernetes checks on there two probes to decide what to do to the pod, like, from stop sending requests to just kill the pod, sending a signal the app will capture and start a graceful shutdown.

If a single probe of a group is not OK, than the whole group is not OK. In this event, the HTTP handler returns the name of all the probes that are not OK for the given group.

mux.HandleFunc("/healthy", func(w http.ResponseWriter, _ *http.Request) {
	isHealthy, cause := app.Healthy.CheckProbes()
	if isHealthy {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	} else {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(cause))
	}
})
mux.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) {
	isReady, cause := app.Ready.CheckProbes()
	if isReady {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	} else {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(cause))
	}
})

If the application is unhealthy kubernetes will send a signal that will trigger the graceful shutdown. All registered shutdown handlers will be executed ordered by priority (highest first) and the pod will be restarted. Only set an application as unhealthy if it reached an unworkable state and should be restarted. We have an example of this on `gokitmiddlewares/stalemiddleware/`. This is a middleware that was developed to be used in workers. It checks if the endpoint is being called (messages are being fetched and processed) and if not, it assumes there could be a problem with the queue and sets the application to unready, causing the application to restart. This mitigated a problem we had with kafka when a change of brokers made the worker stop receiving messages forever.

If the application is unready kubernetes will stop sending requests, but if the application becomes ready again, it will start receiving requests. This is used during initialization to signalize to kubernetes when the application is ready and can receive requests. If we can identify that the the application is degraded we can use this probe to temporary remove the application from the kubernetes service until it recovers.

A probe only exists as part of a group so the group provides a proper constructor for a probe. Probe's name must also be unique for the group but can be reused on different groups.

readinessProbe, err := app.Ready.NewProbe("fkit/app", false)
healthnessProbe, err := app.Healthy.NewProbe("fkit/app", true)

The probe is automatically added to the group and any change is automatically reflected on the group it belongs to and the HTTP probe endpoints.

The state of a probe can be altered at any time using SetOk and SetNotOk:

readinessProbe.SetOk()
readinessProbe.SetNotOk()

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bootstrap

func Bootstrap(appVersion string, config AppConfig)

Bootstrap initializes the config structure, the log and creates a new app internally.

func ErrorPolicyString

func ErrorPolicyString(p ErrorPolicy) string

ErrorPolicyString returns a string representation of a ErrorPolicy. This was intended for logging purposes.

func Recover

func Recover()

func RegisterShutdownHandler

func RegisterShutdownHandler(sh *ShutdownHandler)

RegisterShutdownHandler adds a shutdown handler to the app. Shutdown Handlers are executed one at a time from the highest priority to the lowest priority. Shutdown handlers of the same priority are normally executed in the added order but this is not guaranteed.

func RunAndWait

func RunAndWait(f MainLoopFunc)

RunAndWait executes the main loop on a go-routine and listens to SIGINT and SIGKILL to start the shutdown. This is expected to be called only once and will panic if called a second time.

func SetupConfig

func SetupConfig(config any)

setupConfig loads the configuration in the given struct. In case of error, prints help and exit application.

func Shutdown

func Shutdown(ctx context.Context) error

Shutdown calls all shutdown methods ordered by priority. Handlers are processed from higher priority to lower priority.

Types

type App

type App struct {
	Ready   ProbeGroup
	Healthy ProbeGroup
	// contains filtered or unexported fields
}

App represents an application with a main loop and a shutdown routine

func New

func New(c Config) *App

New returns a new App using the app.Config struct for configuration.

func (*App) RegisterShutdownHandler

func (app *App) RegisterShutdownHandler(sh *ShutdownHandler)

RegisterShutdownHandler adds a shutdown handler to the app. Shutdown Handlers are executed one at a time from the highest priority to the lowest priority. Shutdown handlers of the same priority are normally executed in the added order but this is not guaranteed.

func (*App) RunAndWait

func (app *App) RunAndWait(mainLoop MainLoopFunc)

RunAndWait executes the main loop on a go-routine and listens to SIGINT and SIGKILL to start the shutdown. This is expected to be called only once and will panic if called a second time.

func (*App) Shutdown

func (app *App) Shutdown(ctx context.Context) error

Shutdown calls all shutdown methods ordered by priority. Handlers are processed from higher priority to lower priority.

type AppConfig

type AppConfig interface {
	GetAppConfig() Config
}

type Config

type Config struct {
	App struct {
		Log         logger.Config
		AdminServer struct {
			// Enabled sets the admin server
			Enabled bool `default:"true"`
			// DefaultAdminPort is the default port the app will bind the admin HTTP interface.
			Addr string `default:"localhost:9000"`
			With struct {
				// DebugURLs sets the debug URLs in the admin server. To disable them, set to false.
				DebugURLs bool `default:"true"`
				// Metrics
				Metrics bool `default:"true"`
				// Probes
				Probes bool `default:"true"`
			}
		}
		Shutdown struct {
			// DefaultGracePeriod is the default value for the grace period.
			// During normal shutdown procedures, the shutdown function will wait
			// this amount of time before actually starting calling the shutdown handlers.
			GracePeriod time.Duration `default:"3s"`
			// DefaultShutdownTimeout is the default value for the timeout during shutdown.
			Timeout time.Duration `default:"5s"`
		}
		Config struct {
			Output string
		}
	}
}

func (Config) GetAppConfig

func (c Config) GetAppConfig() Config

type ErrorPolicy

type ErrorPolicy int

ErrorPolicy specifies what should be done when a handler fails

const (
	// ErrorPolicyWarn prints the error as a warning and continues to the next handler. This is the default.
	ErrorPolicyWarn ErrorPolicy = iota
	// ErrorPolicyAbort stops the shutdown process and returns an error
	ErrorPolicyAbort
	// ErrorPolicyFatal logs the error as Fatal, it means the application will close immediately
	ErrorPolicyFatal
	// ErrorPolicyPanic panics if there is an error
	ErrorPolicyPanic
)

type MainLoopFunc

type MainLoopFunc func(context.Context) error

MainLoopFunc is the function runned by app. If it finishes, it will trigger a shutdown. The context will be canceled when application is gracefully shutting down.

type Probe

type Probe struct {
	// contains filtered or unexported fields
}

Probe stores the state of a probe (`true` or `false` for ok and not ok respectively).

func (*Probe) IsOk

func (p *Probe) IsOk() bool

IsOk returns the state of the probe (`true` or `false` for ok and not ok respectively).

func (*Probe) Set

func (p *Probe) Set(ok bool)

Set changes the state of a probe. Use `true` for ok and `false` for not ok.

func (*Probe) SetNotOk

func (p *Probe) SetNotOk()

SetNotOk sets the probe as not ok. Same as `Set(false)`.

func (*Probe) SetOk

func (p *Probe) SetOk()

SetOk sets the probe as ok. Same as `Set(true)`.

type ProbeGroup

type ProbeGroup struct {
	// contains filtered or unexported fields
}

ProbeGroup aggregates and manages probes. Probes inside a group must have a unique name.

func HealthinessProbeGroup

func HealthinessProbeGroup() *ProbeGroup

HealthinessProbeGroup is a colection of healthiness probes.

func NewProbeGroup

func NewProbeGroup(name string) ProbeGroup

NewProbeGroup returns a new ProbeGroup.

func ReadinessProbeGoup

func ReadinessProbeGoup() *ProbeGroup

ReadinessProbeGoup is a collection of readiness probes.

func (*ProbeGroup) CheckProbes

func (g *ProbeGroup) CheckProbes() (bool, string)

CheckProbes range through the probes and returns the state of the group. If any probe is not ok (false) the group state is not ok (false). If the group is not ok, it's also returned the cause in the second return parameter. If all probes are ok (true) the cause is returned as OK. If more than one probe is not ok, the causes are concatenated by a comma.

func (*ProbeGroup) MustNewProbe

func (g *ProbeGroup) MustNewProbe(name string, ok bool) Probe

MustNewProbe returns a new Probe with the given name and panics in case of error.

func (*ProbeGroup) NewProbe

func (g *ProbeGroup) NewProbe(name string, ok bool) (Probe, error)

NewProbe returns a new Probe with the given name. The name must satisfy the following regular expression: a-zA-Z0-9_/-{3,}

func (*ProbeGroup) ServeHTTP

func (g *ProbeGroup) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP serves the Probe Group as an HTTP handler.

type ShutdownFunc

type ShutdownFunc func(context.Context) error

ShutdownFunc is a shutdown function that will be executed when the app is shutting down.

type ShutdownHandler

type ShutdownHandler struct {
	Name     string
	Handler  ShutdownFunc
	Policy   ErrorPolicy
	Priority ShutdownPriority
	Timeout  time.Duration
	// contains filtered or unexported fields
}

ShutdownHandler is a shutdown structure that allows configuring and storing shutdown information of an orchestrated shutdown flow.

func (*ShutdownHandler) Execute

func (sh *ShutdownHandler) Execute(ctx context.Context) error

Execute runs the shutdown functions and handles timeout and error policy

type ShutdownPriority

type ShutdownPriority uint8

ShutdownPriority is used to guide the execution of the shutdown handlers during a graceful shutdown. The shutdown is performed from the higher to the lowest priority

Directories

Path Synopsis
examples
panic
This example shows how panics are captured and gracefuly printed.
This example shows how panics are captured and gracefuly printed.
servefiles
This is an example program on how to use the app package.
This is an example program on how to use the app package.
shutdown-handlers
This example ilustrates how shutdowns are handled.
This example ilustrates how shutdowns are handled.
internal
thirdparty/uconfig
Package uconfig provides advanced command line flags supporting defaults, env vars, and config structs.
Package uconfig provides advanced command line flags supporting defaults, env vars, and config structs.
thirdparty/uconfig/flat
Package flat provides a flat view of an arbitrary nested structs.
Package flat provides a flat view of an arbitrary nested structs.
thirdparty/uconfig/internal/f
Package f provides simple test fixtures for uconfig.
Package f provides simple test fixtures for uconfig.
thirdparty/uconfig/plugins
Package plugins describes the uconfig provider interface.
Package plugins describes the uconfig provider interface.
thirdparty/uconfig/plugins/defaults
Package defaults provides flags support for uconfig
Package defaults provides flags support for uconfig
thirdparty/uconfig/plugins/env
Package env provides environment variables support for uconfig
Package env provides environment variables support for uconfig
thirdparty/uconfig/plugins/file
Package file provides config file support for uconfig
Package file provides config file support for uconfig
thirdparty/uconfig/plugins/flag
Package flag provides flags support for uconfig
Package flag provides flags support for uconfig
thirdparty/uconfig/plugins/secret
Package secret enable uconfig to integrate with secret plugins.
Package secret enable uconfig to integrate with secret plugins.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL