Documentation ¶
Overview ¶
Package app provides some tools useful for typical Go project.
Error ¶
Custom Error type and set of standard errors:
- ErrUnknown
- ErrTimeout
- ErrUnavailable
- ErrSystem
- ErrNotExists
- ErrValidation
- ErrInvalidArgument
- ErrIncorrectOperation
- ErrInsufficientPrivileges
Use Err function to create Error instance with custom code, message and optional reason.
Note, that Error instances matched by code:
errors.Is(app.Err("CODE", "msg1"), app.Err("CODE", "msg2")) // true errors.Is(app.Err(app.ErrCodeValidation, "msg1"), app.ErrValidation) // true errors.Is(app.Err(app.ErrCodeValidation, "msg1"), app.ErrUnavailable) // false
Logger ¶
Logger - use NewLogger to set up.
You can add some key-value pairs to operation context using WithLogAttrs and next, when you call any of Logger.DebugContext/Logger.InfoContext/Logger.WarnContext/Logger.ErrorContext methods, logger will add those key-value pairs into structured log record.
Logger provides methods to make error logging easier: Logger.Err and Logger.ErrContext.
Logger allows to change it level dynamically using Logger.SetLevel.
Index ¶
- Constants
- Variables
- func RequestID(ctx context.Context) string
- func WithAddSource(addSource bool) func(logger *Logger)
- func WithLogAttrs(ctx context.Context, attrs Attrs) context.Context
- func WithLogLevel(level slog.Level) func(*Logger)
- func WithRequestID(ctx context.Context, reqID string) context.Context
- type Attrs
- type Error
- type LogHandlerMiddleware
- func (l LogHandlerMiddleware) Enabled(ctx context.Context, level slog.Level) bool
- func (l LogHandlerMiddleware) Handle(ctx context.Context, record slog.Record) error
- func (l LogHandlerMiddleware) WithAttrs(attrs []slog.Attr) slog.Handler
- func (l LogHandlerMiddleware) WithGroup(name string) slog.Handler
- type Logger
- func (l *Logger) Err(err error, msg string, args ...any)
- func (l *Logger) ErrContext(ctx context.Context, err error, msg string, args ...any)
- func (l *Logger) LogLogger() *log.Logger
- func (l *Logger) SetLevel(level slog.Level)
- func (l *Logger) With(args ...any) *Logger
- func (l *Logger) WithComponent(component string) *Logger
- func (l *Logger) WithGroup(name string) *Logger
- type LoggerOption
- type NoopLogger
Examples ¶
Constants ¶
const ( ErrCodeTimeout = "ERR_TIMEOUT" ErrCodeSystem = "ERR_SYSTEM" )
The following errors represents temporal problems on the server side, we expect it will be resolved soon... so caller can repeat request a bit later and get success response
- ErrCodeTimeout is given when the app cannot perform action (or get response from external system) in time
- ErrCodeUnavailable means the app is currently unable to perform action (or external request), but we expect that it will be resolved soon
- ErrCodeSystem - unclassified error on server side
const ( ErrCodeNotExists = "ERR_NOT_FOUND" ErrCodeValidation = "ERR_VALIDATION" ErrCodeInvalidArgument = "ERR_INVALID_ARGUMENT" ErrCodeIncorrectOperation = "ERR_INCORRECT_OPERATION" ErrCodeInsufficientPrivileges = "ERR_INSUFFICIENT_PRIVILEGES" )
The following errors represents problems on client side, so caller must change request to get success response
- ErrCodeNotExists
- ErrCodeValidation
- ErrCodeInvalidArgument
- ErrCodeIncorrectOperation
- ErrCodeInsufficientPrivileges
const ( CtxKeyLogAttrs = ctxKey(iota + 1) CtxKeyXRequestId )
const ( LogKeyRequestId = "request_id" LogKeyComponent = "component" LogKeyError = "error" LogKeyErrorReason = "reason" )
const ( // ErrCodeUnknown is the worst case: app has not any suggestion, how to handle it. // Caller should decide itself, repeat request ot not ErrCodeUnknown = "ERR_UNKNOWN" )
Variables ¶
var ( ErrUnknown = Error{/* contains filtered or unexported fields */} ErrTimeout = Error{/* contains filtered or unexported fields */} ErrSystem = Error{/* contains filtered or unexported fields */} ErrNotExists = Error{/* contains filtered or unexported fields */} ErrValidation = Error{/* contains filtered or unexported fields */} ErrInvalidArgument = Error{/* contains filtered or unexported fields */} ErrIncorrectOperation = Error{/* contains filtered or unexported fields */} ErrInsufficientPrivileges = Error{/* contains filtered or unexported fields */} )
Sentinel errors to compare using errors.Is/As
Functions ¶
func WithAddSource ¶
WithAddSource enables/disables logging of source code position
func WithLogAttrs ¶
Example ¶
package main import ( "context" "fmt" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { ctxFirst := app.WithLogAttrs(context.Background(), app.Attrs{"k0": 0}) ctx := ctxFirst ctx = app.WithLogAttrs(ctx, app.Attrs{"k1": 1}) ctx = app.WithLogAttrs(ctx, app.Attrs{"k2": 2}) ctx = app.WithLogAttrs(ctx, app.Attrs{"k3": 3}) ctxFinal := ctx fmt.Printf("first ctx: %#v\n", app.LogAttrs(ctxFirst)) fmt.Printf("last ctx: %#v\n", app.LogAttrs(ctxFinal)) }
Output: first ctx: app.Attrs{"k0":0} last ctx: app.Attrs{"k0":0, "k1":1, "k2":2, "k3":3}
func WithLogLevel ¶
WithLogLevel sets the log level
Types ¶
type Attrs ¶
type Error ¶
type Error struct {
// contains filtered or unexported fields
}
Error is an error with additional data
func Err ¶
Err creates new instance of Error.
Params:
- code - 'business' error code, see ErrCodeUnknown or other ErrCode* constants
- msg - any error description
- reason - wraps 'root cause' errors
Example ¶
package main import ( "errors" "fmt" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { reason1 := errors.New("foo") reason2 := errors.New("bar") appErr := app.Err(app.ErrCodeValidation, "baz", reason1, reason2) fmt.Printf("appErr code: '%s'\n", appErr.Code()) fmt.Printf("appErr is reason 1: %v\n", errors.Is(appErr, reason1)) fmt.Printf("appErr is reason 2: %v\n", errors.Is(appErr, reason2)) }
Output: appErr code: 'ERR_VALIDATION' appErr is reason 1: true appErr is reason 2: true
func (Error) Code ¶
Code returns error-code, see ErrCode* constants
Example ¶
package main import ( "fmt" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { baseErr := app.Error{} appErr := app.Err(app.ErrCodeValidation, "foo") fmt.Printf("baseErr code: '%s'\n", baseErr.Code()) fmt.Printf("appErr code: '%s'\n", appErr.Code()) }
Output: baseErr code: '' appErr code: 'ERR_VALIDATION'
func (Error) Message ¶
Message returns Error's message
Example ¶
package main import ( "fmt" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { baseErr := app.Error{} appErr := app.Err(app.ErrCodeValidation, "bar") fmt.Printf("baseErr message: '%s'\n", baseErr.Message()) fmt.Printf("appErr message: '%s'\n", appErr.Message()) }
Output: baseErr message: '' appErr message: 'bar'
type LogHandlerMiddleware ¶
type LogHandlerMiddleware struct {
// contains filtered or unexported fields
}
LogHandlerMiddleware is a slog.Handler wrapper that enriches log record with context data
func NewLogHandlerMiddleware ¶
func NewLogHandlerMiddleware(next slog.Handler) *LogHandlerMiddleware
type Logger ¶
Logger is a slog.Logger wrapper with some shortcut methods
func NewLogger ¶
func NewLogger(w io.Writer, options ...LoggerOption) *Logger
NewLogger returns ready to use instance of Logger.
If w is nil it creates 'noop' logger useful for tests
There are options:
- WithLogLevel(slog.LogLevel) - sets level, slog.LevelError is default
- WithAddSource - enables/disables logging of source code position
Example (Noop) ¶
package main import ( "context" "log/slog" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { ctx := context.Background() log := app.NewLogger(nil, app.WithLogLevel(slog.LevelDebug)) log.DebugContext(ctx, "hello", "key", "value") log.InfoContext(ctx, "hello", "key", "value") log.WarnContext(ctx, "hello", "key", "value") log.ErrorContext(ctx, "hello", "key", "value") }
Output:
Example (Stdout) ¶
package main import ( "context" "log/slog" "os" "regexp" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { ctx := context.Background() ctx = app.WithRequestID(ctx, "ZZZ") ctx = app.WithLogAttrs(ctx, app.Attrs{"XXX": "YYY"}) log := app.NewLogger(stdout{}, app.WithLogLevel(slog.LevelDebug)) log.DebugContext(ctx, "hello", "key", "value") log.InfoContext(ctx, "hello", "key", "value") log.WarnContext(ctx, "hello", "key", "value") log.ErrorContext(ctx, "hello", "key", "value") } var hideTimeRe = regexp.MustCompile(`"time":"[TZ0-9:+.-]+"`) type stdout struct { } func (s stdout) Write(b []byte) (int, error) { b = hideTimeRe.ReplaceAll(b, []byte(`"time":"2006-01-02T15:05:06.000000000+07:00"`)) return os.Stdout.Write(b) }
Output: {"time":"2006-01-02T15:05:06.000000000+07:00","level":"DEBUG","msg":"hello","key":"value","request_id":"ZZZ","XXX":"YYY"} {"time":"2006-01-02T15:05:06.000000000+07:00","level":"INFO","msg":"hello","key":"value","request_id":"ZZZ","XXX":"YYY"} {"time":"2006-01-02T15:05:06.000000000+07:00","level":"WARN","msg":"hello","key":"value","request_id":"ZZZ","XXX":"YYY"} {"time":"2006-01-02T15:05:06.000000000+07:00","level":"ERROR","msg":"hello","key":"value","request_id":"ZZZ","XXX":"YYY"}
func (*Logger) Err ¶
Err writes ERROR message
Example ¶
package main import ( "errors" "log/slog" "os" "regexp" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { log := app.NewLogger(stdout{}, app.WithLogLevel(slog.LevelDebug)) reason := errors.New("foo") err := app.Err(app.ErrCodeValidation, "baz", reason) log.Err(err, "hello", "key", "value") } var hideTimeRe = regexp.MustCompile(`"time":"[TZ0-9:+.-]+"`) type stdout struct { } func (s stdout) Write(b []byte) (int, error) { b = hideTimeRe.ReplaceAll(b, []byte(`"time":"2006-01-02T15:05:06.000000000+07:00"`)) return os.Stdout.Write(b) }
Output: {"time":"2006-01-02T15:05:06.000000000+07:00","level":"ERROR","msg":"hello","key":"value","error":"ERR_VALIDATION: baz","reason":"foo"}
func (*Logger) ErrContext ¶
ErrContext writes ERROR message with context
Example ¶
package main import ( "context" "errors" "log/slog" "os" "regexp" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { log := app.NewLogger(stdout{}, app.WithLogLevel(slog.LevelDebug)) ctx := app.WithRequestID(context.Background(), "ZZZ") reason := errors.New("foo") err := app.Err(app.ErrCodeValidation, "baz", reason) log.ErrContext(ctx, err, "hello", "key", "value") } var hideTimeRe = regexp.MustCompile(`"time":"[TZ0-9:+.-]+"`) type stdout struct { } func (s stdout) Write(b []byte) (int, error) { b = hideTimeRe.ReplaceAll(b, []byte(`"time":"2006-01-02T15:05:06.000000000+07:00"`)) return os.Stdout.Write(b) }
Output: {"time":"2006-01-02T15:05:06.000000000+07:00","level":"ERROR","msg":"hello","key":"value","error":"ERR_VALIDATION: baz","reason":"foo","request_id":"ZZZ"}
func (*Logger) LogLogger ¶
LogLogger returns std log.Logger that acts as bridge to structured handler.
For compatibility with libs that uses the older log API (http.Server.ErrorLog for example)
Example ¶
package main import ( "log/slog" "os" "regexp" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { log := app.NewLogger(stdout{}, app.WithLogLevel(slog.LevelInfo)) logLogger := log.LogLogger() logLogger.Printf("some key: %s", "val") } var hideTimeRe = regexp.MustCompile(`"time":"[TZ0-9:+.-]+"`) type stdout struct { } func (s stdout) Write(b []byte) (int, error) { b = hideTimeRe.ReplaceAll(b, []byte(`"time":"2006-01-02T15:05:06.000000000+07:00"`)) return os.Stdout.Write(b) }
Output: {"time":"2006-01-02T15:05:06.000000000+07:00","level":"INFO","msg":"some key: val"}
func (*Logger) SetLevel ¶
SetLevel updates current logger level
Example ¶
package main import ( "context" "log/slog" "os" "regexp" "github.com/ncotds/nco-qoordinator/pkg/app" ) func main() { ctx := context.Background() log := app.NewLogger(stdout{}, app.WithLogLevel(slog.LevelInfo)) // debug record will be skipped log.DebugContext(ctx, "hello first", "key", "value") log.SetLevel(slog.LevelDebug) // debug record will now be printed log.DebugContext(ctx, "hello second", "key", "value") } var hideTimeRe = regexp.MustCompile(`"time":"[TZ0-9:+.-]+"`) type stdout struct { } func (s stdout) Write(b []byte) (int, error) { b = hideTimeRe.ReplaceAll(b, []byte(`"time":"2006-01-02T15:05:06.000000000+07:00"`)) return os.Stdout.Write(b) }
Output: {"time":"2006-01-02T15:05:06.000000000+07:00","level":"DEBUG","msg":"hello second","key":"value"}
func (*Logger) With ¶
With returns a Logger that includes the given attributes in each output operation. Arguments are converted to attributes as if by Logger.Log
func (*Logger) WithComponent ¶
WithComponent returns a Logger that appends 'component' attribute. If component is empty, WithGroup returns the receiver.
func (*Logger) WithGroup ¶
WithGroup returns a Logger that starts a group, if name is non-empty. The keys of all attributes added to the Logger will be qualified by the given name. (How that qualification happens depends on the Handler.WithGroup method of the Logger's Handler.) If name is empty, WithGroup returns the receiver.
type LoggerOption ¶
type LoggerOption func(*Logger)
type NoopLogger ¶
type NoopLogger struct { }
NoopLogger is a slog.Handler implementation that do nothing, like >/dev/null
Useful for tests
Directories ¶
Path | Synopsis |
---|---|
middleware
Package middleware contains common used HTTP-server middlewares:
|
Package middleware contains common used HTTP-server middlewares: |