mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 08:42:27 -05:00 
			
		
		
		
	* start fixing up tests * fix up tests + automate with drone * fiddle with linting * messing about with drone.yml * some more fiddling * hmmm * add cache * add vendor directory * verbose * ci updates * update some little things * update sig
		
			
				
	
	
		
			537 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			537 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)
 | |
| 		}
 | |
| 	}
 | |
| }
 |