cmd

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2016 License: MIT Imports: 16 Imported by: 0

README

GitHub release GoDoc Build Status Go Report Card License

Go Command Framework

This is a command framework mainly for our Go products.

Be warned that this is a framework rather than a library. Most features cannot be configured.

Features

  • Logging options.
  • Context-based goroutine management.
  • Signal handlers.
  • Graceful stop for network servers.
  • Enhanced http.Server.

Requirements

Go 1.7 or better.

Specifications

Commands using this framework implement these features:

Command-line options
  • -logfile FILE

    Output logs to FILE instead of standard error.

  • -loglevel LEVEL

    Change logging threshold to LEVEL. Default is info.
    LEVEL is one of critical, error, warning, info, or debug.

  • -logfmt FORMAT

    Change log formatter. Default is plain.
    FORMAT is one of plain, logfmt, or json.

Signal Handlers
  • SIGUSR1

    If -logfile is specified, this signal make the program reopen the log file to cooperate with an external log rotation program.

    On Windows, this is not implemented.

  • SIGINT and SIGTERM

    These signals cancel the context and hence goroutines and gracefully stop TCP and HTTP servers if any, then terminate the program.

    On Windows, only SIGINT is handled.

Usage

Read the design notes and godoc.

License

MIT

Documentation

Overview

Package cmd provides a framework that helps implementation of commands having these features:

Better logging:

By using github.com/cybozu-go/log package, logs can be structured in JSON or logfmt format. HTTP servers log accesses automatically.

Graceful exit:

The framework provides functions to manage goroutines and network server implementation that can be shutdown gracefully.

Signal handlers:

The framework installs SIGINT/SIGTERM signal handlers for graceful exit, and SIGUSR1 signal handler to reopen log files.

Environment

Environment is the heart of the framework. It provides a base context.Context that will be canceled before program stops, and methods to manage goroutines.

To use the framework easily, the framework provides an instance of Environment as the default, and functions to work with it.

Example (Basic)

The most basic usage of the framework.

package main

import (
	"context"
	"errors"
	"flag"

	"github.com/cybozu-go/cmd"
	"github.com/cybozu-go/log"
)

func main() {
	flag.Parse()
	cmd.LogConfig{}.Apply()

	cmd.Go(func(ctx context.Context) error {
		// do something

		return errors.New("...")
	})

	// some more Go
	//cmd.Go(func(ctx context.Context) error {})

	// Stop declares no Go calls will be made from this point.
	cmd.Stop()

	// Wait waits for all goroutines started by Go to complete,
	// or one of such goroutine returns non-nil error.
	err := cmd.Wait()
	if err != nil {
		log.ErrorExit(err)
	}
}
Example (Http)

HTTP server that stops gracefully.

package main

import (
	"flag"
	"net/http"
	"syscall"

	"github.com/cybozu-go/cmd"
	"github.com/cybozu-go/log"
)

