diff --git a/src/logger/exit.go b/src/logger/exit.go new file mode 100644 index 0000000..9d185ad --- /dev/null +++ b/src/logger/exit.go @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Sue B.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +// Inspired by +// https://stackoverflow.com/a/30690532 + +import "os" + +// Func takes a code as exit status +type Func func(int) + +// Exit has an exit func, and will memorize the exit status code +type Exit struct { + exit Func + status int +} + +// Exit calls the exiter, and then returns code as status. +// If e was declared, but never set (since only a test would set e), +// simply calls os.Exit() +func (e *Exit) Exit(code int) { + if e != nil { + e.status = code + e.exit(code) + } else { + os.Exit(code) + } +} + +// Status get the exit status code as memorized +// after the call to the exit func. +func (e *Exit) Status() int { + return e.status +} + +// Default returns an Exit with default os.Exit() call. +// That means the status will never be visible, +// since os.Exit() stops everything. +func Default() *Exit { + return &Exit{exit: os.Exit} +} + +// CreateExiter returns an exiter with a custom function +func CreateExiter(exit Func) *Exit { + return &Exit{exit: exit} +} diff --git a/src/logger/logger.go b/src/logger/logger.go index 26afcc6..cc2fc53 100644 --- a/src/logger/logger.go +++ b/src/logger/logger.go @@ -22,7 +22,6 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "log" - "os" ) const ( @@ -34,6 +33,7 @@ const ( type Logger struct { logr.Logger + exiter *Exit } // CreateLogger creates a new logger instance @@ -41,15 +41,37 @@ type Logger struct { func CreateLogger(isDebug bool) Logger { var logger logr.Logger logger = zapr.NewLogger(createZapLogger(isDebug)) - // TODO return Logger{ logger, + nil, } } +// SetExiter useful for debugging +// when the exit is set, the code won't call os.Exit($code) but will set the status on the Exit struct +// this is useful when you want to test if a function crashes with os.Exit(1) for example. +func (l Logger) SetExiter(exit *Exit) { + l.exiter = exit +} + +// Fatal logs message with important level and exits with code 1 +func (l Logger) Fatal(msg string, keysAndValues ...interface{}) { + if keysAndValues == nil { + l.V(ImportantLevel).Info(msg) + } else { + l.V(ImportantLevel).Info(msg, keysAndValues) + } + l.exiter.Exit(1) +} + +// FatalError logs message and error with important level and exits with code 1 func (l Logger) FatalError(err error, msg string, keysAndValues ...interface{}) { - l.V(ImportantLevel).Error(err, msg, keysAndValues) - os.Exit(1) + if keysAndValues == nil { + l.V(ImportantLevel).Error(err, msg) + } else { + l.V(ImportantLevel).Error(err, msg, keysAndValues) + } + l.exiter.Exit(1) } func createZapLogger(isDebug bool) *zap.Logger { diff --git a/src/logger/logger_test.go b/src/logger/logger_test.go new file mode 100644 index 0000000..5319881 --- /dev/null +++ b/src/logger/logger_test.go @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Sue B.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFatalError(t *testing.T) { + l := CreateDebugLogger() + err := fmt.Errorf("test error") + msg := "this is a test crash" + l.FatalError(err, msg) + assert.Equal(t, 1, l.GetExiter().status) + logs := GetObservedLogs() + theMsg := logs.FilterMessage(msg) + assert.Equal(t, theMsg.All()[0].Message, msg) +} + +func TestFatal(t *testing.T) { + l := CreateDebugLogger() + msg := "this is a test crash" + l.Fatal(msg) + assert.Equal(t, 1, l.GetExiter().status) + logs := GetObservedLogs() + theMsg := logs.FilterMessage(msg) + assert.Equal(t, theMsg.All()[0].Message, msg) +} diff --git a/src/logger/test_util.go b/src/logger/test_util.go index af1ba07..adc54e7 100644 --- a/src/logger/test_util.go +++ b/src/logger/test_util.go @@ -27,7 +27,11 @@ var observedLogs *observer.ObservedLogs func CreateDebugLogger() Logger { l := zapr.NewLogger(createObservedZapLogger()) - return Logger{l} + e := CreateExiter(func(i int) {}) + return Logger{ + l, + e, + } } // GetObservedLogs returns ObservedLogs object, can be used in unit tests to see if something got logged @@ -35,6 +39,10 @@ func GetObservedLogs() *observer.ObservedLogs { return observedLogs } +func (l Logger) GetExiter() *Exit { + return l.exiter +} + func createObservedZapLogger() *zap.Logger { core, logs := observer.New(zapcore.Level(PerformanceTestLevel)) // TODO implement variable info level zLogger := zap.New(core)