Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager

This commit is contained in:
Jamie Curnow
2022-05-12 08:47:31 +10:00
parent 4db34f5894
commit 2110ecc382
830 changed files with 38168 additions and 36635 deletions

View File

@ -0,0 +1,40 @@
package logger
import "github.com/getsentry/sentry-go"
// Level type
type Level int
// Log level definitions
const (
// DebugLevel usually only enabled when debugging. Very verbose logging.
DebugLevel Level = 10
// InfoLevel general operational entries about what's going on inside the application.
InfoLevel Level = 20
// WarnLevel non-critical entries that deserve eyes.
WarnLevel Level = 30
// ErrorLevel used for errors that should definitely be noted.
ErrorLevel Level = 40
)
// Config options for the logger.
type Config struct {
LogThreshold Level
Formatter string
SentryConfig sentry.ClientOptions
}
// Interface for a logger
type Interface interface {
GetLogLevel() Level
Debug(format string, args ...interface{})
Info(format string, args ...interface{})
Warn(format string, args ...interface{})
Error(errorClass string, err error, args ...interface{})
Errorf(errorClass, format string, err error, args ...interface{})
}
// ConfigurableLogger is an interface for a logger that can be configured
type ConfigurableLogger interface {
Configure(c *Config) error
}

View File

@ -0,0 +1,242 @@
package logger
import (
"encoding/json"
"fmt"
stdlog "log"
"os"
"runtime/debug"
"sync"
"time"
"github.com/fatih/color"
"github.com/getsentry/sentry-go"
)
var colorReset, colorGray, colorYellow, colorBlue, colorRed, colorMagenta, colorBlack, colorWhite *color.Color
// Log message structure.
type Log struct {
Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Pid int `json:"pid"`
Summary string `json:"summary,omitempty"`
Caller string `json:"caller,omitempty"`
StackTrace []string `json:"stack_trace,omitempty"`
}
// Logger instance
type Logger struct {
Config
mux sync.Mutex
}
// global logging configuration.
var logger = NewLogger()
// NewLogger creates a new logger instance
func NewLogger() *Logger {
color.NoColor = false
colorReset = color.New(color.Reset)
colorGray = color.New(color.FgWhite)
colorYellow = color.New(color.Bold, color.FgYellow)
colorBlue = color.New(color.Bold, color.FgBlue)
colorRed = color.New(color.Bold, color.FgRed)
colorMagenta = color.New(color.Bold, color.FgMagenta)
colorBlack = color.New(color.Bold, color.FgBlack)
colorWhite = color.New(color.Bold, color.FgWhite)
return &Logger{
Config: NewConfig(),
}
}
// NewConfig returns the default config
func NewConfig() Config {
return Config{
LogThreshold: InfoLevel,
Formatter: "json",
}
}
// Configure logger and will return error if missing required fields.
func Configure(c *Config) error {
return logger.Configure(c)
}
// GetLogLevel currently configured
func GetLogLevel() Level {
return logger.GetLogLevel()
}
// Debug logs if the log level is set to DebugLevel or below. Arguments are handled in the manner of fmt.Printf.
func Debug(format string, args ...interface{}) {
logger.Debug(format, args...)
}
// Info logs if the log level is set to InfoLevel or below. Arguments are handled in the manner of fmt.Printf.
func Info(format string, args ...interface{}) {
logger.Info(format, args...)
}
// Warn logs if the log level is set to WarnLevel or below. Arguments are handled in the manner of fmt.Printf.
func Warn(format string, args ...interface{}) {
logger.Warn(format, args...)
}
// Error logs error given if the log level is set to ErrorLevel or below. Arguments are not logged.
// Attempts to log to bugsang.
func Error(errorClass string, err error) {
logger.Error(errorClass, err)
}
// Configure logger and will return error if missing required fields.
func (l *Logger) Configure(c *Config) error {
// ensure updates to the config are atomic
l.mux.Lock()
defer l.mux.Unlock()
if c == nil {
return fmt.Errorf("a non nil Config is mandatory")
}
if err := c.LogThreshold.validate(); err != nil {
return err
}
l.LogThreshold = c.LogThreshold
l.Formatter = c.Formatter
l.SentryConfig = c.SentryConfig
if c.SentryConfig.Dsn != "" {
if sentryErr := sentry.Init(c.SentryConfig); sentryErr != nil {
fmt.Printf("Sentry initialization failed: %v\n", sentryErr)
}
}
stdlog.SetFlags(0) // this removes timestamp prefixes from logs
return nil
}
// validate the log level is in the accepted list.
func (l Level) validate() error {
switch l {
case DebugLevel, InfoLevel, WarnLevel, ErrorLevel:
return nil
default:
return fmt.Errorf("invalid \"Level\" %d", l)
}
}
var logLevels = map[Level]string{
DebugLevel: "DEBUG",
InfoLevel: "INFO",
WarnLevel: "WARN",
ErrorLevel: "ERROR",
}
func (l *Logger) logLevel(logLevel Level, format string, args ...interface{}) {
if logLevel < l.LogThreshold {
return
}
errorClass := ""
if logLevel == ErrorLevel {
// First arg is the errorClass
errorClass = args[0].(string)
if len(args) > 1 {
args = args[1:]
} else {
args = []interface{}{}
}
}
stringMessage := fmt.Sprintf(format, args...)
if l.Formatter == "json" {
// JSON Log Format
jsonLog, _ := json.Marshal(
Log{
Timestamp: time.Now().Format(time.RFC3339Nano),
Level: logLevels[logLevel],
Message: stringMessage,
Pid: os.Getpid(),
},
)
stdlog.Println(string(jsonLog))
} else {
// Nice Log Format
var colorLevel *color.Color
switch logLevel {
case DebugLevel:
colorLevel = colorMagenta
case InfoLevel:
colorLevel = colorBlue
case WarnLevel:
colorLevel = colorYellow
case ErrorLevel:
colorLevel = colorRed
stringMessage = fmt.Sprintf("%s: %s", errorClass, stringMessage)
}
t := time.Now()
stdlog.Println(
colorBlack.Sprint("["),
colorWhite.Sprint(t.Format("2006-01-02 15:04:05")),
colorBlack.Sprint("] "),
colorLevel.Sprintf("%-8v", logLevels[logLevel]),
colorGray.Sprint(stringMessage),
colorReset.Sprint(""),
)
if logLevel == ErrorLevel && l.LogThreshold == DebugLevel {
// Print a stack trace too
debug.PrintStack()
}
}
}
// GetLogLevel currently configured
func (l *Logger) GetLogLevel() Level {
return l.LogThreshold
}
// Debug logs if the log level is set to DebugLevel or below. Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Debug(format string, args ...interface{}) {
l.logLevel(DebugLevel, format, args...)
}
// Info logs if the log level is set to InfoLevel or below. Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Info(format string, args ...interface{}) {
l.logLevel(InfoLevel, format, args...)
}
// Warn logs if the log level is set to WarnLevel or below. Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Warn(format string, args ...interface{}) {
l.logLevel(WarnLevel, format, args...)
}
// Error logs error given if the log level is set to ErrorLevel or below. Arguments are not logged.
// Attempts to log to bugsang.
func (l *Logger) Error(errorClass string, err error) {
l.logLevel(ErrorLevel, err.Error(), errorClass)
l.notifySentry(errorClass, err)
}
func (l *Logger) notifySentry(errorClass string, err error) {
if l.SentryConfig.Dsn != "" && l.SentryConfig.Dsn != "-" {
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetLevel(sentry.LevelError)
scope.SetTag("service", "backend")
scope.SetTag("error_class", errorClass)
})
sentry.CaptureException(err)
// Since sentry emits events in the background we need to make sure
// they are sent before we shut down
sentry.Flush(time.Second * 5)
}
}

