538 lines
11 KiB
Go
538 lines
11 KiB
Go
package log
|
|
|
|
import (
|
|
"bytes"
|
|
e "errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"text/template"
|
|
|
|
"github.com/go-errors/errors"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// TODO(dustin): Finish symbol documentation
|
|
|
|
// Config severity integers.
|
|
const (
|
|
LevelDebug = iota
|
|
LevelInfo = iota
|
|
LevelWarning = iota
|
|
LevelError = iota
|
|
)
|
|
|
|
// Config severity names.
|
|
const (
|
|
LevelNameDebug = "debug"
|
|
LevelNameInfo = "info"
|
|
LevelNameWarning = "warning"
|
|
LevelNameError = "error"
|
|
)
|
|
|
|
// Seveirty name->integer map.
|
|
var (
|
|
LevelNameMap = map[string]int{
|
|
LevelNameDebug: LevelDebug,
|
|
LevelNameInfo: LevelInfo,
|
|
LevelNameWarning: LevelWarning,
|
|
LevelNameError: LevelError,
|
|
}
|
|
|
|
LevelNameMapR = map[int]string{
|
|
LevelDebug: LevelNameDebug,
|
|
LevelInfo: LevelNameInfo,
|
|
LevelWarning: LevelNameWarning,
|
|
LevelError: LevelNameError,
|
|
}
|
|
)
|
|
|
|
// Errors
|
|
var (
|
|
ErrAdapterAlreadyRegistered = e.New("adapter already registered")
|
|
ErrFormatEmpty = e.New("format is empty")
|
|
ErrExcludeLevelNameInvalid = e.New("exclude bypass-level is invalid")
|
|
ErrNoAdapterConfigured = e.New("no default adapter configured")
|
|
ErrAdapterIsNil = e.New("adapter is nil")
|
|
ErrConfigurationNotLoaded = e.New("can not configure because configuration is not loaded")
|
|
)
|
|
|
|
// Other
|
|
var (
|
|
includeFilters = make(map[string]bool)
|
|
useIncludeFilters = false
|
|
excludeFilters = make(map[string]bool)
|
|
useExcludeFilters = false
|
|
|
|
adapters = make(map[string]LogAdapter)
|
|
|
|
// TODO(dustin): !! Finish implementing this.
|
|
excludeBypassLevel = -1
|
|
)
|
|
|
|
// Add global include filter.
|
|
func AddIncludeFilter(noun string) {
|
|
includeFilters[noun] = true
|
|
useIncludeFilters = true
|
|
}
|
|
|
|
// Remove global include filter.
|
|
func RemoveIncludeFilter(noun string) {
|
|
delete(includeFilters, noun)
|
|
if len(includeFilters) == 0 {
|
|
useIncludeFilters = false
|
|
}
|
|
}
|
|
|
|
// Add global exclude filter.
|
|
func AddExcludeFilter(noun string) {
|
|
excludeFilters[noun] = true
|
|
useExcludeFilters = true
|
|
}
|
|
|
|
// Remove global exclude filter.
|
|
func RemoveExcludeFilter(noun string) {
|
|
delete(excludeFilters, noun)
|
|
if len(excludeFilters) == 0 {
|
|
useExcludeFilters = false
|
|
}
|
|
}
|
|
|
|
func AddAdapter(name string, la LogAdapter) {
|
|
if _, found := adapters[name]; found == true {
|
|
Panic(ErrAdapterAlreadyRegistered)
|
|
}
|
|
|
|
if la == nil {
|
|
Panic(ErrAdapterIsNil)
|
|
}
|
|
|
|
adapters[name] = la
|
|
|
|
if GetDefaultAdapterName() == "" {
|
|
SetDefaultAdapterName(name)
|
|
}
|
|
}
|
|
|
|
func ClearAdapters() {
|
|
adapters = make(map[string]LogAdapter)
|
|
SetDefaultAdapterName("")
|
|
}
|
|
|
|
type LogAdapter interface {
|
|
Debugf(lc *LogContext, message *string) error
|
|
Infof(lc *LogContext, message *string) error
|
|
Warningf(lc *LogContext, message *string) error
|
|
Errorf(lc *LogContext, message *string) error
|
|
}
|
|
|
|
// TODO(dustin): !! Also populate whether we've bypassed an exception so that
|
|
// we can add a template macro to prefix an exclamation of
|
|
// some sort.
|
|
type MessageContext struct {
|
|
Level *string
|
|
Noun *string
|
|
Message *string
|
|
ExcludeBypass bool
|
|
}
|
|
|
|
type LogContext struct {
|
|
Logger *Logger
|
|
Ctx context.Context
|
|
}
|
|
|
|
type Logger struct {
|
|
isConfigured bool
|
|
an string
|
|
la LogAdapter
|
|
t *template.Template
|
|
systemLevel int
|
|
noun string
|
|
}
|
|
|
|
func NewLoggerWithAdapterName(noun string, adapterName string) (l *Logger) {
|
|
l = &Logger{
|
|
noun: noun,
|
|
an: adapterName,
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
func NewLogger(noun string) (l *Logger) {
|
|
l = NewLoggerWithAdapterName(noun, "")
|
|
|
|
return l
|
|
}
|
|
|
|
func (l *Logger) Noun() string {
|
|
return l.noun
|
|
}
|
|
|
|
func (l *Logger) Adapter() LogAdapter {
|
|
return l.la
|
|
}
|
|
|
|
var (
|
|
configureMutex sync.Mutex
|
|
)
|
|
|
|
func (l *Logger) doConfigure(force bool) {
|
|
configureMutex.Lock()
|
|
defer configureMutex.Unlock()
|
|
|
|
if l.isConfigured == true && force == false {
|
|
return
|
|
}
|
|
|
|
if IsConfigurationLoaded() == false {
|
|
Panic(ErrConfigurationNotLoaded)
|
|
}
|
|
|
|
if l.an == "" {
|
|
l.an = GetDefaultAdapterName()
|
|
}
|
|
|
|
// If this is empty, then no specific adapter was given or no system
|
|
// default was configured (which implies that no adapters were registered).
|
|
// All of our logging will be skipped.
|
|
if l.an != "" {
|
|
la, found := adapters[l.an]
|
|
if found == false {
|
|
Panic(fmt.Errorf("adapter is not valid: %s", l.an))
|
|
}
|
|
|
|
l.la = la
|
|
}
|
|
|
|
// Set the level.
|
|
|
|
systemLevel, found := LevelNameMap[levelName]
|
|
if found == false {
|
|
Panic(fmt.Errorf("log-level not valid: [%s]", levelName))
|
|
}
|
|
|
|
l.systemLevel = systemLevel
|
|
|
|
// Set the form.
|
|
|
|
if format == "" {
|
|
Panic(ErrFormatEmpty)
|
|
}
|
|
|
|
if t, err := template.New("logItem").Parse(format); err != nil {
|
|
Panic(err)
|
|
} else {
|
|
l.t = t
|
|
}
|
|
|
|
l.isConfigured = true
|
|
}
|
|
|
|
func (l *Logger) flattenMessage(lc *MessageContext, format *string, args []interface{}) (string, error) {
|
|
m := fmt.Sprintf(*format, args...)
|
|
|
|
lc.Message = &m
|
|
|
|
var b bytes.Buffer
|
|
if err := l.t.Execute(&b, *lc); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
func (l *Logger) allowMessage(noun string, level int) bool {
|
|
if _, found := includeFilters[noun]; found == true {
|
|
return true
|
|
}
|
|
|
|
// If we didn't hit an include filter and we *had* include filters, filter
|
|
// it out.
|
|
if useIncludeFilters == true {
|
|
return false
|
|
}
|
|
|
|
if _, found := excludeFilters[noun]; found == true {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (l *Logger) makeLogContext(ctx context.Context) *LogContext {
|
|
return &LogContext{
|
|
Ctx: ctx,
|
|
Logger: l,
|
|
}
|
|
}
|
|
|
|
type LogMethod func(lc *LogContext, message *string) error
|
|
|
|
func (l *Logger) log(ctx context.Context, level int, lm LogMethod, format string, args []interface{}) error {
|
|
if l.systemLevel > level {
|
|
return nil
|
|
}
|
|
|
|
// Preempt the normal filter checks if we can unconditionally allow at a
|
|
// certain level and we've hit that level.
|
|
//
|
|
// Notice that this is only relevant if the system-log level is letting
|
|
// *anything* show logs at the level we came in with.
|
|
canExcludeBypass := level >= excludeBypassLevel && excludeBypassLevel != -1
|
|
didExcludeBypass := false
|
|
|
|
n := l.Noun()
|
|
|
|
if l.allowMessage(n, level) == false {
|
|
if canExcludeBypass == false {
|
|
return nil
|
|
} else {
|
|
didExcludeBypass = true
|
|
}
|
|
}
|
|
|
|
levelName, found := LevelNameMapR[level]
|
|
if found == false {
|
|
Panic(fmt.Errorf("level not valid: (%d)", level))
|
|
}
|
|
|
|
levelName = strings.ToUpper(levelName)
|
|
|
|
lc := &MessageContext{
|
|
Level: &levelName,
|
|
Noun: &n,
|
|
ExcludeBypass: didExcludeBypass,
|
|
}
|
|
|
|
if s, err := l.flattenMessage(lc, &format, args); err != nil {
|
|
return err
|
|
} else {
|
|
lc := l.makeLogContext(ctx)
|
|
if err := lm(lc, &s); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return e.New(s)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) Debugf(ctx context.Context, format string, args ...interface{}) {
|
|
l.doConfigure(false)
|
|
|
|
if l.la != nil {
|
|
l.log(ctx, LevelDebug, l.la.Debugf, format, args)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) Infof(ctx context.Context, format string, args ...interface{}) {
|
|
l.doConfigure(false)
|
|
|
|
if l.la != nil {
|
|
l.log(ctx, LevelInfo, l.la.Infof, format, args)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) Warningf(ctx context.Context, format string, args ...interface{}) {
|
|
l.doConfigure(false)
|
|
|
|
if l.la != nil {
|
|
l.log(ctx, LevelWarning, l.la.Warningf, format, args)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) mergeStack(err interface{}, format string, args []interface{}) (string, []interface{}) {
|
|
if format != "" {
|
|
format += "\n%s"
|
|
} else {
|
|
format = "%s"
|
|
}
|
|
|
|
var stackified *errors.Error
|
|
stackified, ok := err.(*errors.Error)
|
|
if ok == false {
|
|
stackified = errors.Wrap(err, 2)
|
|
}
|
|
|
|
args = append(args, stackified.ErrorStack())
|
|
|
|
return format, args
|
|
}
|
|
|
|
func (l *Logger) Errorf(ctx context.Context, errRaw interface{}, format string, args ...interface{}) {
|
|
l.doConfigure(false)
|
|
|
|
var err interface{}
|
|
|
|
if errRaw != nil {
|
|
_, ok := errRaw.(*errors.Error)
|
|
if ok == true {
|
|
err = errRaw
|
|
} else {
|
|
err = errors.Wrap(errRaw, 1)
|
|
}
|
|
}
|
|
|
|
if l.la != nil {
|
|
if errRaw != nil {
|
|
format, args = l.mergeStack(err, format, args)
|
|
}
|
|
|
|
l.log(ctx, LevelError, l.la.Errorf, format, args)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) ErrorIff(ctx context.Context, errRaw interface{}, format string, args ...interface{}) {
|
|
if errRaw == nil {
|
|
return
|
|
}
|
|
|
|
var err interface{}
|
|
|
|
_, ok := errRaw.(*errors.Error)
|
|
if ok == true {
|
|
err = errRaw
|
|
} else {
|
|
err = errors.Wrap(errRaw, 1)
|
|
}
|
|
|
|
l.Errorf(ctx, err, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Panicf(ctx context.Context, errRaw interface{}, format string, args ...interface{}) {
|
|
l.doConfigure(false)
|
|
|
|
var err interface{}
|
|
|
|
_, ok := errRaw.(*errors.Error)
|
|
if ok == true {
|
|
err = errRaw
|
|
} else {
|
|
err = errors.Wrap(errRaw, 1)
|
|
}
|
|
|
|
if l.la != nil {
|
|
format, args = l.mergeStack(err, format, args)
|
|
err = l.log(ctx, LevelError, l.la.Errorf, format, args)
|
|
}
|
|
|
|
Panic(err.(error))
|
|
}
|
|
|
|
func (l *Logger) PanicIff(ctx context.Context, errRaw interface{}, format string, args ...interface{}) {
|
|
if errRaw == nil {
|
|
return
|
|
}
|
|
|
|
var err interface{}
|
|
|
|
_, ok := errRaw.(*errors.Error)
|
|
if ok == true {
|
|
err = errRaw
|
|
} else {
|
|
err = errors.Wrap(errRaw, 1)
|
|
}
|
|
|
|
l.Panicf(ctx, err.(error), format, args...)
|
|
}
|
|
|
|
func Wrap(err interface{}) *errors.Error {
|
|
es, ok := err.(*errors.Error)
|
|
if ok == true {
|
|
return es
|
|
} else {
|
|
return errors.Wrap(err, 1)
|
|
}
|
|
}
|
|
|
|
func Errorf(message string, args ...interface{}) *errors.Error {
|
|
err := fmt.Errorf(message, args...)
|
|
return errors.Wrap(err, 1)
|
|
}
|
|
|
|
func Panic(err interface{}) {
|
|
_, ok := err.(*errors.Error)
|
|
if ok == true {
|
|
panic(err)
|
|
} else {
|
|
panic(errors.Wrap(err, 1))
|
|
}
|
|
}
|
|
|
|
func Panicf(message string, args ...interface{}) {
|
|
err := Errorf(message, args...)
|
|
Panic(err)
|
|
}
|
|
|
|
func PanicIf(err interface{}) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
_, ok := err.(*errors.Error)
|
|
if ok == true {
|
|
panic(err)
|
|
} else {
|
|
panic(errors.Wrap(err, 1))
|
|
}
|
|
}
|
|
|
|
// Is checks if the left ("actual") error equals the right ("against") error.
|
|
// The right must be an unwrapped error (the kind that you'd initialize as a
|
|
// global variable). The left can be a wrapped or unwrapped error.
|
|
func Is(actual, against error) bool {
|
|
// If it's an unwrapped error.
|
|
if _, ok := actual.(*errors.Error); ok == false {
|
|
return actual == against
|
|
}
|
|
|
|
return errors.Is(actual, against)
|
|
}
|
|
|
|
// Print is a utility function to prevent the caller from having to import the
|
|
// third-party library.
|
|
func PrintError(err error) {
|
|
wrapped := Wrap(err)
|
|
fmt.Printf("Stack:\n\n%s\n", wrapped.ErrorStack())
|
|
}
|
|
|
|
// PrintErrorf is a utility function to prevent the caller from having to
|
|
// import the third-party library.
|
|
func PrintErrorf(err error, format string, args ...interface{}) {
|
|
wrapped := Wrap(err)
|
|
|
|
fmt.Printf(format, args...)
|
|
fmt.Printf("\n")
|
|
fmt.Printf("Stack:\n\n%s\n", wrapped.ErrorStack())
|
|
}
|
|
|
|
func init() {
|
|
if format == "" {
|
|
format = defaultFormat
|
|
}
|
|
|
|
if levelName == "" {
|
|
levelName = defaultLevelName
|
|
}
|
|
|
|
if includeNouns != "" {
|
|
for _, noun := range strings.Split(includeNouns, ",") {
|
|
AddIncludeFilter(noun)
|
|
}
|
|
}
|
|
|
|
if excludeNouns != "" {
|
|
for _, noun := range strings.Split(excludeNouns, ",") {
|
|
AddExcludeFilter(noun)
|
|
}
|
|
}
|
|
|
|
if excludeBypassLevelName != "" {
|
|
var found bool
|
|
if excludeBypassLevel, found = LevelNameMap[excludeBypassLevelName]; found == false {
|
|
panic(ErrExcludeLevelNameInvalid)
|
|
}
|
|
}
|
|
}
|