func main() {
	flag.Parse() // must precedes LogConfig.Apply
	cmd.LogConfig{}.Apply()

	// log accesses to another file in JSON Lines format.
	w, err := log.NewFileReopener("/path/to/access.log", syscall.SIGUSR1)
	if err != nil {
		log.ErrorExit(err)
	}
	accessLog := log.NewLogger()
	accessLog.SetFormatter(log.JSONFormat{})
	accessLog.SetOutput(w)

	// HTTP server.
	serv := &cmd.HTTPServer{
		Server: &http.Server{
			Handler: http.FileServer(http.Dir("/path/to/directory")),
		},
		AccessLog: accessLog,
	}

	// ListenAndServe is overridden to start a goroutine by cmd.Go.
	err = serv.ListenAndServe()
	if err != nil {
		log.ErrorExit(err)
	}

	// Wait waits for SIGINT or SIGTERM.
	// In this case, cmd.Stop can be omitted.
	err = cmd.Wait()

	// Use IsSignaled to determine err is the result of a signal.
	if err != nil && !cmd.IsSignaled(err) {
		log.ErrorExit(err)
	}
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Cancel

func Cancel(err error) bool

Cancel cancels the base context of the global environment.

Passed err will be returned by Wait(). Once canceled, Go() will not start new goroutines.

Note that calling Cancel(nil) is perfectly valid. Unlike Stop(), Cancel(nil) cancels the base context and can gracefully stop goroutines started by Server.Serve or HTTPServer.ListenAndServe.

This returns true if the caller is the first that calls Cancel. For second and later calls, Cancel does nothing and returns false.

func Context

func Context() context.Context

Context returns the base context of the global environment.

func Go

func Go(f func(ctx context.Context) error)

Go starts a goroutine that executes f in the global environment.

f takes a drived context from the base context. The context will be canceled when f returns.

Goroutines started by this function will be waited for by Wait until all such goroutines return.

If f returns non-nil error, Cancel is called with that error.

f should watch ctx.Done() channel and return quickly when the channel is canceled.

func IsSignaled

func IsSignaled(err error) bool

IsSignaled returns true if err returned by Wait indicates that the program has received SIGINT or SIGTERM.

func Stop

func Stop()

Stop just declares no further Go will be called.

Calling Stop is optional if and only if Cancel is guaranteed to be called at some point. For instance, if the program runs until SIGINT or SIGTERM, Stop is optional.

func Wait

func Wait() error

Wait waits for Stop or Cancel, and for all goroutines started by Go to finish.

The returned err is the one passed to Cancel, or nil. err can be tested by IsSignaled to determine whether the program got SIGINT or SIGTERM.

Types

type AccessLog

type AccessLog struct {
	Topic    string    `json:"topic"`
	LoggedAt time.Time `json:"logged_at"`
	Severity string    `json:"severity"`
	Utsname  string    `json:"utsname"`
	Message  string    `json:"message"`

	Type           string  `json:"type"`             // "access"
	Elapsed        float64 `json:"response_time"`    // floating point number of seconds.
	Protocol       string  `json:"protocol"`         // "HTTP/1.1" or alike
	StatusCode     int     `json:"http_status_code"` // 200, 404, ...
	Method         string  `json:"http_method"`
	RequestURI     string  `json:"url"`
	Host           string  `json:"http_host"`
	RequestLength  int64   `json:"request_size"`
	ResponseLength int64   `json:"response_size"`
	RemoteAddr     string  `json:"remote_ipaddr"`
	UserAgent      string  `json:"http_user_agent"`
	RequestID      string  `json:"request_id"`
}

AccessLog is to decode access log records from HTTPServer.

The struct is tagged for JSON formatted records.

type Environment

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

Environment implements context-based goroutine management.

func NewEnvironment

func NewEnvironment(ctx context.Context) *Environment

NewEnvironment creates a new Environment.

This function installs a signal handler that calls Cancel when SIGINT or SIGTERM is sent.

Example

Sub environment to barrier wait goroutines.

package main

import (
	"context"

	"github.com/cybozu-go/cmd"
	"github.com/cybozu-go/log"
)

func main() {
	// env is a sub environment of the global environment.
	env := cmd.NewEnvironment(cmd.Context())

	env.Go(func(ctx context.Context) error {
		// do something
		return nil
	})
	// some more env.Go()

	// wait all goroutines started by env.Go().
	// These goroutines may also be canceled when the global
	// environment is canceled.
	env.Stop()
	err := env.Wait()
	if err != nil {
		log.ErrorExit(err)
	}

	// another sub environment for another barrier.
	env = cmd.NewEnvironment(cmd.Context())

	// env.Go, env.Stop, and env.Wait again.
}

func (*Environment) Cancel

func (e *Environment) Cancel(err error) bool

Cancel cancels the base context.

Passed err will be returned by Wait(). Once canceled, Go() will not start new goroutines.

Note that calling Cancel(nil) is perfectly valid. Unlike Stop(), Cancel(nil) cancels the base context and can gracefully stop goroutines started by Server.Serve or HTTPServer.ListenAndServe.

This returns true if the caller is the first that calls Cancel. For second and later calls, Cancel does nothing and returns false.

func (*Environment) Context

func (e *Environment) Context() context.Context

Context returns the base context of the environment.

func (*Environment) Go

func (e *Environment) Go(f func(ctx context.Context) error)

Go starts a goroutine that executes f.

f takes a drived context from the base context. The context will be canceled when f returns.

Goroutines started by this function will be waited for by Wait until all such goroutines return.

If f returns non-nil error, Cancel is called immediately with that error.

f should watch ctx.Done() channel and return quickly when the channel is closed.

func (*Environment) Stop

func (e *Environment) Stop()

Stop just declares no further Go will be called.

Calling Stop is optional if and only if Cancel is guaranteed to be called at some point. For instance, if the program runs until SIGINT or SIGTERM, Stop is optional.

func (*Environment) Wait

func (e *Environment) Wait() error

Wait waits for Stop or Cancel, and for all goroutines started by Go to finish.

The returned err is the one passed to Cancel, or nil. err can be tested by IsSignaled to determine whether the program got SIGINT or SIGTERM.

type HTTPServer

type HTTPServer struct {
	*http.Server

	// AccessLog is a logger for access logs.
	// If this is nil, the default logger is used.
	AccessLog *log.Logger

	// ShutdownTimeout is the maximum duration the server waits for
	// all connections to be closed before shutdown.
	//
	// Zero duration disables timeout.
	ShutdownTimeout time.Duration

	// Env is the environment where this server runs.
	//
	// The global environment is used if Env is nil.
	Env *Environment
	// contains filtered or unexported fields
}

HTTPServer is a wrapper for http.Server.

This struct overrides Serve and ListenAndServe* methods.

http.Server members are replaced as following:

  • Handler is replaced with a wrapper handler.
  • ReadTimeout is set to 30 seconds if it is zero.
  • ConnState is replaced with the one provided by the framework.

func (*HTTPServer) ListenAndServe

func (s *HTTPServer) ListenAndServe() error

ListenAndServe overrides http.Server's method.

Unlike the original, this method returns immediately just after starting a goroutine to accept connections. To stop listening, call the environment's Cancel.

ListenAndServe returns non-nil error if and only if net.Listen failed.

func (*HTTPServer) ListenAndServeTLS

func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error

ListenAndServeTLS overrides http.Server's method.

Unlike the original, this method returns immediately just after starting a goroutine to accept connections. To stop listening, call the environment's Cancel.

Another difference from the original is that certFile and keyFile must be specified. If not, configure http.Server.TLSConfig manually and use Serve().

HTTP/2 is always enabled.

ListenAndServeTLS returns non-nil error if net.Listen failed or failed to load certificate files.

func (*HTTPServer) Serve

func (s *HTTPServer) Serve(l net.Listener) error

Serve overrides http.Server's Serve method.

Unlike the original, this method returns immediately just after starting a goroutine to accept connections.

The framework automatically closes l when the environment's Cancel is called.

Serve always returns nil.

func (*HTTPServer) ServeHTTP

func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler interface.

func (*HTTPServer) TimedOut

func (s *HTTPServer) TimedOut() bool

TimedOut returns true if the server shut down before all connections got closed.

type LogConfig

type LogConfig struct {
	Filename string `toml:"filename" json:"filename"`
	Level    string `toml:"level"    json:"level"`
	Format   string `toml:"format"   json:"format"`
}

LogConfig configures cybozu-go/log's default logger.

Filename, if not an empty string, specifies the output filename.

Level is the log threshold level name. Valid levels are "critical", "error", "warning", "info", and "debug". Empty string is treated as "info".

Format specifies log formatter to be used. Available formatters are "plain", "logfmt", and "json". Empty string is treated as "plain".

For details, see https://godoc.org/github.com/cybozu-go/log .

Example

Load logging configurations from TOML file.

package main

import (
	"flag"

	"github.com/BurntSushi/toml"
	"github.com/cybozu-go/cmd"
	"github.com/cybozu-go/log"
)

func main() {
	// compile-time defaults
	config := &cmd.LogConfig{
		Level:  "error",
		Format: "json",
	}

	// load from TOML
	_, err := toml.DecodeFile("/path/to/config.toml", config)
	if err != nil {
		log.ErrorExit(err)
	}

	// Apply gives priority to command-line flags, if any.
	flag.Parse()
	err = config.Apply()
	if err != nil {
		log.ErrorExit(err)
	}
}

func (LogConfig) Apply

func (c LogConfig) Apply() error

Apply applies configurations to the default logger.

Command-line flags take precedence over the struct member values.

type Server

type Server struct {

	// Handler handles a connection.  This must not be nil.
	//
	// ctx is a derived context from the base context that will be
	// canceled when Handler returns.
	Handler func(ctx context.Context, conn net.Conn)

	// TCPKeepAlivePeriod is the duration for TCP keep-alive.
	// If not zero, and the listener given to Serve accepts TCP,
	// TCP keep-alive is turned on with the given period.
	TCPKeepAlivePeriod time.Duration

	// ShutdownTimeout is the maximum duration the server waits for
	// all connections to be closed before shutdown.
	//
	// Zero duration disables timeout.
	ShutdownTimeout time.Duration

	// Env is the environment where this server runs.
	//
	// The global environment is used if Env is nil.
	Env *Environment
	// contains filtered or unexported fields
}

Server is a generic network server that accepts connections and invokes Handler in a goroutine for each connection.

In addition, Serve method gracefully waits all its goroutines to complete before returning.

func (*Server) Serve

func (s *Server) Serve(l net.Listener)

Serve starts a managed goroutine to accept connections.

Serve itself returns immediately. The goroutine continues to accept and handle connections until the base context is canceled.

If the listener is *net.TCPListener, TCP keep-alive is automatically enabled.

The listener l will be closed automatically when the environment's Cancel is called.

func (*Server) TimedOut

func (s *Server) TimedOut() bool

TimedOut returns true if the server shut down before all connections got closed.

type StdResponseWriter

type StdResponseWriter interface {
	http.ResponseWriter
	io.ReaderFrom
	http.Flusher
	http.CloseNotifier
	http.Hijacker
	WriteString(data string) (int, error)
}

StdResponseWriter is the interface implemented by the ResponseWriter from http.Server.

HTTPServer's ResponseWriter implements this as well.

Jump to

Keyboard shortcuts

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