View File

@ -0,0 +1,168 @@
package logger
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/getsentry/sentry-go"
"github.com/stretchr/testify/assert"
)
func TestGetLogLevel(t *testing.T) {
assert.Equal(t, InfoLevel, GetLogLevel())
}
func TestThreshold(t *testing.T) {
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
log.SetOutput(os.Stderr)
}()
assert.NoError(t, Configure(&Config{
LogThreshold: InfoLevel,
}))
Debug("this should not display")
assert.Empty(t, buf.String())
Info("this should display")
assert.NotEmpty(t, buf.String())
Error("ErrorClass", errors.New("this should display"))
assert.NotEmpty(t, buf.String())
}
func TestDebug(t *testing.T) {
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
log.SetOutput(os.Stderr)
}()
assert.NoError(t, Configure(&Config{
LogThreshold: DebugLevel,
}))
Debug("This is a %s message", "test")
assert.Contains(t, buf.String(), "DEBUG")
assert.Contains(t, buf.String(), "This is a test message")
}
func TestInfo(t *testing.T) {
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
log.SetOutput(os.Stderr)
}()
assert.NoError(t, Configure(&Config{
LogThreshold: InfoLevel,
}))
Info("This is a %s message", "test")
assert.Contains(t, buf.String(), "INFO")
assert.Contains(t, buf.String(), "This is a test message")
}
func TestWarn(t *testing.T) {
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
log.SetOutput(os.Stderr)
}()
assert.NoError(t, Configure(&Config{
LogThreshold: InfoLevel,
}))
Warn("This is a %s message", "test")
assert.Contains(t, buf.String(), "WARN")
assert.Contains(t, buf.String(), "This is a test message")
}
func TestError(t *testing.T) {
buf := new(bytes.Buffer)
log.SetOutput(buf)
defer func() {
log.SetOutput(os.Stderr)
}()
assert.NoError(t, Configure(&Config{
LogThreshold: ErrorLevel,
}))
Error("TestErrorClass", fmt.Errorf("this is a %s error", "test"))
assert.Contains(t, buf.String(), "ERROR")
assert.Contains(t, buf.String(), "this is a test error")
}
func TestConfigure(t *testing.T) {
type args struct {
c *Config
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "configure",
args: args{
&Config{
LogThreshold: InfoLevel,
SentryConfig: sentry.ClientOptions{},
},
},
wantErr: false,
},
{
name: "invalid log level",
args: args{
&Config{
SentryConfig: sentry.ClientOptions{},
},
},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if err := Configure(tt.args.c); (err != nil) != tt.wantErr {
t.Errorf("Configure() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func BenchmarkLogLevelBelowThreshold(b *testing.B) {
l := NewLogger()
log.SetOutput(ioutil.Discard)
defer func() {
log.SetOutput(os.Stderr)
}()
for i := 0; i < b.N; i++ {
l.logLevel(DebugLevel, "benchmark %d", i)
}
}
func BenchmarkLogLevelAboveThreshold(b *testing.B) {
l := NewLogger()
log.SetOutput(ioutil.Discard)
defer func() {
log.SetOutput(os.Stderr)
}()
for i := 0; i < b.N; i++ {
l.logLevel(InfoLevel, "benchmark %d", i)
}
}