From fa8685b88ae35154da8a7723ae658fdb8a2ad7a0 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Mon, 29 Dec 2025 20:14:03 +0100 Subject: [PATCH 01/10] refactor: sets the services to be injected into the encoders to simplify the logger components communication --- internal/services/colors_printer.go | 34 ---------- internal/services/datetime_printer.go | 22 +++---- internal/services/json_marshaler.go | 4 ++ internal/services/printer.go | 66 +++++++++++++++++++ ...{color_printer_test.go => printer_test.go} | 7 +- logs/encoders/base.go | 25 ------- logs/encoders/base_test.go | 7 +- logs/encoders/default.go | 21 +++--- logs/encoders/default_test.go | 19 +++--- logs/encoders/json.go | 19 +++--- logs/encoders/json_test.go | 23 +++---- logs/encoders/yaml.go | 19 +++--- logs/encoders/yaml_test.go | 23 +++---- logs/logger.go | 28 ++++---- shared/interfaces.go | 1 - 15 files changed, 164 insertions(+), 154 deletions(-) delete mode 100644 internal/services/colors_printer.go create mode 100644 internal/services/printer.go rename internal/services/{color_printer_test.go => printer_test.go} (97%) diff --git a/internal/services/colors_printer.go b/internal/services/colors_printer.go deleted file mode 100644 index d071cff..0000000 --- a/internal/services/colors_printer.go +++ /dev/null @@ -1,34 +0,0 @@ -package services - -import ( - c "github.com/pho3b/tiny-logger/logs/colors" - "github.com/pho3b/tiny-logger/logs/log_level" -) - -type ColorsPrinter struct { -} - -// RetrieveColorsFromLogLevel returns an array of colors as strings to be used in log output based on a given log level. -// if enableColors is false, it returns an array of empty strings. -func (d *ColorsPrinter) RetrieveColorsFromLogLevel(enableColors bool, logLevelInt int8) []c.Color { - var res = []c.Color{"", ""} - - if enableColors { - switch logLevelInt { - case log_level.FatalErrorLvl: - res[0] = c.Magenta - case log_level.ErrorLvl: - res[0] = c.Red - case log_level.WarnLvl: - res[0] = c.Yellow - case log_level.InfoLvl: - res[0] = c.Cyan - case log_level.DebugLvl: - res[0] = c.Gray - } - - res[1] = c.Reset - } - - return res -} diff --git a/internal/services/datetime_printer.go b/internal/services/datetime_printer.go index 1ef9d97..1afd661 100644 --- a/internal/services/datetime_printer.go +++ b/internal/services/datetime_printer.go @@ -43,7 +43,7 @@ func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, strin if currentFmt == s.UnixTimestamp && (addDate || addTime) { d.unixOnce.Do(func() { d.currentUnix.Store(strconv.FormatInt(d.timeNow().Unix(), 10)) - go d.updateCurrentUnixEverySecond() + go d.updateCurrentUnix() }) return "", "", d.currentUnix.Load().(string) @@ -52,7 +52,7 @@ func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, strin if addDate { d.dateOnce.Do(func() { d.currentDate.Store(d.timeNow().Format(dateFormat[currentFmt])) - go d.updateCurrentDateEveryDay() + go d.updateCurrentDate() }) dateRes = d.currentDate.Load().(string) @@ -61,7 +61,7 @@ func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, strin if addTime { d.timeOnce.Do(func() { d.currentTime.Store(d.timeNow().Format(timeFormat[currentFmt])) - go d.updateCurrentTimeEverySecond() + go d.updateCurrentTime() }) timeRes = d.currentTime.Load().(string) @@ -85,22 +85,20 @@ func (d *DateTimePrinter) UpdateDateTimeFormat(format s.DateTimeFormat) { } // updateCurrentDateEveryDay synchronizes with the system clock and updates the DateTimePrinter's -// currentDate property every midnight. -func (d *DateTimePrinter) updateCurrentDateEveryDay() { +// currentDate property every 10 minutes. +func (d *DateTimePrinter) updateCurrentDate() { for { now := d.timeNow() currentFmt := d.currentFormat.Load().(s.DateTimeFormat) d.currentDate.Store(now.Format(dateFormat[currentFmt])) - // computing next midnight in local time zone - nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()) - time.Sleep(time.Until(nextMidnight)) + time.Sleep(time.Minute * 10) } } -// updateCurrentTimeEverySecond synchronizes with the system clock and updates the DateTimePrinter's +// updateCurrentTime synchronizes with the system clock and updates the DateTimePrinter's // currentTime property every full second. -func (d *DateTimePrinter) updateCurrentTimeEverySecond() { +func (d *DateTimePrinter) updateCurrentTime() { for { now := d.timeNow() currentFmt := d.currentFormat.Load().(s.DateTimeFormat) @@ -111,9 +109,9 @@ func (d *DateTimePrinter) updateCurrentTimeEverySecond() { } } -// updateCurrentUnixEverySecond synchronizes with the system clock and updates the DateTimePrinter's +// updateCurrentUnix synchronizes with the system clock and updates the DateTimePrinter's // currentTime property every full second. -func (d *DateTimePrinter) updateCurrentUnixEverySecond() { +func (d *DateTimePrinter) updateCurrentUnix() { for { now := d.timeNow() d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10)) diff --git a/internal/services/json_marshaler.go b/internal/services/json_marshaler.go index 227881f..9f4bdea 100644 --- a/internal/services/json_marshaler.go +++ b/internal/services/json_marshaler.go @@ -152,3 +152,7 @@ func (j *JsonMarshaler) writeLogEntryProperties( buf.WriteByte(',') } } + +func NewJsonMarshaler() JsonMarshaler { + return JsonMarshaler{} +} diff --git a/internal/services/printer.go b/internal/services/printer.go new file mode 100644 index 0000000..af2e219 --- /dev/null +++ b/internal/services/printer.go @@ -0,0 +1,66 @@ +package services + +import ( + "bytes" + "os" + + c "github.com/pho3b/tiny-logger/logs/colors" + "github.com/pho3b/tiny-logger/logs/log_level" + s "github.com/pho3b/tiny-logger/shared" +) + +type Printer struct { +} + +// PrintLog prints the given msgBuffer to the given outputType (stdout or stderr). +// If 'file' is not nil AND (outType == FileOutput), the message is written to the file. +func (p *Printer) PrintLog(outType s.OutputType, msgBuffer *bytes.Buffer, file *os.File) { + var err error + + switch outType { + case s.StdOutput: + _, err = os.Stdout.Write(msgBuffer.Bytes()) + case s.StdErrOutput: + _, err = os.Stderr.Write(msgBuffer.Bytes()) + case s.FileOutput: + if file == nil { + _, _ = os.Stderr.Write([]byte("tiny-logger-err: given out file is nil")) + return + } + + _, err = file.Write(msgBuffer.Bytes()) + } + + if err != nil { + _, _ = os.Stderr.Write([]byte("tiny-logger-err: " + err.Error() + "\n")) + } +} + +// RetrieveColorsFromLogLevel returns an array of colors as strings to be used in log output based on a given log level. +// if enableColors is false, it returns an array of empty strings. +func (p *Printer) RetrieveColorsFromLogLevel(enableColors bool, logLevelInt int8) []c.Color { + var res = []c.Color{"", ""} + + if enableColors { + switch logLevelInt { + case log_level.FatalErrorLvl: + res[0] = c.Magenta + case log_level.ErrorLvl: + res[0] = c.Red + case log_level.WarnLvl: + res[0] = c.Yellow + case log_level.InfoLvl: + res[0] = c.Cyan + case log_level.DebugLvl: + res[0] = c.Gray + } + + res[1] = c.Reset + } + + return res +} + +func NewPrinter() Printer { + return Printer{} +} diff --git a/internal/services/color_printer_test.go b/internal/services/printer_test.go similarity index 97% rename from internal/services/color_printer_test.go rename to internal/services/printer_test.go index 1428b58..cd6c8f8 100644 --- a/internal/services/color_printer_test.go +++ b/internal/services/printer_test.go @@ -1,15 +1,16 @@ package services import ( - "github.com/pho3b/tiny-logger/logs/log_level" "testing" + "github.com/pho3b/tiny-logger/logs/log_level" + c "github.com/pho3b/tiny-logger/logs/colors" "github.com/stretchr/testify/assert" ) func TestPrintColors_EnableColorsTrue(t *testing.T) { - printer := ColorsPrinter{} + printer := Printer{} result := printer.RetrieveColorsFromLogLevel(true, log_level.FatalErrorLvl) assert.Equal(t, c.Magenta, result[0], "Expected first element to be the provided color") @@ -33,7 +34,7 @@ func TestPrintColors_EnableColorsTrue(t *testing.T) { } func TestPrintColors_EnableColorsFalse(t *testing.T) { - printer := ColorsPrinter{} + printer := Printer{} result := printer.RetrieveColorsFromLogLevel(false, log_level.DebugLvl) assert.Equal(t, c.Color(""), result[0], "Expected first element to be an empty string when colors are disabled") diff --git a/logs/encoders/base.go b/logs/encoders/base.go index eb77e37..e585af7 100644 --- a/logs/encoders/base.go +++ b/logs/encoders/base.go @@ -3,7 +3,6 @@ package encoders import ( "bytes" "fmt" - "os" "strconv" "sync" @@ -79,30 +78,6 @@ func (b *baseEncoder) castToString(arg any) string { } } -// printLog prints the given msgBuffer to the given outputType (stdout or stderr). -// If 'file' is not nil, the message is written to the file. -func (b *baseEncoder) printLog(outType s.OutputType, msgBuffer *bytes.Buffer, file *os.File) { - var err error - - switch outType { - case s.StdOutput: - _, err = os.Stdout.Write(msgBuffer.Bytes()) - case s.StdErrOutput: - _, err = os.Stderr.Write(msgBuffer.Bytes()) - case s.FileOutput: - if file == nil { - _, _ = os.Stderr.Write([]byte("tiny-logger-err: given out file is nil")) - return - } - - _, err = file.Write(msgBuffer.Bytes()) - } - - if err != nil { - _, _ = os.Stderr.Write([]byte("tiny-logger-err: " + err.Error() + "\n")) - } -} - // getBuffer returns a new bytes buffer from the pool. // If the pool is empty, a new buffer is created. func (b *baseEncoder) getBuffer() *bytes.Buffer { diff --git a/logs/encoders/base_test.go b/logs/encoders/base_test.go index 26c84e4..2f9d098 100644 --- a/logs/encoders/base_test.go +++ b/logs/encoders/base_test.go @@ -7,6 +7,7 @@ import ( "sync" "testing" + "github.com/pho3b/tiny-logger/internal/services" s "github.com/pho3b/tiny-logger/shared" "github.com/stretchr/testify/assert" ) @@ -87,13 +88,13 @@ func TestBuildMsgWithCastAndConcatenateInto(t *testing.T) { } func TestBaseEncoder_GetType(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) assert.Equal(t, s.DefaultEncoderType, encoder.GetType()) - jsonEncoder := NewJSONEncoder() + jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) assert.Equal(t, s.JsonEncoderType, jsonEncoder.GetType()) - yamlEncoder := NewYAMLEncoder() + yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) assert.Equal(t, s.YamlEncoderType, yamlEncoder.GetType()) baseEncoder := newBaseEncoder() diff --git a/logs/encoders/default.go b/logs/encoders/default.go index 7f80498..be960dd 100644 --- a/logs/encoders/default.go +++ b/logs/encoders/default.go @@ -13,7 +13,7 @@ import ( type DefaultEncoder struct { baseEncoder dateTimeFormat s.DateTimeFormat - ColorsPrinter services.ColorsPrinter + printer services.Printer DateTimePrinter *services.DateTimePrinter } @@ -38,7 +38,7 @@ func (d *DefaultEncoder) Log( ) msgBuffer.WriteByte('\n') - d.printLog(outType, msgBuffer, logger.GetLogFile()) + d.printer.PrintLog(outType, msgBuffer, logger.GetLogFile()) d.putBuffer(msgBuffer) } @@ -60,17 +60,11 @@ func (d *DefaultEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, a msgBuffer.WriteString(c.Reset.String()) msgBuffer.WriteByte('\n') - d.printLog(s.StdOutput, msgBuffer, logger.GetLogFile()) + d.printer.PrintLog(s.StdOutput, msgBuffer, logger.GetLogFile()) d.putBuffer(msgBuffer) } } -// SetDateTimeFormat updates the date and time format used by the encoder's DateTimePrinter. -// This method triggers an immediate update of the cached date and time strings to match the new format. -func (d *DefaultEncoder) SetDateTimeFormat(format s.DateTimeFormat) { - d.DateTimePrinter.UpdateDateTimeFormat(format) -} - // composeMsgInto formats and writes the given 'msg' into the given buffer. func (d *DefaultEncoder) composeMsgInto( buf *bytes.Buffer, @@ -84,7 +78,7 @@ func (d *DefaultEncoder) composeMsgInto( buf.Grow(len(args)*averageWordLen + defaultCharOverhead) isDateOrTimeEnabled := dateEnabled || timeEnabled - colors := d.ColorsPrinter.RetrieveColorsFromLogLevel(headerColorEnabled, ll.LogLvlNameToInt[logLevel]) + colors := d.printer.RetrieveColorsFromLogLevel(headerColorEnabled, ll.LogLvlNameToInt[logLevel]) buf.WriteString(string(colors[0])) if showLogLevel { @@ -136,8 +130,11 @@ func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeSt } // NewDefaultEncoder initializes and returns a new DefaultEncoder instance. -func NewDefaultEncoder() *DefaultEncoder { - encoder := &DefaultEncoder{DateTimePrinter: services.NewDateTimePrinter(), ColorsPrinter: services.ColorsPrinter{}} +func NewDefaultEncoder( + printer services.Printer, + dateTimePrinter *services.DateTimePrinter, +) *DefaultEncoder { + encoder := &DefaultEncoder{DateTimePrinter: dateTimePrinter, printer: printer} encoder.encoderType = s.DefaultEncoderType encoder.bufferSyncPool = sync.Pool{ New: func() any { diff --git a/logs/encoders/default_test.go b/logs/encoders/default_test.go index 806a469..24f9ac3 100644 --- a/logs/encoders/default_test.go +++ b/logs/encoders/default_test.go @@ -6,6 +6,7 @@ import ( "os/exec" "testing" + "github.com/pho3b/tiny-logger/internal/services" "github.com/pho3b/tiny-logger/logs/colors" ll "github.com/pho3b/tiny-logger/logs/log_level" s "github.com/pho3b/tiny-logger/shared" @@ -14,7 +15,7 @@ import ( ) func TestLogDebug(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -26,7 +27,7 @@ func TestLogDebug(t *testing.T) { } func TestLogInfo(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -38,7 +39,7 @@ func TestLogInfo(t *testing.T) { } func TestLogWarn(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -50,7 +51,7 @@ func TestLogWarn(t *testing.T) { } func TestLogError(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureErrorOutput(func() { @@ -62,7 +63,7 @@ func TestLogError(t *testing.T) { } func TestLogFatalError(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} if os.Getenv("BE_CRASHER") == "1" { @@ -79,7 +80,7 @@ func TestLogFatalError(t *testing.T) { func TestFormatDateTimeString(t *testing.T) { b := bytes.NewBuffer([]byte{}) - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) encoder.addFormattedDateTime(b, "dateTest", "timeTest", "") assert.Contains(t, b.String(), "[") @@ -104,7 +105,7 @@ func TestFormatDateTimeString(t *testing.T) { } func TestShowLogLevel(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -125,7 +126,7 @@ func TestShowLogLevel(t *testing.T) { } func TestCheckColorsInTheOutput(t *testing.T) { - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test msg") }) @@ -145,7 +146,7 @@ func TestDefaultEncoder_Color(t *testing.T) { var output string testLog := "my testing Log" originalStdOut := os.Stdout - encoder := NewDefaultEncoder() + encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) lConfig := test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, diff --git a/logs/encoders/json.go b/logs/encoders/json.go index 82c8b0e..61e7ef1 100644 --- a/logs/encoders/json.go +++ b/logs/encoders/json.go @@ -14,6 +14,7 @@ type JSONEncoder struct { baseEncoder DateTimePrinter *services.DateTimePrinter jsonMarshaler services.JsonMarshaler + printer services.Printer } // Log formats and prints a log message to the given output type. @@ -40,7 +41,7 @@ func (j *JSONEncoder) Log( ) msgBuffer.WriteByte('\n') - j.printLog(outType, msgBuffer, logger.GetLogFile()) + j.printer.PrintLog(outType, msgBuffer, logger.GetLogFile()) j.putBuffer(msgBuffer) } @@ -65,17 +66,11 @@ func (j *JSONEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args msgBuffer.WriteString(c.Reset.String()) msgBuffer.WriteByte('\n') - j.printLog(s.StdOutput, msgBuffer, logger.GetLogFile()) + j.printer.PrintLog(s.StdOutput, msgBuffer, logger.GetLogFile()) j.putBuffer(msgBuffer) } } -// SetDateTimeFormat updates the date and time format used by the encoder's DateTimePrinter. -// This method triggers an immediate update of the cached date and time strings to match the new format. -func (j *JSONEncoder) SetDateTimeFormat(format s.DateTimeFormat) { - j.DateTimePrinter.UpdateDateTimeFormat(format) -} - // composeMsgInto formats and writes the given 'msg' into the given buffer. func (j *JSONEncoder) composeMsgInto( buf *bytes.Buffer, @@ -110,8 +105,12 @@ func (j *JSONEncoder) composeMsgInto( } // NewJSONEncoder initializes and returns a new JSONEncoder instance. -func NewJSONEncoder() *JSONEncoder { - encoder := &JSONEncoder{DateTimePrinter: services.NewDateTimePrinter(), jsonMarshaler: services.JsonMarshaler{}} +func NewJSONEncoder( + printer services.Printer, + jsonMarshaler services.JsonMarshaler, + dateTimePrinter *services.DateTimePrinter, +) *JSONEncoder { + encoder := &JSONEncoder{DateTimePrinter: dateTimePrinter, jsonMarshaler: jsonMarshaler, printer: printer} encoder.encoderType = s.JsonEncoderType encoder.bufferSyncPool = sync.Pool{ New: func() any { diff --git a/logs/encoders/json_test.go b/logs/encoders/json_test.go index f621708..f5600f2 100644 --- a/logs/encoders/json_test.go +++ b/logs/encoders/json_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/pho3b/tiny-logger/internal/services" "github.com/pho3b/tiny-logger/logs/colors" ll "github.com/pho3b/tiny-logger/logs/log_level" "github.com/pho3b/tiny-logger/shared" @@ -24,7 +25,7 @@ func decodeLogEntry(t *testing.T, logOutput string) shared.JsonLog { } func TestJSONEncoder_LogDebug(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -37,7 +38,7 @@ func TestJSONEncoder_LogDebug(t *testing.T) { } func TestJSONEncoder_LogInfo(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -50,7 +51,7 @@ func TestJSONEncoder_LogInfo(t *testing.T) { } func TestJSONEncoder_LogInfoWithExtras(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -75,7 +76,7 @@ func TestJSONEncoder_LogInfoWithExtras(t *testing.T) { } func TestJSONEncoder_LogWarn(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -88,7 +89,7 @@ func TestJSONEncoder_LogWarn(t *testing.T) { } func TestJSONEncoder_LogError(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureErrorOutput(func() { @@ -101,7 +102,7 @@ func TestJSONEncoder_LogError(t *testing.T) { } func TestJSONEncoder_LogFatalError(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} if os.Getenv("BE_CRASHER") == "1" { @@ -117,7 +118,7 @@ func TestJSONEncoder_LogFatalError(t *testing.T) { } func TestJSONEncoder_DateTime(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") @@ -153,7 +154,7 @@ func TestJSONEncoder_DateTime(t *testing.T) { } func TestJSONEncoder_ExtraMessages(t *testing.T) { - jsonEncoder := NewJSONEncoder() + jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) lConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: false, ShowLogLevel: false} output := captureOutput(func() { @@ -183,7 +184,7 @@ func TestJSONEncoder_ExtraMessages(t *testing.T) { } func TestJSONEncoder_ShowLogLevelLt(t *testing.T) { - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -209,7 +210,7 @@ func TestJSONEncoder_Color(t *testing.T) { testLog := "my testing Log" originalStdOut := os.Stdout - encoder := NewJSONEncoder() + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) lConfig := test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, @@ -248,7 +249,7 @@ func TestJSONEncoder_ValidJSONOutput(t *testing.T) { originalStdOut := os.Stdout testLog := "my testing Log" - jsonEncoder := NewJSONEncoder() + jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) lConfig := &test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, diff --git a/logs/encoders/yaml.go b/logs/encoders/yaml.go index f8d224b..928936d 100644 --- a/logs/encoders/yaml.go +++ b/logs/encoders/yaml.go @@ -14,6 +14,7 @@ type YAMLEncoder struct { baseEncoder DateTimePrinter *services.DateTimePrinter yamlMarshaler services.YamlMarshaler + printer services.Printer } // Log formats and prints a log message to the given output type. @@ -40,7 +41,7 @@ func (y *YAMLEncoder) Log( ) msgBuffer.WriteByte('\n') - y.printLog(outType, msgBuffer, logger.GetLogFile()) + y.printer.PrintLog(outType, msgBuffer, logger.GetLogFile()) y.putBuffer(msgBuffer) } @@ -65,17 +66,11 @@ func (y *YAMLEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args msgBuffer.WriteString(c.Reset.String()) msgBuffer.WriteByte('\n') - y.printLog(s.StdOutput, msgBuffer, logger.GetLogFile()) + y.printer.PrintLog(s.StdOutput, msgBuffer, logger.GetLogFile()) y.putBuffer(msgBuffer) } } -// SetDateTimeFormat updates the date and time format used by the encoder's DateTimePrinter. -// This method triggers an immediate update of the cached date and time strings to match the new format. -func (y *YAMLEncoder) SetDateTimeFormat(format s.DateTimeFormat) { - y.DateTimePrinter.UpdateDateTimeFormat(format) -} - // composeMsgInto formats and writes the given 'msg' into the given buffer. func (y *YAMLEncoder) composeMsgInto( buf *bytes.Buffer, @@ -110,8 +105,12 @@ func (y *YAMLEncoder) composeMsgInto( } // NewYAMLEncoder initializes and returns a new YAMLEncoder instance. -func NewYAMLEncoder() *YAMLEncoder { - encoder := &YAMLEncoder{DateTimePrinter: services.NewDateTimePrinter(), yamlMarshaler: services.NewYamlMarshaler()} +func NewYAMLEncoder( + printer services.Printer, + yamlMarshaler services.YamlMarshaler, + dateTimePrinter *services.DateTimePrinter, +) *YAMLEncoder { + encoder := &YAMLEncoder{DateTimePrinter: dateTimePrinter, yamlMarshaler: yamlMarshaler, printer: printer} encoder.encoderType = s.YamlEncoderType encoder.bufferSyncPool = sync.Pool{ New: func() any { diff --git a/logs/encoders/yaml_test.go b/logs/encoders/yaml_test.go index d1141fb..d959282 100644 --- a/logs/encoders/yaml_test.go +++ b/logs/encoders/yaml_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/pho3b/tiny-logger/internal/services" "github.com/pho3b/tiny-logger/logs/colors" ll "github.com/pho3b/tiny-logger/logs/log_level" "github.com/pho3b/tiny-logger/shared" @@ -23,7 +24,7 @@ func decodeYamlLogEntry(t *testing.T, logOutput string) shared.YamlLog { } func TestYAMLEncoder_LogDebug(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -36,7 +37,7 @@ func TestYAMLEncoder_LogDebug(t *testing.T) { } func TestYAMLEncoder_LogInfo(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -49,7 +50,7 @@ func TestYAMLEncoder_LogInfo(t *testing.T) { } func TestYAMLEncoder_LogInfoWithExtras(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -74,7 +75,7 @@ func TestYAMLEncoder_LogInfoWithExtras(t *testing.T) { } func TestYAMLEncoder_LogWarn(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -87,7 +88,7 @@ func TestYAMLEncoder_LogWarn(t *testing.T) { } func TestYAMLEncoder_LogError(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureErrorOutput(func() { @@ -100,7 +101,7 @@ func TestYAMLEncoder_LogError(t *testing.T) { } func TestYAMLEncoder_LogFatalError(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} if os.Getenv("BE_CRASHER") == "1" { @@ -116,7 +117,7 @@ func TestYAMLEncoder_LogFatalError(t *testing.T) { } func TestYAMLEncoder_DateTime(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") @@ -152,7 +153,7 @@ func TestYAMLEncoder_DateTime(t *testing.T) { } func TestYAMLEncoder_ExtraMessages(t *testing.T) { - yamlEncoder := NewYAMLEncoder() + yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) lConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: false, ShowLogLevel: false} output := captureOutput(func() { @@ -182,7 +183,7 @@ func TestYAMLEncoder_ExtraMessages(t *testing.T) { } func TestYAMLEncoder_ShowLogLevelLt(t *testing.T) { - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := captureOutput(func() { @@ -207,7 +208,7 @@ func TestYAMLEncoder_Color(t *testing.T) { var output string testLog := "my testing Log" originalStdOut := os.Stdout - encoder := NewYAMLEncoder() + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) lConfig := test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, @@ -246,7 +247,7 @@ func TestYAMLEncoder_ValidYAMLOutput(t *testing.T) { originalStdOut := os.Stdout testLog := "my testing Log" - yamlEncoder := NewYAMLEncoder() + yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) lConfig := &test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, diff --git a/logs/logger.go b/logs/logger.go index 916be5b..14a26ba 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -3,6 +3,7 @@ package logs import ( "os" + "github.com/pho3b/tiny-logger/internal/services" "github.com/pho3b/tiny-logger/logs/colors" "github.com/pho3b/tiny-logger/logs/encoders" ll "github.com/pho3b/tiny-logger/logs/log_level" @@ -10,14 +11,15 @@ import ( ) type Logger struct { - dateEnabled bool - timeEnabled bool - colorsEnabled bool - showLogLevel bool - encoder s.EncoderInterface - logLvl ll.LogLevel - outFile *os.File - dateTimeFormat s.DateTimeFormat + dateEnabled bool + timeEnabled bool + colorsEnabled bool + showLogLevel bool + encoder s.EncoderInterface + logLvl ll.LogLevel + outFile *os.File + dateTimeFormat s.DateTimeFormat + dateTimePrinter *services.DateTimePrinter } // Debug logs a debug-level message if the logger's log level allows it. @@ -152,14 +154,13 @@ func (l *Logger) GetEncoderType() s.EncoderType { func (l *Logger) SetEncoder(encoderType s.EncoderType) *Logger { switch encoderType { case s.DefaultEncoderType: - l.encoder = encoders.NewDefaultEncoder() + l.encoder = encoders.NewDefaultEncoder(services.NewPrinter(), l.dateTimePrinter) case s.JsonEncoderType: - l.encoder = encoders.NewJSONEncoder() + l.encoder = encoders.NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), l.dateTimePrinter) case s.YamlEncoderType: - l.encoder = encoders.NewYAMLEncoder() + l.encoder = encoders.NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), l.dateTimePrinter) } - l.encoder.SetDateTimeFormat(l.dateTimeFormat) return l } @@ -204,7 +205,7 @@ func (l *Logger) GetDateTimeFormat() s.DateTimeFormat { // SetDateTimeFormat sets the DateTimeFormat of the logger. func (l *Logger) SetDateTimeFormat(format s.DateTimeFormat) *Logger { l.dateTimeFormat = format - l.encoder.SetDateTimeFormat(l.dateTimeFormat) + l.dateTimePrinter.UpdateDateTimeFormat(format) return l } @@ -233,6 +234,7 @@ func (l *Logger) checkOutFile(outType s.OutputType) s.OutputType { func NewLogger() *Logger { logger := &Logger{showLogLevel: true, dateTimeFormat: s.IT} logger.SetLogLvlEnvVariable(ll.DefaultEnvLogLvlVar) + logger.dateTimePrinter = services.NewDateTimePrinter() logger.SetEncoder(s.DefaultEncoderType) return logger diff --git a/shared/interfaces.go b/shared/interfaces.go index 06d8853..eae90c6 100644 --- a/shared/interfaces.go +++ b/shared/interfaces.go @@ -30,5 +30,4 @@ type EncoderInterface interface { Log(logger LoggerConfigsInterface, lvl log_level.LogLvlName, outType OutputType, args ...any) Color(lConfigs LoggerConfigsInterface, color colors.Color, args ...any) GetType() EncoderType - SetDateTimeFormat(format DateTimeFormat) } From 2041568a1110e57ee44077a5ab982fcf4422acb6 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Mon, 29 Dec 2025 20:21:21 +0100 Subject: [PATCH 02/10] docs: updates method comment --- internal/services/datetime_printer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/services/datetime_printer.go b/internal/services/datetime_printer.go index 1afd661..7bb9f1f 100644 --- a/internal/services/datetime_printer.go +++ b/internal/services/datetime_printer.go @@ -84,8 +84,9 @@ func (d *DateTimePrinter) UpdateDateTimeFormat(format s.DateTimeFormat) { d.currentTime.Store(now.Format(timeFormat[format])) } -// updateCurrentDateEveryDay synchronizes with the system clock and updates the DateTimePrinter's -// currentDate property every 10 minutes. +// updateCurrentDate periodically refreshes d.currentDate based on the latest time and +// the current date format. +// Updates the stored date every 10 minutes. func (d *DateTimePrinter) updateCurrentDate() { for { now := d.timeNow() From dc9c445dfbcd4be7b14ee933af70cf0509a86f47 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Mon, 29 Dec 2025 20:34:55 +0100 Subject: [PATCH 03/10] chore: removes unneded Grow call and resuses same printer instance --- logs/encoders/default.go | 1 - logs/logger.go | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/logs/encoders/default.go b/logs/encoders/default.go index be960dd..9dfad83 100644 --- a/logs/encoders/default.go +++ b/logs/encoders/default.go @@ -111,7 +111,6 @@ func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeSt return } - buf.Grow(averageWordLen) buf.WriteByte('[') if dateTimeStr != "" { diff --git a/logs/logger.go b/logs/logger.go index 14a26ba..f5b196a 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -19,6 +19,7 @@ type Logger struct { logLvl ll.LogLevel outFile *os.File dateTimeFormat s.DateTimeFormat + printer services.Printer dateTimePrinter *services.DateTimePrinter } @@ -154,11 +155,11 @@ func (l *Logger) GetEncoderType() s.EncoderType { func (l *Logger) SetEncoder(encoderType s.EncoderType) *Logger { switch encoderType { case s.DefaultEncoderType: - l.encoder = encoders.NewDefaultEncoder(services.NewPrinter(), l.dateTimePrinter) + l.encoder = encoders.NewDefaultEncoder(l.printer, l.dateTimePrinter) case s.JsonEncoderType: - l.encoder = encoders.NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), l.dateTimePrinter) + l.encoder = encoders.NewJSONEncoder(l.printer, services.NewJsonMarshaler(), l.dateTimePrinter) case s.YamlEncoderType: - l.encoder = encoders.NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), l.dateTimePrinter) + l.encoder = encoders.NewYAMLEncoder(l.printer, services.NewYamlMarshaler(), l.dateTimePrinter) } return l @@ -234,6 +235,7 @@ func (l *Logger) checkOutFile(outType s.OutputType) s.OutputType { func NewLogger() *Logger { logger := &Logger{showLogLevel: true, dateTimeFormat: s.IT} logger.SetLogLvlEnvVariable(ll.DefaultEnvLogLvlVar) + logger.printer = services.NewPrinter() logger.dateTimePrinter = services.NewDateTimePrinter() logger.SetEncoder(s.DefaultEncoderType) From 48ff28ad887ae7ee5c2d2f4ee8343de75bf998b2 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Mon, 29 Dec 2025 20:53:12 +0100 Subject: [PATCH 04/10] perf: reduces by 1 allocation moving the datetime string concatenation --- internal/services/datetime_printer.go | 8 ++------ internal/services/json_marshaler.go | 9 +++++---- internal/services/yaml_marshaler.go | 9 +++++---- logs/encoders/default.go | 23 +++++++++-------------- logs/encoders/json.go | 3 +-- logs/encoders/yaml.go | 13 ++++++------- 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/internal/services/datetime_printer.go b/internal/services/datetime_printer.go index 7bb9f1f..c1a8481 100644 --- a/internal/services/datetime_printer.go +++ b/internal/services/datetime_printer.go @@ -32,8 +32,8 @@ type DateTimePrinter struct { unixOnce sync.Once } -// RetrieveDateTime returns the current date, time, and combined/unix string based on the configuration. -// Returns: (dateString, timeString, combinedOrUnixString). +// RetrieveDateTime returns the current date, time, and unix string based on the configuration. +// Returns: (dateString, timeString, unixTimeStamp). // If the format is UnixTimestamp, the timestamp is returned as the third value, ignoring the boolean flags. // Otherwise, 'addDate' and 'addTime' control which components are generated. func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, string, string) { @@ -67,10 +67,6 @@ func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, strin timeRes = d.currentTime.Load().(string) } - if addDate && addTime { - return "", "", dateRes + " " + timeRes - } - return dateRes, timeRes, "" } diff --git a/internal/services/json_marshaler.go b/internal/services/json_marshaler.go index 9f4bdea..cf731d7 100644 --- a/internal/services/json_marshaler.go +++ b/internal/services/json_marshaler.go @@ -31,7 +31,7 @@ func (j *JsonMarshaler) MarshalInto(buf *bytes.Buffer, logEntry JsonLogEntry) { buf.Grow(jsonCharOverhead + (averageExtraLen * extrasLen)) buf.WriteByte('{') - j.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.DateTime, logEntry.DateTimeFormat) + j.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.DateTimeFormat) buf.WriteString("\"msg\":\"") buf.WriteString(logEntry.Message) @@ -114,7 +114,6 @@ func (j *JsonMarshaler) writeLogEntryProperties( level string, date string, time string, - dateTime string, dateTimeFormat s.DateTimeFormat, ) { if level != "" { @@ -124,14 +123,16 @@ func (j *JsonMarshaler) writeLogEntryProperties( buf.WriteByte(',') } - if dateTime != "" || (date != "" && time != "") { + if date != "" && time != "" { if dateTimeFormat == s.UnixTimestamp { buf.WriteString("\"ts\":\"") } else { buf.WriteString("\"datetime\":\"") } - buf.WriteString(dateTime) + buf.WriteString(date) + buf.WriteByte(' ') + buf.WriteString(time) buf.WriteByte('"') buf.WriteByte(',') diff --git a/internal/services/yaml_marshaler.go b/internal/services/yaml_marshaler.go index 8fc6ddb..6f8fb11 100644 --- a/internal/services/yaml_marshaler.go +++ b/internal/services/yaml_marshaler.go @@ -31,7 +31,7 @@ func (y *YamlMarshaler) MarshalInto(buf *bytes.Buffer, logEntry YamlLogEntry) { extrasLen := len(logEntry.Extras) buf.Grow(yamlCharOverhead + (averageExtraLen * extrasLen)) - y.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.DateTime, logEntry.DateTimeFormat) + y.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.DateTimeFormat) buf.WriteString("msg: ") buf.WriteString(logEntry.Message) @@ -109,7 +109,6 @@ func (y *YamlMarshaler) writeLogEntryProperties( level string, date string, time string, - dateTime string, dateTimeFormat s.DateTimeFormat, ) { if level != "" { @@ -118,14 +117,16 @@ func (y *YamlMarshaler) writeLogEntryProperties( buf.WriteByte('\n') } - if dateTime != "" || (date != "" && time != "") { + if date != "" && time != "" { if dateTimeFormat == s.UnixTimestamp { buf.WriteString("ts: ") } else { buf.WriteString("datetime: ") } - buf.WriteString(dateTime) + buf.WriteString(date) + buf.WriteByte(' ') + buf.WriteString(time) buf.WriteByte('\n') return diff --git a/logs/encoders/default.go b/logs/encoders/default.go index 9dfad83..0efcb22 100644 --- a/logs/encoders/default.go +++ b/logs/encoders/default.go @@ -90,8 +90,8 @@ func (d *DefaultEncoder) composeMsgInto( } if isDateOrTimeEnabled { - dateStr, timeStr, dateTimeStr := d.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) - d.addFormattedDateTime(buf, dateStr, timeStr, dateTimeStr) + dateStr, timeStr, _ := d.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) + d.addFormattedDateTime(buf, dateStr, timeStr) } if showLogLevel || isDateOrTimeEnabled { @@ -106,25 +106,20 @@ func (d *DefaultEncoder) composeMsgInto( // addFormattedDateTime correctly formats the dateTime string, adding and removing square brackets // and white spaces as needed. // While formatting, it adds the dateTime string to the given buffer. -func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeStr, dateTimeStr string) { - if dateStr == "" && timeStr == "" && dateTimeStr == "" { +func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeStr string) { + if dateStr == "" && timeStr == "" { return } buf.WriteByte('[') + buf.WriteString(dateStr) - if dateTimeStr != "" { - buf.WriteString(dateTimeStr) - } else { - buf.WriteString(dateStr) - - if dateStr != "" && timeStr != "" { - buf.WriteByte(' ') - } - - buf.WriteString(timeStr) + if dateStr != "" && timeStr != "" { + buf.WriteByte(' ') } + buf.WriteString(timeStr) + buf.WriteByte(']') } diff --git a/logs/encoders/json.go b/logs/encoders/json.go index 61e7ef1..aa1afa3 100644 --- a/logs/encoders/json.go +++ b/logs/encoders/json.go @@ -84,7 +84,7 @@ func (j *JSONEncoder) composeMsgInto( extras ...any, ) { buf.Grow((averageWordLen * len(extras)) + len(msg) + 60) - dateStr, timeStr, dateTimeStr := j.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) + dateStr, timeStr, _ := j.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) if !showLogLevel { logLevel = "" @@ -95,7 +95,6 @@ func (j *JSONEncoder) composeMsgInto( services.JsonLogEntry{ Level: logLevel.String(), Date: dateStr, - DateTime: dateTimeStr, Time: timeStr, DateTimeFormat: dateTimeFormat, Message: msg, diff --git a/logs/encoders/yaml.go b/logs/encoders/yaml.go index 928936d..881a9e6 100644 --- a/logs/encoders/yaml.go +++ b/logs/encoders/yaml.go @@ -93,13 +93,12 @@ func (y *YAMLEncoder) composeMsgInto( yamlMarshaler.MarshalInto( buf, services.YamlLogEntry{ - Level: logLevel.String(), - Date: date, - Time: time, - DateTime: dateTime, - DateTimeFormat: dateTimeFormat, - Message: msg, - Extras: extras, + Level: logLevel.String(), + Date: date, + Time: time, + DateTime: dateTime, + Message: msg, + Extras: extras, }, ) } From 491c72f2d1a537838e61161f88126d6e9b8c877f Mon Sep 17 00:00:00 2001 From: arinaldi Date: Tue, 30 Dec 2025 00:45:59 +0100 Subject: [PATCH 05/10] test: updates all unit tests --- README.md | 12 ++-- internal/services/datetime_printer_test.go | 30 +++++----- internal/services/json_marshaler.go | 35 +++++------ internal/services/json_mashaler_test.go | 42 ++++++------- internal/services/printer_test.go | 68 ++++++++++++++++++++++ internal/services/yaml_marshaler.go | 34 +++++------ internal/services/yaml_marshaler_test.go | 21 +++---- logs/encoders/base_test.go | 35 ----------- logs/encoders/default.go | 5 +- logs/encoders/default_test.go | 36 ++++++------ logs/encoders/json.go | 17 +++--- logs/encoders/json_test.go | 42 ++++++------- logs/encoders/yaml.go | 17 +++--- logs/encoders/yaml_test.go | 42 ++++++------- logs/logger_test.go | 59 +++++-------------- shared/models.go | 4 +- test/functions.go | 40 +++++++++++++ 17 files changed, 283 insertions(+), 256 deletions(-) create mode 100644 test/functions.go diff --git a/README.md b/README.md index aa306be..aa11c90 100644 --- a/README.md +++ b/README.md @@ -71,12 +71,12 @@ terminal graphical visualization time. | Encoder | Configuration | ns/op | B/op | allocs/op | |---------------------|--------------------|-------|------|-----------| -| **Default Encoder** | All Properties OFF | 484.0 | 80 | 1 | -| | All Properties ON | 540.1 | 104 | 2 | -| **JSON Encoder** | All Properties OFF | 507.3 | 80 | 1 | -| | All Properties ON | 553.6 | 104 | 2 | -| **YAML Encoder** | All Properties OFF | 531.3 | 80 | 1 | -| | All Properties ON | 588.2 | 104 | 2 | +| **Default Encoder** | All Properties OFF | 490.3 | 80 | 1 | +| | All Properties ON | 511.2 | 104 | 1 | +| **JSON Encoder** | All Properties OFF | 513.3 | 80 | 1 | +| | All Properties ON | 536.5 | 104 | 1 | +| **YAML Encoder** | All Properties OFF | 535.3 | 80 | 1 | +| | All Properties ON | 557.1 | 104 | 1 | ## 🤝 Contributing diff --git a/internal/services/datetime_printer_test.go b/internal/services/datetime_printer_test.go index 55235e1..6a3dcff 100644 --- a/internal/services/datetime_printer_test.go +++ b/internal/services/datetime_printer_test.go @@ -18,26 +18,27 @@ func TestDateTimePrinter_PrintDateTime(t *testing.T) { t.Run("Return both date and time", func(t *testing.T) { dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true) - assert.Empty(t, dateRes) - assert.Empty(t, timeRes) - assert.Equal(t, "01/11/2023 15:30:45", dateTimeRes) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(true, true) + assert.Empty(t, unixTs) + assert.NotEmpty(t, dateRes) + assert.NotEmpty(t, timeRes) + assert.Equal(t, "01/11/2023 15:30:45", dateRes+" "+timeRes) }) t.Run("Return date only", func(t *testing.T) { dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, false) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(true, false) assert.Equal(t, "01/11/2023", dateRes) assert.Equal(t, "", timeRes) - assert.Equal(t, "", dateTimeRes) + assert.Equal(t, "", unixTs) }) t.Run("Return time only", func(t *testing.T) { dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, true) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(false, true) assert.Equal(t, "", dateRes) assert.Equal(t, "15:30:45", timeRes) - assert.Equal(t, "", dateTimeRes) + assert.Equal(t, "", unixTs) }) t.Run("Return empty string when both flags are false", func(t *testing.T) { @@ -94,10 +95,11 @@ func TestDateTimePrinter_Formats(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dateTimePrinter.UpdateDateTimeFormat(tt.format) - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true) - assert.Empty(t, dateRes) - assert.Empty(t, timeRes) - assert.Equal(t, tt.wantCombined, dateTimeRes) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(true, true) + assert.NotEmpty(t, dateRes) + assert.NotEmpty(t, timeRes) + assert.Empty(t, unixTs) + assert.Equal(t, tt.wantCombined, dateRes+" "+timeRes) // Also test individual components d, tRes, _ := dateTimePrinter.RetrieveDateTime(true, false) @@ -157,8 +159,8 @@ func TestDateTimePrinter_FullSecondUpdate(t *testing.T) { t.Run("Return both date and time", func(t *testing.T) { dateTimePrinter.UpdateDateTimeFormat(shared.IT) dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(true, true) - assert.Empty(t, dateRes) - assert.Empty(t, timeRes) + assert.NotEmpty(t, dateRes) + assert.NotEmpty(t, timeRes) prevTime := dateTimePrinter.currentTime.Load() time.Sleep(2 * time.Second) diff --git a/internal/services/json_marshaler.go b/internal/services/json_marshaler.go index cf731d7..04598ae 100644 --- a/internal/services/json_marshaler.go +++ b/internal/services/json_marshaler.go @@ -4,20 +4,17 @@ import ( "bytes" "fmt" "strconv" - - s "github.com/pho3b/tiny-logger/shared" ) // JsonLogEntry represents a structured log entry that can be marshaled to JSON format. // All fields except Message are optional and will be omitted if empty. type JsonLogEntry struct { - Level string `json:"level,omitempty"` - Date string `json:"date,omitempty"` - Time string `json:"time,omitempty"` - DateTime string `json:"datetime,omitempty"` - Message string `json:"msg"` - DateTimeFormat s.DateTimeFormat `json:"dateTimeFormat,omitempty"` - Extras []any `json:"extras,omitempty"` + Level string `json:"level,omitempty"` + Date string `json:"date,omitempty"` + Time string `json:"time,omitempty"` + Message string `json:"msg"` + UnixTS string `json:"unixTimestamp,omitempty"` + Extras []any `json:"extras,omitempty"` } // JsonMarshaler provides custom JSON marshaling functionality optimized for log entries. @@ -31,7 +28,7 @@ func (j *JsonMarshaler) MarshalInto(buf *bytes.Buffer, logEntry JsonLogEntry) { buf.Grow(jsonCharOverhead + (averageExtraLen * extrasLen)) buf.WriteByte('{') - j.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.DateTimeFormat) + j.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.UnixTS) buf.WriteString("\"msg\":\"") buf.WriteString(logEntry.Message) @@ -114,7 +111,7 @@ func (j *JsonMarshaler) writeLogEntryProperties( level string, date string, time string, - dateTimeFormat s.DateTimeFormat, + unixTS string, ) { if level != "" { buf.WriteString("\"level\":\"") @@ -123,13 +120,17 @@ func (j *JsonMarshaler) writeLogEntryProperties( buf.WriteByte(',') } - if date != "" && time != "" { - if dateTimeFormat == s.UnixTimestamp { - buf.WriteString("\"ts\":\"") - } else { - buf.WriteString("\"datetime\":\"") - } + if unixTS != "" { + buf.WriteString("\"ts\":\"") + buf.WriteString(unixTS) + buf.WriteByte('"') + buf.WriteByte(',') + return + } + + if date != "" && time != "" { + buf.WriteString("\"datetime\":\"") buf.WriteString(date) buf.WriteByte(' ') buf.WriteString(time) diff --git a/internal/services/json_mashaler_test.go b/internal/services/json_mashaler_test.go index f8bdcc5..ee66347 100644 --- a/internal/services/json_mashaler_test.go +++ b/internal/services/json_mashaler_test.go @@ -46,17 +46,16 @@ func TestJsonMarshaler_Marshal_AllFields(t *testing.T) { buf := &bytes.Buffer{} m := &JsonMarshaler{} entry := JsonLogEntry{ - Level: "info", - Date: "2025-06-14", - Time: "20:15:30", - DateTime: "2025-06-14T20:15:30", - Message: "all systems go", - DateTimeFormat: shared.IT, + Level: "info", + Date: "2025-06-14", + Time: "20:15:30", + Message: "all systems go", + UnixTS: "", } m.MarshalInto(buf, entry) got := buf.String() - want := `{"level":"info","datetime":"2025-06-14T20:15:30","msg":"all systems go"}` + want := `{"level":"info","datetime":"2025-06-14 20:15:30","msg":"all systems go"}` if got != want { t.Errorf("Marshal() = %q, want %q", got, want) } @@ -66,12 +65,11 @@ func TestJsonMarshaler_Marshal_WithExtras(t *testing.T) { buf := &bytes.Buffer{} m := &JsonMarshaler{} entry := JsonLogEntry{ - Level: "INFO", - Date: "", - Time: "", - DateTime: "20/06/2025 08:11:06", - Message: "all systems go", - Extras: []any{"bool", true, "int", 3, "float", 4.3, "arr", []int{1, 2, 3}, "rune", 'A', "string", "ciaooo", "null"}, + Level: "INFO", + Date: "20/06/2025", + Time: "08:11:06", + Message: "all systems go", + Extras: []any{"bool", true, "int", 3, "float", 4.3, "arr", []int{1, 2, 3}, "rune", 'A', "string", "ciaooo", "null"}, } m.MarshalInto(buf, entry) @@ -87,12 +85,11 @@ func TestJsonMarshaler_Unmarshal_Std_Marshal_Result(t *testing.T) { buf := &bytes.Buffer{} m := JsonMarshaler{} entry := JsonLogEntry{ - Level: "INFO", - Date: "", - Time: "", - DateTime: "20/06/2025 08:11:06", - Message: "all systems go", - Extras: []any{"bool", true, "int", 3, "float", 4.3, "arr", []int{1, 2, 3}, "rune", 'A', "string", "ciaooo", "null"}, + Level: "INFO", + Date: "20/06/2025", + Time: "08:11:06", + Message: "all systems go", + Extras: []any{"bool", true, "int", 3, "float", 4.3, "arr", []int{1, 2, 3}, "rune", 'A', "string", "ciaooo", "null"}, } m.MarshalInto(buf, entry) @@ -103,10 +100,9 @@ func TestJsonMarshaler_Marshal_UnixTimestamp(t *testing.T) { buf := &bytes.Buffer{} m := &JsonMarshaler{} entry := JsonLogEntry{ - Level: "info", - DateTime: "1700000000", - Message: "unix time", - DateTimeFormat: shared.UnixTimestamp, + Level: "info", + Message: "unix time", + UnixTS: "1700000000", } m.MarshalInto(buf, entry) diff --git a/internal/services/printer_test.go b/internal/services/printer_test.go index cd6c8f8..0c6a2a6 100644 --- a/internal/services/printer_test.go +++ b/internal/services/printer_test.go @@ -1,14 +1,82 @@ package services import ( + "bytes" + "os" "testing" "github.com/pho3b/tiny-logger/logs/log_level" + s "github.com/pho3b/tiny-logger/shared" + "github.com/pho3b/tiny-logger/test" c "github.com/pho3b/tiny-logger/logs/colors" "github.com/stretchr/testify/assert" ) +func TestPrinter_PrintLog_Stdout(t *testing.T) { + p := NewPrinter() + buf := bytes.NewBufferString("hello stdout") + + output := test.CaptureOutput(func() { + p.PrintLog(s.StdOutput, buf, nil) + }) + + assert.Equal(t, "hello stdout", output) +} + +func TestPrinter_PrintLog_Stderr(t *testing.T) { + p := NewPrinter() + buf := bytes.NewBufferString("hello stderr") + + output := test.CaptureErrorOutput(func() { + p.PrintLog(s.StdErrOutput, buf, nil) + }) + + assert.Equal(t, "hello stderr", output) +} + +func TestPrinter_PrintLog_FileOutput_WritesToFile(t *testing.T) { + p := NewPrinter() + buf := bytes.NewBufferString("file log") + + tmpFile, err := os.CreateTemp("", "printer-log-*") + assert.NoError(t, err) + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + p.PrintLog(s.FileOutput, buf, tmpFile) + + content, err := os.ReadFile(tmpFile.Name()) + assert.NoError(t, err) + assert.Equal(t, "file log", string(content)) +} + +func TestPrinter_PrintLog_FileOutput_NilFile_WritesErrorToStderr(t *testing.T) { + p := NewPrinter() + buf := bytes.NewBufferString("ignored") + + output := test.CaptureErrorOutput(func() { + p.PrintLog(s.FileOutput, buf, nil) + }) + + assert.Contains(t, output, "tiny-logger-err: given out file is nil") +} + +func TestPrinter_PrintLog_WriteError_LogsToStderr(t *testing.T) { + p := NewPrinter() + buf := bytes.NewBufferString("data") + + // Create an invalid *os.File to trigger a write error. + // The fd value here is intentionally bogus. + badFile := os.NewFile(^uintptr(0), "bad") + + output := test.CaptureErrorOutput(func() { + p.PrintLog(s.FileOutput, buf, badFile) + }) + + assert.Contains(t, output, "tiny-logger-err:") +} + func TestPrintColors_EnableColorsTrue(t *testing.T) { printer := Printer{} diff --git a/internal/services/yaml_marshaler.go b/internal/services/yaml_marshaler.go index 6f8fb11..f71c251 100644 --- a/internal/services/yaml_marshaler.go +++ b/internal/services/yaml_marshaler.go @@ -4,20 +4,17 @@ import ( "bytes" "fmt" "strconv" - - s "github.com/pho3b/tiny-logger/shared" ) // YamlLogEntry represents a structured log entry that can be marshaled to YAML format. // All fields except Message are optional and will be omitted if empty. type YamlLogEntry struct { - Level string `yaml:"level,omitempty"` - Date string `yaml:"date,omitempty"` - Time string `yaml:"time,omitempty"` - DateTime string `yaml:"datetime,omitempty"` - DateTimeFormat s.DateTimeFormat `yaml:"dateTimeFormat,omitempty"` - Message string `yaml:"msg"` - Extras []any `yaml:"extras,omitempty"` + Level string `yaml:"level,omitempty"` + Date string `yaml:"date,omitempty"` + Time string `yaml:"time,omitempty"` + UnixTS string `yaml:"unixTimestamp,omitempty"` + Message string `yaml:"msg"` + Extras []any `yaml:"extras,omitempty"` } // YamlMarshaler provides custom YAML marshaling functionality optimized for log entries. @@ -31,7 +28,7 @@ func (y *YamlMarshaler) MarshalInto(buf *bytes.Buffer, logEntry YamlLogEntry) { extrasLen := len(logEntry.Extras) buf.Grow(yamlCharOverhead + (averageExtraLen * extrasLen)) - y.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.DateTimeFormat) + y.writeLogEntryProperties(buf, logEntry.Level, logEntry.Date, logEntry.Time, logEntry.UnixTS) buf.WriteString("msg: ") buf.WriteString(logEntry.Message) @@ -109,7 +106,7 @@ func (y *YamlMarshaler) writeLogEntryProperties( level string, date string, time string, - dateTimeFormat s.DateTimeFormat, + unixTS string, ) { if level != "" { buf.WriteString("level: ") @@ -117,13 +114,16 @@ func (y *YamlMarshaler) writeLogEntryProperties( buf.WriteByte('\n') } - if date != "" && time != "" { - if dateTimeFormat == s.UnixTimestamp { - buf.WriteString("ts: ") - } else { - buf.WriteString("datetime: ") - } + if unixTS != "" { + buf.WriteString("ts: ") + buf.WriteString(unixTS) + buf.WriteByte('\n') + return + } + + if date != "" && time != "" { + buf.WriteString("datetime: ") buf.WriteString(date) buf.WriteByte(' ') buf.WriteString(time) diff --git a/internal/services/yaml_marshaler_test.go b/internal/services/yaml_marshaler_test.go index 3472686..3fac4f5 100644 --- a/internal/services/yaml_marshaler_test.go +++ b/internal/services/yaml_marshaler_test.go @@ -3,8 +3,6 @@ package services import ( "bytes" "testing" - - "github.com/pho3b/tiny-logger/shared" ) func TestYamlMarshaler_Marshal(t *testing.T) { @@ -31,13 +29,13 @@ func TestYamlMarshaler_Marshal(t *testing.T) { { name: "full log entry", entry: YamlLogEntry{ - Level: "DEBUG", - Date: "2024-03-21", - Time: "15:04:05", - DateTime: "2024-03-21T15:04:05", - Message: "full test message", + Level: "DEBUG", + Date: "2024-03-21", + Time: "15:04:05", + UnixTS: "", + Message: "full test message", }, - expected: "level: DEBUG\ndatetime: 2024-03-21T15:04:05\nmsg: full test message\n", + expected: "level: DEBUG\ndatetime: 2024-03-21 15:04:05\nmsg: full test message\n", }, { name: "with simple extras", @@ -166,10 +164,9 @@ func TestYamlMarshaler_Marshal_UnixTimestamp(t *testing.T) { buf := &bytes.Buffer{} m := NewYamlMarshaler() entry := YamlLogEntry{ - Level: "debug", - DateTime: "1715421234", // Unix timestamp string - Message: "unix timestamp test", - DateTimeFormat: shared.UnixTimestamp, + Level: "debug", + UnixTS: "1715421234", // Unix timestamp string + Message: "unix timestamp test", } // Pass pointer to m because MarshalInto is defined on *YamlMarshaler diff --git a/logs/encoders/base_test.go b/logs/encoders/base_test.go index 2f9d098..9b697de 100644 --- a/logs/encoders/base_test.go +++ b/logs/encoders/base_test.go @@ -3,7 +3,6 @@ package encoders import ( "bytes" "errors" - "os" "sync" "testing" @@ -112,37 +111,3 @@ func newBaseEncoder() *baseEncoder { return encoder } - -// captureOutput redirects os.Stdout to capture the output of the function f -func captureOutput(f func()) string { - r, w, _ := os.Pipe() - defer r.Close() - - origStdout := os.Stdout - os.Stdout = w - - f() - w.Close() - os.Stdout = origStdout - - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) - return buf.String() -} - -// captureErrorOutput redirects os.Stderr to capture the output of the function f -func captureErrorOutput(f func()) string { - r, w, _ := os.Pipe() - defer r.Close() - - origStderr := os.Stderr - os.Stderr = w - - f() - w.Close() - os.Stderr = origStderr - - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) - return buf.String() -} diff --git a/logs/encoders/default.go b/logs/encoders/default.go index 0efcb22..486d581 100644 --- a/logs/encoders/default.go +++ b/logs/encoders/default.go @@ -103,9 +103,7 @@ func (d *DefaultEncoder) composeMsgInto( d.castAndConcatenateInto(buf, args...) } -// addFormattedDateTime correctly formats the dateTime string, adding and removing square brackets -// and white spaces as needed. -// While formatting, it adds the dateTime string to the given buffer. +// addFormattedDateTime formats and adds the date and time strings enclosed in square brackets to the given buffer. func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeStr string) { if dateStr == "" && timeStr == "" { return @@ -119,7 +117,6 @@ func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeSt } buf.WriteString(timeStr) - buf.WriteByte(']') } diff --git a/logs/encoders/default_test.go b/logs/encoders/default_test.go index 24f9ac3..0ff718e 100644 --- a/logs/encoders/default_test.go +++ b/logs/encoders/default_test.go @@ -18,7 +18,7 @@ func TestLogDebug(t *testing.T) { encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test debug message") }) @@ -30,7 +30,7 @@ func TestLogInfo(t *testing.T) { encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, s.StdOutput, "Test info message") }) @@ -42,7 +42,7 @@ func TestLogWarn(t *testing.T) { encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, s.StdOutput, "Test warning message") }) @@ -54,7 +54,7 @@ func TestLogError(t *testing.T) { encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureErrorOutput(func() { + output := test.CaptureErrorOutput(func() { encoder.Log(loggerConfig, ll.ErrorLvlName, s.StdErrOutput, "Test error message") }) @@ -82,23 +82,23 @@ func TestFormatDateTimeString(t *testing.T) { b := bytes.NewBuffer([]byte{}) encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) - encoder.addFormattedDateTime(b, "dateTest", "timeTest", "") + encoder.addFormattedDateTime(b, "dateTest", "timeTest") assert.Contains(t, b.String(), "[") assert.Contains(t, b.String(), "]") assert.Contains(t, b.String(), " ") b.Reset() - encoder.addFormattedDateTime(b, "", "timeTest", "") + encoder.addFormattedDateTime(b, "", "timeTest") assert.Contains(t, b.String(), "[") assert.Contains(t, b.String(), "]") b.Reset() - encoder.addFormattedDateTime(b, "dateTest", "", "") + encoder.addFormattedDateTime(b, "dateTest", "") assert.Contains(t, b.String(), "[") assert.Contains(t, b.String(), "]") b.Reset() - encoder.addFormattedDateTime(b, "", "", "") + encoder.addFormattedDateTime(b, "", "") assert.NotContains(t, b.String(), "[") assert.NotContains(t, b.String(), "]") assert.NotContains(t, b.String(), " ") @@ -108,7 +108,7 @@ func TestShowLogLevel(t *testing.T) { encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test my-test message") }) @@ -117,7 +117,7 @@ func TestShowLogLevel(t *testing.T) { loggerConfig = &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ShowLogLevel: false} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test my-test message") }) @@ -129,16 +129,16 @@ func TestCheckColorsInTheOutput(t *testing.T) { encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test msg") }) + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test msg") }) assert.Contains(t, output, colors.Gray.String()) - output = captureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, s.StdOutput, "Test my-test message") }) + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, s.StdOutput, "Test my-test message") }) assert.Contains(t, output, colors.Cyan.String()) - output = captureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, s.StdOutput, "Test my-test message") }) + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, s.StdOutput, "Test my-test message") }) assert.Contains(t, output, colors.Yellow.String()) - output = captureErrorOutput(func() { encoder.Log(loggerConfig, ll.ErrorLvlName, s.StdErrOutput, "Test my-test message") }) + output = test.CaptureErrorOutput(func() { encoder.Log(loggerConfig, ll.ErrorLvlName, s.StdErrOutput, "Test my-test message") }) assert.Contains(t, output, colors.Red.String()) } @@ -154,16 +154,16 @@ func TestDefaultEncoder_Color(t *testing.T) { ShowLogLevel: false, } - output = captureOutput(func() { encoder.Color(&lConfig, colors.Magenta, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Magenta, testLog) }) assert.Contains(t, output, colors.Magenta.String()+testLog) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Cyan, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Cyan, testLog) }) assert.Contains(t, output, colors.Cyan.String()+testLog+colors.Reset.String()) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Gray, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Gray, testLog) }) assert.Contains(t, output, colors.Gray.String()+testLog+colors.Reset.String()) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Blue, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Blue, testLog) }) assert.Contains(t, output, colors.Blue.String()+testLog+colors.Reset.String()) os.Stdout = originalStdOut diff --git a/logs/encoders/json.go b/logs/encoders/json.go index aa1afa3..05bd30e 100644 --- a/logs/encoders/json.go +++ b/logs/encoders/json.go @@ -35,7 +35,6 @@ func (j *JSONEncoder) Log( dEnabled, tEnabled, logger.GetShowLogLevel(), - logger.GetDateTimeFormat(), j.castToString(args[0]), args[1:]..., ) @@ -59,7 +58,6 @@ func (j *JSONEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args dEnabled, tEnabled, false, - logger.GetDateTimeFormat(), j.castToString(args[0]), args[1:]..., ) @@ -79,12 +77,11 @@ func (j *JSONEncoder) composeMsgInto( dateEnabled bool, timeEnabled bool, showLogLevel bool, - dateTimeFormat s.DateTimeFormat, msg string, extras ...any, ) { buf.Grow((averageWordLen * len(extras)) + len(msg) + 60) - dateStr, timeStr, _ := j.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) + dateStr, timeStr, unixTs := j.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) if !showLogLevel { logLevel = "" @@ -93,12 +90,12 @@ func (j *JSONEncoder) composeMsgInto( jsonMarshaler.MarshalInto( buf, services.JsonLogEntry{ - Level: logLevel.String(), - Date: dateStr, - Time: timeStr, - DateTimeFormat: dateTimeFormat, - Message: msg, - Extras: extras, + Level: logLevel.String(), + Date: dateStr, + Time: timeStr, + UnixTS: unixTs, + Message: msg, + Extras: extras, }, ) } diff --git a/logs/encoders/json_test.go b/logs/encoders/json_test.go index f5600f2..694a0a3 100644 --- a/logs/encoders/json_test.go +++ b/logs/encoders/json_test.go @@ -28,7 +28,7 @@ func TestJSONEncoder_LogDebug(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, shared.StdOutput, "Test debug message") }) @@ -41,7 +41,7 @@ func TestJSONEncoder_LogInfo(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, shared.StdOutput, "Test info message") }) @@ -54,7 +54,7 @@ func TestJSONEncoder_LogInfoWithExtras(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, shared.StdOutput, "Test info message") }) @@ -63,7 +63,7 @@ func TestJSONEncoder_LogInfoWithExtras(t *testing.T) { assert.Equal(t, "Test info message", entry.Message) assert.IsType(t, make(map[string]any), entry.Extras) - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, shared.StdOutput, "Test info message with extras", "Location", "Italy", "Weather", "sunny", "Mood") }) @@ -79,7 +79,7 @@ func TestJSONEncoder_LogWarn(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test warning message") }) @@ -92,7 +92,7 @@ func TestJSONEncoder_LogError(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureErrorOutput(func() { + output := test.CaptureErrorOutput(func() { encoder.Log(loggerConfig, ll.ErrorLvlName, shared.StdErrOutput, "Test error message") }) @@ -120,7 +120,7 @@ func TestJSONEncoder_LogFatalError(t *testing.T) { func TestJSONEncoder_DateTime(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") }) @@ -131,7 +131,7 @@ func TestJSONEncoder_DateTime(t *testing.T) { assert.NotEmpty(t, entry.Time) loggerConfig = &test.LoggerConfigMock{DateEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") }) @@ -142,7 +142,7 @@ func TestJSONEncoder_DateTime(t *testing.T) { assert.NotEmpty(t, entry.Date) loggerConfig = &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") }) @@ -157,7 +157,7 @@ func TestJSONEncoder_ExtraMessages(t *testing.T) { jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) lConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: false, ShowLogLevel: false} - output := captureOutput(func() { + output := test.CaptureOutput(func() { jsonEncoder.Log(lConfig, ll.InfoLvlName, shared.StdOutput, "test", "user", "alice", "ip", "192.168.1.1") }) entry := decodeLogEntry(t, output) @@ -165,14 +165,14 @@ func TestJSONEncoder_ExtraMessages(t *testing.T) { assert.NotNil(t, entry.Extras["ip"]) assert.Len(t, entry.Extras, 2) - output = captureOutput(func() { + output = test.CaptureOutput(func() { jsonEncoder.Log(lConfig, ll.InfoLvlName, shared.StdOutput, "test", "user", "alice", "ip") }) entry = decodeLogEntry(t, output) assert.Nil(t, entry.Extras["ip"]) assert.Len(t, entry.Extras, 2) - output = captureOutput(func() { + output = test.CaptureOutput(func() { jsonEncoder.Log(lConfig, ll.InfoLvlName, shared.StdOutput, "test", "user", "alice", "ip", "192.168.1.1", "city", "paris", "pass") }) entry = decodeLogEntry(t, output) @@ -187,7 +187,7 @@ func TestJSONEncoder_ShowLogLevelLt(t *testing.T) { encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, shared.StdOutput, "Test debug message") }) @@ -197,7 +197,7 @@ func TestJSONEncoder_ShowLogLevelLt(t *testing.T) { loggerConfig = &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: false} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, shared.StdOutput, "Test debug message") }) @@ -218,25 +218,25 @@ func TestJSONEncoder_Color(t *testing.T) { ShowLogLevel: false, } - output = captureOutput(func() { encoder.Color(&lConfig, colors.Magenta, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Magenta, testLog) }) assert.Contains(t, output, colors.Magenta.String()) assert.Contains(t, output, testLog) assert.NotContains(t, output, time.Now().Format("02/01/2006")) assert.Contains(t, output, colors.Reset.String()) lConfig.DateEnabled = true - output = captureOutput(func() { encoder.Color(&lConfig, colors.Cyan, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Cyan, testLog) }) assert.Contains(t, output, colors.Cyan.String()) assert.Contains(t, output, time.Now().Format("02/01/2006")) assert.Contains(t, output, testLog) assert.Contains(t, output, colors.Reset.String()) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Gray, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Gray, testLog) }) assert.Contains(t, output, colors.Gray.String()) assert.Contains(t, output, testLog) assert.Contains(t, output, colors.Reset.String()) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Blue, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Blue, testLog) }) assert.Contains(t, output, colors.Blue.String()) assert.Contains(t, output, testLog) assert.Contains(t, output, colors.Reset.String()) @@ -257,21 +257,21 @@ func TestJSONEncoder_ValidJSONOutput(t *testing.T) { ShowLogLevel: false, } - jsonMsg = captureOutput( + jsonMsg = test.CaptureOutput( func() { jsonEncoder.Log(lConfig, ll.DebugLvlName, shared.StdOutput, testLog, "id", 3) }, ) assert.NoError(t, json.Unmarshal([]byte(jsonMsg), &shared.JsonLog{})) - jsonMsg = captureOutput( + jsonMsg = test.CaptureOutput( func() { jsonEncoder.Log(lConfig, ll.DebugLvlName, shared.StdOutput, testLog, "id", 3, 34, []string{"test", "test2"}) }, ) assert.NoError(t, json.Unmarshal([]byte(jsonMsg), &shared.JsonLog{})) - jsonMsg = captureOutput(func() { + jsonMsg = test.CaptureOutput(func() { jsonEncoder.Log(lConfig, ll.DebugLvlName, shared.StdOutput, testLog, "id", 3, 34, []string{"test", "test2"}, []string{"k", "k2"}, 2.3, 'f', 'A') }) assert.NoError(t, json.Unmarshal([]byte(jsonMsg), &shared.JsonLog{})) diff --git a/logs/encoders/yaml.go b/logs/encoders/yaml.go index 881a9e6..efcf05f 100644 --- a/logs/encoders/yaml.go +++ b/logs/encoders/yaml.go @@ -35,7 +35,6 @@ func (y *YAMLEncoder) Log( dEnabled, tEnabled, logger.GetShowLogLevel(), - logger.GetDateTimeFormat(), y.castToString(args[0]), args[1:]..., ) @@ -59,7 +58,6 @@ func (y *YAMLEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args dEnabled, tEnabled, false, - logger.GetDateTimeFormat(), y.castToString(args[0]), args[1:]..., ) @@ -79,12 +77,11 @@ func (y *YAMLEncoder) composeMsgInto( dateEnabled bool, timeEnabled bool, showLogLevel bool, - dateTimeFormat s.DateTimeFormat, msg string, extras ...any, ) { buf.Grow((averageWordLen * len(extras)) + len(msg) + 60) - date, time, dateTime := y.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) + date, time, unixTs := y.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) if !showLogLevel { logLevel = "" @@ -93,12 +90,12 @@ func (y *YAMLEncoder) composeMsgInto( yamlMarshaler.MarshalInto( buf, services.YamlLogEntry{ - Level: logLevel.String(), - Date: date, - Time: time, - DateTime: dateTime, - Message: msg, - Extras: extras, + Level: logLevel.String(), + Date: date, + Time: time, + UnixTS: unixTs, + Message: msg, + Extras: extras, }, ) } diff --git a/logs/encoders/yaml_test.go b/logs/encoders/yaml_test.go index d959282..5203959 100644 --- a/logs/encoders/yaml_test.go +++ b/logs/encoders/yaml_test.go @@ -27,7 +27,7 @@ func TestYAMLEncoder_LogDebug(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, shared.StdOutput, "Test debug message") }) @@ -40,7 +40,7 @@ func TestYAMLEncoder_LogInfo(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, shared.StdOutput, "Test info message") }) @@ -53,7 +53,7 @@ func TestYAMLEncoder_LogInfoWithExtras(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, shared.StdOutput, "Test info message") }) @@ -62,7 +62,7 @@ func TestYAMLEncoder_LogInfoWithExtras(t *testing.T) { assert.Equal(t, "Test info message", entry.Message) assert.IsType(t, make(map[string]any), entry.Extras) - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.InfoLvlName, shared.StdOutput, "Test info message with extras", "Location", "Italy", "Weather", "sunny", "Mood") }) @@ -78,7 +78,7 @@ func TestYAMLEncoder_LogWarn(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test warning message") }) @@ -91,7 +91,7 @@ func TestYAMLEncoder_LogError(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureErrorOutput(func() { + output := test.CaptureErrorOutput(func() { encoder.Log(loggerConfig, ll.ErrorLvlName, shared.StdErrOutput, "Test error message") }) @@ -119,7 +119,7 @@ func TestYAMLEncoder_LogFatalError(t *testing.T) { func TestYAMLEncoder_DateTime(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") }) @@ -130,7 +130,7 @@ func TestYAMLEncoder_DateTime(t *testing.T) { assert.NotEmpty(t, entry.Time) loggerConfig = &test.LoggerConfigMock{DateEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") }) @@ -141,7 +141,7 @@ func TestYAMLEncoder_DateTime(t *testing.T) { assert.NotEmpty(t, entry.Date) loggerConfig = &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") }) @@ -156,7 +156,7 @@ func TestYAMLEncoder_ExtraMessages(t *testing.T) { yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) lConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: false, ShowLogLevel: false} - output := captureOutput(func() { + output := test.CaptureOutput(func() { yamlEncoder.Log(lConfig, ll.InfoLvlName, shared.StdOutput, "test", "user", "alice", "ip", "192.168.1.1") }) entry := decodeYamlLogEntry(t, output) @@ -164,14 +164,14 @@ func TestYAMLEncoder_ExtraMessages(t *testing.T) { assert.NotNil(t, entry.Extras["ip"]) assert.Len(t, entry.Extras, 2) - output = captureOutput(func() { + output = test.CaptureOutput(func() { yamlEncoder.Log(lConfig, ll.InfoLvlName, shared.StdOutput, "test", "user", "alice", "ip") }) entry = decodeYamlLogEntry(t, output) assert.Nil(t, entry.Extras["ip"]) assert.Len(t, entry.Extras, 2) - output = captureOutput(func() { + output = test.CaptureOutput(func() { yamlEncoder.Log(lConfig, ll.InfoLvlName, shared.StdOutput, "test", "user", "alice", "ip", "192.168.1.1", "city", "paris", "pass") }) entry = decodeYamlLogEntry(t, output) @@ -186,7 +186,7 @@ func TestYAMLEncoder_ShowLogLevelLt(t *testing.T) { encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} - output := captureOutput(func() { + output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, shared.StdOutput, "Test debug message") }) @@ -196,7 +196,7 @@ func TestYAMLEncoder_ShowLogLevelLt(t *testing.T) { loggerConfig = &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: false} - output = captureOutput(func() { + output = test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, shared.StdOutput, "Test debug message") }) @@ -216,25 +216,25 @@ func TestYAMLEncoder_Color(t *testing.T) { ShowLogLevel: false, } - output = captureOutput(func() { encoder.Color(&lConfig, colors.Magenta, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Magenta, testLog) }) assert.Contains(t, output, colors.Magenta.String()) assert.Contains(t, output, testLog) assert.NotContains(t, output, time.Now().Format("02/01/2006")) assert.Contains(t, output, colors.Reset.String()) lConfig.DateEnabled = true - output = captureOutput(func() { encoder.Color(&lConfig, colors.Cyan, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Cyan, testLog) }) assert.Contains(t, output, colors.Cyan.String()) assert.Contains(t, output, time.Now().Format("02/01/2006")) assert.Contains(t, output, testLog) assert.Contains(t, output, colors.Reset.String()) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Gray, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Gray, testLog) }) assert.Contains(t, output, colors.Gray.String()) assert.Contains(t, output, testLog) assert.Contains(t, output, colors.Reset.String()) - output = captureOutput(func() { encoder.Color(&lConfig, colors.Blue, testLog) }) + output = test.CaptureOutput(func() { encoder.Color(&lConfig, colors.Blue, testLog) }) assert.Contains(t, output, colors.Blue.String()) assert.Contains(t, output, testLog) assert.Contains(t, output, colors.Reset.String()) @@ -255,21 +255,21 @@ func TestYAMLEncoder_ValidYAMLOutput(t *testing.T) { ShowLogLevel: false, } - yamlMsg = captureOutput( + yamlMsg = test.CaptureOutput( func() { yamlEncoder.Log(lConfig, ll.DebugLvlName, shared.StdOutput, testLog, "id", 3) }, ) assert.NoError(t, yaml.Unmarshal([]byte(yamlMsg), &shared.YamlLog{})) - yamlMsg = captureOutput( + yamlMsg = test.CaptureOutput( func() { yamlEncoder.Log(lConfig, ll.DebugLvlName, shared.StdOutput, testLog, "id", 3, 34, []string{"test", "test2"}) }, ) assert.NoError(t, yaml.Unmarshal([]byte(yamlMsg), &shared.YamlLog{})) - yamlMsg = captureOutput(func() { + yamlMsg = test.CaptureOutput(func() { yamlEncoder.Log(lConfig, ll.DebugLvlName, shared.StdOutput, testLog, "id", 3, 34, []string{"test", "test2"}, []string{"k", "k2"}, 2.3, 'f', 'A') }) assert.NoError(t, yaml.Unmarshal([]byte(yamlMsg), &shared.YamlLog{})) diff --git a/logs/logger_test.go b/logs/logger_test.go index d8d81db..6a95677 100644 --- a/logs/logger_test.go +++ b/logs/logger_test.go @@ -12,6 +12,7 @@ import ( "github.com/pho3b/tiny-logger/logs/colors" "github.com/pho3b/tiny-logger/logs/log_level" "github.com/pho3b/tiny-logger/shared" + "github.com/pho3b/tiny-logger/test" "github.com/stretchr/testify/assert" ) @@ -224,16 +225,16 @@ func TestLogger_Color(t *testing.T) { originalStdOut := os.Stdout logger := NewLogger() - output = captureOutput(func() { logger.Color(colors.Magenta, testLog) }) + output = test.CaptureOutput(func() { logger.Color(colors.Magenta, testLog) }) assert.Contains(t, output, colors.Magenta.String()+testLog) - output = captureOutput(func() { logger.Color(colors.Cyan, testLog) }) + output = test.CaptureOutput(func() { logger.Color(colors.Cyan, testLog) }) assert.Contains(t, output, colors.Cyan.String()+testLog+colors.Reset.String()) - output = captureOutput(func() { logger.Color(colors.Gray, testLog) }) + output = test.CaptureOutput(func() { logger.Color(colors.Gray, testLog) }) assert.Contains(t, output, colors.Gray.String()+testLog+colors.Reset.String()) - output = captureOutput(func() { logger.Color(colors.Blue, testLog) }) + output = test.CaptureOutput(func() { logger.Color(colors.Blue, testLog) }) assert.Contains(t, output, colors.Blue.String()+testLog+colors.Reset.String()) os.Stdout = originalStdOut @@ -244,13 +245,13 @@ func TestLogger_ShowLogLevel(t *testing.T) { ShowLogLevel(true). EnableColors(false) - output := captureOutput(func() { + output := test.CaptureOutput(func() { logger.Info("my testing log") }) assert.Contains(t, output, "INFO: my testing log") logger.ShowLogLevel(false) - output = captureOutput(func() { + output = test.CaptureOutput(func() { logger.Info("my testing log") }) assert.NotContains(t, output, "INFO: my testing log") @@ -275,14 +276,14 @@ func TestLogger_CorrectLogsFormattingDefaultEncoder(t *testing.T) { logger := NewLogger().SetEncoder(shared.DefaultEncoderType).AddDateTime(true).ShowLogLevel(true) re := regexp.MustCompile(`^([A-Z]+) \[(\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2})\]: (.+)\n?$`) - outMsg := captureOutput(func() { logger.Debug("testing log") }) + outMsg := test.CaptureOutput(func() { logger.Debug("testing log") }) matches := re.FindStringSubmatch(outMsg) assert.Equal(t, "DEBUG", matches[1]) assert.NotNil(t, matches[2]) assert.Equal(t, "testing log", matches[3]) - outMsg = captureOutput(func() { + outMsg = test.CaptureOutput(func() { logger.Warn("testing log") }) @@ -291,7 +292,7 @@ func TestLogger_CorrectLogsFormattingDefaultEncoder(t *testing.T) { assert.NotNil(t, matches[2]) assert.Equal(t, "testing log", matches[3]) - outMsg = captureErrorOutput(func() { + outMsg = test.CaptureErrorOutput(func() { logger.Error("testing log") }) @@ -300,7 +301,7 @@ func TestLogger_CorrectLogsFormattingDefaultEncoder(t *testing.T) { assert.NotNil(t, matches[2]) assert.Equal(t, "testing log", matches[3]) - outMsg = captureOutput(func() { + outMsg = test.CaptureOutput(func() { logger.Info("testing log") }) @@ -387,7 +388,7 @@ func TestLogger_SetLogFile_ExistingFile(t *testing.T) { func TestLogger_SetLogFile_Nil(t *testing.T) { logger := NewLogger() - warnOut := captureOutput(func() { logger.SetLogFile(nil) }) + warnOut := test.CaptureOutput(func() { logger.SetLogFile(nil) }) assert.Equal(t, "WARN: the given log file is nil, skipping logs redirection\n", warnOut) assert.Nil(t, logger.outFile) assert.Nil(t, logger.GetLogFile()) @@ -416,7 +417,7 @@ func TestLogger_CloseLogFile_NoFileSet(t *testing.T) { logger := NewLogger() // Test closing when no file is set - should log warning and not crash - output := captureOutput(func() { + output := test.CaptureOutput(func() { logger.CloseLogFile() }) @@ -472,40 +473,6 @@ func TestLogger_LogsRedirectedToFile(t *testing.T) { assert.Contains(t, contentStr, "error message") } -// captureOutput redirects os.Stdout to capture the output of the function f -func captureOutput(f func()) string { - r, w, _ := os.Pipe() - defer r.Close() - - origStdout := os.Stdout - os.Stdout = w - - f() - w.Close() - os.Stdout = origStdout - - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) - return buf.String() -} - -// captureErrorOutput redirects os.Stderr to capture the output of the function f -func captureErrorOutput(f func()) string { - r, w, _ := os.Pipe() - defer r.Close() - - origStderr := os.Stderr - os.Stderr = w - - f() - w.Close() - os.Stderr = origStderr - - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) - return buf.String() -} - func createMockOutFile(fileName string) *os.File { file, err := os.OpenFile(fmt.Sprintf("./%s", fileName), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { diff --git a/shared/models.go b/shared/models.go index 2bbf47f..2574430 100644 --- a/shared/models.go +++ b/shared/models.go @@ -1,6 +1,6 @@ package shared -// JsonLog represents the structure of a JSON log and can be used to marshal JSON logs. +// JsonLog represents the structure of a JSON log and can be used to Unmarshal JSON logEntries. type JsonLog struct { Level string `json:"level,omitempty"` Date string `json:"date,omitempty"` @@ -10,7 +10,7 @@ type JsonLog struct { Extras map[string]any `json:"extras,omitempty"` } -// YamlLog represents the structure of a YAML log and can be used to marshal YAML logs. +// YamlLog represents the structure of a YAML log and can be used to Unmarshal YAML log entries. type YamlLog struct { Level string `yaml:"level,omitempty"` Date string `yaml:"date,omitempty"` diff --git a/test/functions.go b/test/functions.go new file mode 100644 index 0000000..41969c4 --- /dev/null +++ b/test/functions.go @@ -0,0 +1,40 @@ +package test + +import ( + "bytes" + "os" +) + +// CaptureOutput redirects os.Stdout to capture the output of the function f +func CaptureOutput(f func()) string { + r, w, _ := os.Pipe() + defer r.Close() + + origStdout := os.Stdout + os.Stdout = w + + f() + w.Close() + os.Stdout = origStdout + + var buf bytes.Buffer + _, _ = buf.ReadFrom(r) + return buf.String() +} + +// CaptureErrorOutput redirects os.Stderr to capture the output of the function f +func CaptureErrorOutput(f func()) string { + r, w, _ := os.Pipe() + defer r.Close() + + origStderr := os.Stderr + os.Stderr = w + + f() + w.Close() + os.Stderr = origStderr + + var buf bytes.Buffer + _, _ = buf.ReadFrom(r) + return buf.String() +} From ece5a34d8ebf94bb548c7c231be92f2f8ffdb13e Mon Sep 17 00:00:00 2001 From: arinaldi Date: Tue, 30 Dec 2025 17:20:24 +0100 Subject: [PATCH 06/10] feat: removes log lvl check for the fataError logs --- logs/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/logger.go b/logs/logger.go index f5b196a..d5b9333 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -54,7 +54,7 @@ func (l *Logger) Error(args ...any) { // FatalError logs a fatal error message and terminates the application only if any given args is not NIl, // otherwise the method does nothing. func (l *Logger) FatalError(args ...any) { - if l.logLvl.Lvl >= ll.ErrorLvl && len(args) > 0 && !l.areAllNil(args...) { + if len(args) > 0 && !l.areAllNil(args...) { l.encoder.Log(l, ll.FatalErrorLvlName, l.checkOutFile(s.StdErrOutput), args...) os.Exit(1) } From 06eb9038de65f8229873aad12800e290a8288156 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Tue, 30 Dec 2025 17:40:51 +0100 Subject: [PATCH 07/10] perf: appends string to remaining buffer if any on cast --- internal/services/json_marshaler.go | 8 ++++---- internal/services/yaml_marshaler.go | 8 ++++---- logs/encoders/base.go | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/services/json_marshaler.go b/internal/services/json_marshaler.go index 04598ae..b0646a2 100644 --- a/internal/services/json_marshaler.go +++ b/internal/services/json_marshaler.go @@ -86,13 +86,13 @@ func (j *JsonMarshaler) writeValue(buf *bytes.Buffer, v any, isKey bool) { buf.WriteByte('"') } case int: - buf.WriteString(strconv.Itoa(val)) + buf.Write(strconv.AppendInt(buf.AvailableBuffer(), int64(val), 10)) case int64: - buf.WriteString(strconv.FormatInt(val, 10)) + buf.Write(strconv.AppendInt(buf.AvailableBuffer(), val, 10)) case float64: - buf.WriteString(strconv.FormatFloat(val, 'f', -1, 64)) + buf.Write(strconv.AppendFloat(buf.AvailableBuffer(), val, 'f', -1, 64)) case bool: - buf.WriteString(strconv.FormatBool(val)) + buf.Write(strconv.AppendBool(buf.AvailableBuffer(), val)) default: if isKey { buf.WriteString(fmt.Sprint(val)) diff --git a/internal/services/yaml_marshaler.go b/internal/services/yaml_marshaler.go index f71c251..8cc8ced 100644 --- a/internal/services/yaml_marshaler.go +++ b/internal/services/yaml_marshaler.go @@ -78,13 +78,13 @@ func (y *YamlMarshaler) writeStr(buf *bytes.Buffer, v any, isKey bool) { buf.WriteByte('"') } case int: - buf.WriteString(strconv.Itoa(val)) + buf.Write(strconv.AppendInt(buf.AvailableBuffer(), int64(val), 10)) case int64: - buf.WriteString(strconv.FormatInt(val, 10)) + buf.Write(strconv.AppendInt(buf.AvailableBuffer(), val, 10)) case float64: - buf.WriteString(strconv.FormatFloat(val, 'f', -1, 64)) + buf.Write(strconv.AppendFloat(buf.AvailableBuffer(), val, 'f', -1, 64)) case bool: - buf.WriteString(strconv.FormatBool(val)) + buf.Write(strconv.AppendBool(buf.AvailableBuffer(), val)) default: // Check if the string representation needs quotes str := fmt.Sprint(val) diff --git a/logs/encoders/base.go b/logs/encoders/base.go index e585af7..d673227 100644 --- a/logs/encoders/base.go +++ b/logs/encoders/base.go @@ -35,13 +35,13 @@ func (b *baseEncoder) castAndConcatenateInto(buf *bytes.Buffer, args ...any) { case rune: buf.WriteRune(v) case int: - buf.WriteString(strconv.Itoa(v)) + buf.Write(strconv.AppendInt(buf.AvailableBuffer(), int64(v), 10)) case int64: - buf.WriteString(strconv.FormatInt(v, 10)) + buf.Write(strconv.AppendInt(buf.AvailableBuffer(), v, 10)) case float64: - buf.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) + buf.Write(strconv.AppendFloat(buf.AvailableBuffer(), v, 'f', -1, 64)) case bool: - buf.WriteString(strconv.FormatBool(v)) + buf.Write(strconv.AppendBool(buf.AvailableBuffer(), v)) case fmt.Stringer: buf.WriteString(v.String()) case error: From b1b12893f0b1f88f25cc83bbf1b32dbef5898e8b Mon Sep 17 00:00:00 2001 From: arinaldi Date: Tue, 30 Dec 2025 18:19:51 +0100 Subject: [PATCH 08/10] perf: fixes and drastically improves logging performances by making the datetimeprinter a singleton --- internal/services/datetime_printer.go | 118 ++++++++++----------- internal/services/datetime_printer_test.go | 43 ++++---- logs/encoders/base_test.go | 6 +- logs/encoders/default.go | 17 ++- logs/encoders/default_test.go | 26 ++--- logs/encoders/json.go | 5 +- logs/encoders/json_test.go | 22 ++-- logs/encoders/yaml.go | 5 +- logs/encoders/yaml_test.go | 22 ++-- logs/logger.go | 3 +- 10 files changed, 137 insertions(+), 130 deletions(-) diff --git a/internal/services/datetime_printer.go b/internal/services/datetime_printer.go index c1a8481..204981c 100644 --- a/internal/services/datetime_printer.go +++ b/internal/services/datetime_printer.go @@ -21,94 +21,86 @@ var timeFormat = map[s.DateTimeFormat]string{ s.JP: "03:04:05 PM", } +var ( + dateTimePrinterInstance *DateTimePrinter + dateTimePrinterOnce sync.Once +) + type DateTimePrinter struct { - timeNow func() time.Time // Function to get current time, allows mocking for tests - currentFormat atomic.Value - currentDate atomic.Value - currentTime atomic.Value - currentUnix atomic.Value - dateOnce sync.Once - timeOnce sync.Once - unixOnce sync.Once + timeNow func() time.Time + // Arrays indexed by s.DateTimeFormat (int8) + // Assuming 0=IT, 1=JP, 2=US. UnixTimestamp is handled separately. + cachedDates [3]atomic.Value + cachedTimes [3]atomic.Value + currentUnix atomic.Value } -// RetrieveDateTime returns the current date, time, and unix string based on the configuration. -// Returns: (dateString, timeString, unixTimeStamp). -// If the format is UnixTimestamp, the timestamp is returned as the third value, ignoring the boolean flags. -// Otherwise, 'addDate' and 'addTime' control which components are generated. -func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, string, string) { - var dateRes, timeRes string - currentFmt := d.currentFormat.Load().(s.DateTimeFormat) - - if currentFmt == s.UnixTimestamp && (addDate || addTime) { - d.unixOnce.Do(func() { - d.currentUnix.Store(strconv.FormatInt(d.timeNow().Unix(), 10)) - go d.updateCurrentUnix() - }) - +// RetrieveDateTime now accepts the desired format +func (d *DateTimePrinter) RetrieveDateTime(fmt s.DateTimeFormat, addDate, addTime bool) (string, string, string) { + if fmt == s.UnixTimestamp { return "", "", d.currentUnix.Load().(string) } - if addDate { - d.dateOnce.Do(func() { - d.currentDate.Store(d.timeNow().Format(dateFormat[currentFmt])) - go d.updateCurrentDate() - }) + var dateRes, timeRes string - dateRes = d.currentDate.Load().(string) + if addDate { + dateRes = d.cachedDates[fmt].Load().(string) } if addTime { - d.timeOnce.Do(func() { - d.currentTime.Store(d.timeNow().Format(timeFormat[currentFmt])) - go d.updateCurrentTime() - }) - - timeRes = d.currentTime.Load().(string) + timeRes = d.cachedTimes[fmt].Load().(string) } return dateRes, timeRes, "" } -// UpdateDateTimeFormat updates the DateTimePrinter's currentFormat property and updates the currentDate and -// currentTime properties accordingly. -func (d *DateTimePrinter) UpdateDateTimeFormat(format s.DateTimeFormat) { +func (d *DateTimePrinter) init() { now := d.timeNow() + d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10)) + + for i := 0; i < 3; i++ { + fmt := s.DateTimeFormat(i) + d.cachedDates[i].Store(now.Format(dateFormat[fmt])) + d.cachedTimes[i].Store(now.Format(timeFormat[fmt])) + } - d.currentFormat.Store(format) - d.currentDate.Store(now.Format(dateFormat[format])) - d.currentTime.Store(now.Format(timeFormat[format])) + go d.loopUpdateDate() + go d.loopUpdateTime() + go d.loopUpdateUnix() } -// updateCurrentDate periodically refreshes d.currentDate based on the latest time and -// the current date format. -// Updates the stored date every 10 minutes. -func (d *DateTimePrinter) updateCurrentDate() { +// loopUpdateTime updates all time formats every second +func (d *DateTimePrinter) loopUpdateTime() { for { now := d.timeNow() - currentFmt := d.currentFormat.Load().(s.DateTimeFormat) - d.currentDate.Store(now.Format(dateFormat[currentFmt])) - time.Sleep(time.Minute * 10) + for i := 0; i < 3; i++ { + fmt := s.DateTimeFormat(i) + d.cachedTimes[i].Store(now.Format(timeFormat[fmt])) + } + + nextSecond := now.Truncate(time.Second).Add(time.Second) + time.Sleep(time.Until(nextSecond)) } } -// updateCurrentTime synchronizes with the system clock and updates the DateTimePrinter's -// currentTime property every full second. -func (d *DateTimePrinter) updateCurrentTime() { +// loopUpdateDate updates all date formats every 10 mins +func (d *DateTimePrinter) loopUpdateDate() { for { now := d.timeNow() - currentFmt := d.currentFormat.Load().(s.DateTimeFormat) - d.currentTime.Store(now.Format(timeFormat[currentFmt])) - nextSecond := now.Truncate(time.Second).Add(time.Second) - time.Sleep(time.Until(nextSecond)) + for i := 0; i < 3; i++ { + fmt := s.DateTimeFormat(i) + d.cachedDates[i].Store(now.Format(dateFormat[fmt])) + } + + time.Sleep(time.Minute * 10) } } -// updateCurrentUnix synchronizes with the system clock and updates the DateTimePrinter's +// updateCurrentTime synchronizes with the system clock and updates the DateTimePrinter's // currentTime property every full second. -func (d *DateTimePrinter) updateCurrentUnix() { +func (d *DateTimePrinter) loopUpdateUnix() { for { now := d.timeNow() d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10)) @@ -118,10 +110,14 @@ func (d *DateTimePrinter) updateCurrentUnix() { } } -// NewDateTimePrinter initializes DateTimePrinter with the default timeNow function. -func NewDateTimePrinter() *DateTimePrinter { - dateTimePrinter := &DateTimePrinter{timeNow: time.Now} - dateTimePrinter.currentFormat.Store(s.IT) +// GetDateTimePrinter returns the singleton instance. +func GetDateTimePrinter() *DateTimePrinter { + dateTimePrinterOnce.Do( + func() { + dateTimePrinterInstance = &DateTimePrinter{timeNow: time.Now} + dateTimePrinterInstance.init() + }, + ) - return dateTimePrinter + return dateTimePrinterInstance } diff --git a/internal/services/datetime_printer_test.go b/internal/services/datetime_printer_test.go index 6a3dcff..6cf8a70 100644 --- a/internal/services/datetime_printer_test.go +++ b/internal/services/datetime_printer_test.go @@ -15,10 +15,10 @@ func TestDateTimePrinter_PrintDateTime(t *testing.T) { return time.Date(2023, time.November, 1, 15, 30, 45, 0, time.UTC) }, } + dateTimePrinter.init() t.Run("Return both date and time", func(t *testing.T) { - dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(true, true) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, true, true) assert.Empty(t, unixTs) assert.NotEmpty(t, dateRes) assert.NotEmpty(t, timeRes) @@ -26,24 +26,21 @@ func TestDateTimePrinter_PrintDateTime(t *testing.T) { }) t.Run("Return date only", func(t *testing.T) { - dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(true, false) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, true, false) assert.Equal(t, "01/11/2023", dateRes) assert.Equal(t, "", timeRes) assert.Equal(t, "", unixTs) }) t.Run("Return time only", func(t *testing.T) { - dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(false, true) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, false, true) assert.Equal(t, "", dateRes) assert.Equal(t, "15:30:45", timeRes) assert.Equal(t, "", unixTs) }) t.Run("Return empty string when both flags are false", func(t *testing.T) { - dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, false) + dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.IT, false, false) assert.Equal(t, "", dateRes) assert.Equal(t, "", timeRes) assert.Equal(t, "", dateTimeRes) @@ -61,6 +58,7 @@ func TestDateTimePrinter_Formats(t *testing.T) { return fixedFutureTime }, } + dateTimePrinter.init() tests := []struct { name string @@ -94,19 +92,18 @@ func TestDateTimePrinter_Formats(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dateTimePrinter.UpdateDateTimeFormat(tt.format) - dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(true, true) + dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(tt.format, true, true) assert.NotEmpty(t, dateRes) assert.NotEmpty(t, timeRes) assert.Empty(t, unixTs) assert.Equal(t, tt.wantCombined, dateRes+" "+timeRes) // Also test individual components - d, tRes, _ := dateTimePrinter.RetrieveDateTime(true, false) + d, tRes, _ := dateTimePrinter.RetrieveDateTime(tt.format, true, false) assert.Equal(t, tt.wantDate, d) assert.Empty(t, tRes) - d, tRes, _ = dateTimePrinter.RetrieveDateTime(false, true) + d, tRes, _ = dateTimePrinter.RetrieveDateTime(tt.format, false, true) assert.Empty(t, d) assert.Equal(t, tt.wantTime, tRes) }) @@ -123,25 +120,24 @@ func TestDateTimePrinter_UnixTimestamp(t *testing.T) { return fixedFutureTime }, } - - dateTimePrinter.UpdateDateTimeFormat(shared.UnixTimestamp) + dateTimePrinter.init() t.Run("Return unix timestamp combined", func(t *testing.T) { - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true) + dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, true, true) assert.Empty(t, dateRes) assert.Empty(t, timeRes) assert.Equal(t, expectedUnix, dateTimeRes) }) t.Run("Return unix timestamp with date flag only", func(t *testing.T) { - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, false) + dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, true, false) assert.Empty(t, dateRes) assert.Empty(t, timeRes) assert.Equal(t, expectedUnix, dateTimeRes) }) t.Run("Return unix timestamp with time flag only", func(t *testing.T) { - dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, true) + dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, false, true) assert.Empty(t, dateRes) assert.Empty(t, timeRes) assert.Equal(t, expectedUnix, dateTimeRes) @@ -149,22 +145,21 @@ func TestDateTimePrinter_UnixTimestamp(t *testing.T) { } func TestNewDateTimePrinter(t *testing.T) { - assert.NotNil(t, NewDateTimePrinter()) - assert.IsType(t, &DateTimePrinter{}, NewDateTimePrinter()) + assert.NotNil(t, GetDateTimePrinter()) + assert.IsType(t, &DateTimePrinter{}, GetDateTimePrinter()) } func TestDateTimePrinter_FullSecondUpdate(t *testing.T) { - dateTimePrinter := NewDateTimePrinter() + dateTimePrinter := GetDateTimePrinter() t.Run("Return both date and time", func(t *testing.T) { - dateTimePrinter.UpdateDateTimeFormat(shared.IT) - dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(true, true) + dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(shared.IT, true, true) assert.NotEmpty(t, dateRes) assert.NotEmpty(t, timeRes) - prevTime := dateTimePrinter.currentTime.Load() + prevTime := dateTimePrinter.cachedTimes[shared.IT] time.Sleep(2 * time.Second) - currTime := dateTimePrinter.currentTime.Load() + currTime := dateTimePrinter.cachedTimes[shared.IT] assert.NotEqual(t, prevTime, currTime, "The current %s time should have changed from previous time", currTime, prevTime) diff --git a/logs/encoders/base_test.go b/logs/encoders/base_test.go index 9b697de..6925561 100644 --- a/logs/encoders/base_test.go +++ b/logs/encoders/base_test.go @@ -87,13 +87,13 @@ func TestBuildMsgWithCastAndConcatenateInto(t *testing.T) { } func TestBaseEncoder_GetType(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) assert.Equal(t, s.DefaultEncoderType, encoder.GetType()) - jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) assert.Equal(t, s.JsonEncoderType, jsonEncoder.GetType()) - yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) assert.Equal(t, s.YamlEncoderType, yamlEncoder.GetType()) baseEncoder := newBaseEncoder() diff --git a/logs/encoders/default.go b/logs/encoders/default.go index 486d581..a601165 100644 --- a/logs/encoders/default.go +++ b/logs/encoders/default.go @@ -34,6 +34,7 @@ func (d *DefaultEncoder) Log( tEnabled, logger.GetColorsEnabled(), logger.GetShowLogLevel(), + logger.GetDateTimeFormat(), args..., ) @@ -55,6 +56,7 @@ func (d *DefaultEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, a false, false, false, + logger.GetDateTimeFormat(), args..., ) @@ -73,6 +75,7 @@ func (d *DefaultEncoder) composeMsgInto( timeEnabled bool, headerColorEnabled bool, showLogLevel bool, + dateTimeFormat s.DateTimeFormat, args ...any, ) { buf.Grow(len(args)*averageWordLen + defaultCharOverhead) @@ -90,8 +93,8 @@ func (d *DefaultEncoder) composeMsgInto( } if isDateOrTimeEnabled { - dateStr, timeStr, _ := d.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) - d.addFormattedDateTime(buf, dateStr, timeStr) + dateStr, timeStr, unixTs := d.DateTimePrinter.RetrieveDateTime(dateTimeFormat, dateEnabled, timeEnabled) + d.addFormattedDateTime(buf, dateStr, timeStr, unixTs) } if showLogLevel || isDateOrTimeEnabled { @@ -104,7 +107,15 @@ func (d *DefaultEncoder) composeMsgInto( } // addFormattedDateTime formats and adds the date and time strings enclosed in square brackets to the given buffer. -func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeStr string) { +func (d *DefaultEncoder) addFormattedDateTime(buf *bytes.Buffer, dateStr, timeStr, unixTs string) { + if unixTs != "" { + buf.WriteByte('[') + buf.WriteString(unixTs) + buf.WriteByte(']') + + return + } + if dateStr == "" && timeStr == "" { return } diff --git a/logs/encoders/default_test.go b/logs/encoders/default_test.go index 0ff718e..c47d764 100644 --- a/logs/encoders/default_test.go +++ b/logs/encoders/default_test.go @@ -15,7 +15,7 @@ import ( ) func TestLogDebug(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -27,7 +27,7 @@ func TestLogDebug(t *testing.T) { } func TestLogInfo(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -39,7 +39,7 @@ func TestLogInfo(t *testing.T) { } func TestLogWarn(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -51,7 +51,7 @@ func TestLogWarn(t *testing.T) { } func TestLogError(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureErrorOutput(func() { @@ -63,7 +63,7 @@ func TestLogError(t *testing.T) { } func TestLogFatalError(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} if os.Getenv("BE_CRASHER") == "1" { @@ -80,32 +80,32 @@ func TestLogFatalError(t *testing.T) { func TestFormatDateTimeString(t *testing.T) { b := bytes.NewBuffer([]byte{}) - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) - encoder.addFormattedDateTime(b, "dateTest", "timeTest") + encoder.addFormattedDateTime(b, "dateTest", "timeTest", "") assert.Contains(t, b.String(), "[") assert.Contains(t, b.String(), "]") assert.Contains(t, b.String(), " ") b.Reset() - encoder.addFormattedDateTime(b, "", "timeTest") + encoder.addFormattedDateTime(b, "", "timeTest", "") assert.Contains(t, b.String(), "[") assert.Contains(t, b.String(), "]") b.Reset() - encoder.addFormattedDateTime(b, "dateTest", "") + encoder.addFormattedDateTime(b, "dateTest", "", "") assert.Contains(t, b.String(), "[") assert.Contains(t, b.String(), "]") b.Reset() - encoder.addFormattedDateTime(b, "", "") + encoder.addFormattedDateTime(b, "", "", "") assert.NotContains(t, b.String(), "[") assert.NotContains(t, b.String(), "]") assert.NotContains(t, b.String(), " ") } func TestShowLogLevel(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -126,7 +126,7 @@ func TestShowLogLevel(t *testing.T) { } func TestCheckColorsInTheOutput(t *testing.T) { - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.DebugLvlName, s.StdOutput, "Test msg") }) @@ -146,7 +146,7 @@ func TestDefaultEncoder_Color(t *testing.T) { var output string testLog := "my testing Log" originalStdOut := os.Stdout - encoder := NewDefaultEncoder(services.NewPrinter(), services.NewDateTimePrinter()) + encoder := NewDefaultEncoder(services.NewPrinter(), services.GetDateTimePrinter()) lConfig := test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, diff --git a/logs/encoders/json.go b/logs/encoders/json.go index 05bd30e..9a8e3f9 100644 --- a/logs/encoders/json.go +++ b/logs/encoders/json.go @@ -35,6 +35,7 @@ func (j *JSONEncoder) Log( dEnabled, tEnabled, logger.GetShowLogLevel(), + logger.GetDateTimeFormat(), j.castToString(args[0]), args[1:]..., ) @@ -58,6 +59,7 @@ func (j *JSONEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args dEnabled, tEnabled, false, + logger.GetDateTimeFormat(), j.castToString(args[0]), args[1:]..., ) @@ -77,11 +79,12 @@ func (j *JSONEncoder) composeMsgInto( dateEnabled bool, timeEnabled bool, showLogLevel bool, + dateTimeFormat s.DateTimeFormat, msg string, extras ...any, ) { buf.Grow((averageWordLen * len(extras)) + len(msg) + 60) - dateStr, timeStr, unixTs := j.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) + dateStr, timeStr, unixTs := j.DateTimePrinter.RetrieveDateTime(dateTimeFormat, dateEnabled, timeEnabled) if !showLogLevel { logLevel = "" diff --git a/logs/encoders/json_test.go b/logs/encoders/json_test.go index 694a0a3..08189f0 100644 --- a/logs/encoders/json_test.go +++ b/logs/encoders/json_test.go @@ -25,7 +25,7 @@ func decodeLogEntry(t *testing.T, logOutput string) shared.JsonLog { } func TestJSONEncoder_LogDebug(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -38,7 +38,7 @@ func TestJSONEncoder_LogDebug(t *testing.T) { } func TestJSONEncoder_LogInfo(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -51,7 +51,7 @@ func TestJSONEncoder_LogInfo(t *testing.T) { } func TestJSONEncoder_LogInfoWithExtras(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -76,7 +76,7 @@ func TestJSONEncoder_LogInfoWithExtras(t *testing.T) { } func TestJSONEncoder_LogWarn(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -89,7 +89,7 @@ func TestJSONEncoder_LogWarn(t *testing.T) { } func TestJSONEncoder_LogError(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureErrorOutput(func() { @@ -102,7 +102,7 @@ func TestJSONEncoder_LogError(t *testing.T) { } func TestJSONEncoder_LogFatalError(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} if os.Getenv("BE_CRASHER") == "1" { @@ -118,7 +118,7 @@ func TestJSONEncoder_LogFatalError(t *testing.T) { } func TestJSONEncoder_DateTime(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") @@ -154,7 +154,7 @@ func TestJSONEncoder_DateTime(t *testing.T) { } func TestJSONEncoder_ExtraMessages(t *testing.T) { - jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) lConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: false, ShowLogLevel: false} output := test.CaptureOutput(func() { @@ -184,7 +184,7 @@ func TestJSONEncoder_ExtraMessages(t *testing.T) { } func TestJSONEncoder_ShowLogLevelLt(t *testing.T) { - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -210,7 +210,7 @@ func TestJSONEncoder_Color(t *testing.T) { testLog := "my testing Log" originalStdOut := os.Stdout - encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + encoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) lConfig := test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, @@ -249,7 +249,7 @@ func TestJSONEncoder_ValidJSONOutput(t *testing.T) { originalStdOut := os.Stdout testLog := "my testing Log" - jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.NewDateTimePrinter()) + jsonEncoder := NewJSONEncoder(services.NewPrinter(), services.NewJsonMarshaler(), services.GetDateTimePrinter()) lConfig := &test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, diff --git a/logs/encoders/yaml.go b/logs/encoders/yaml.go index efcf05f..109712c 100644 --- a/logs/encoders/yaml.go +++ b/logs/encoders/yaml.go @@ -35,6 +35,7 @@ func (y *YAMLEncoder) Log( dEnabled, tEnabled, logger.GetShowLogLevel(), + logger.GetDateTimeFormat(), y.castToString(args[0]), args[1:]..., ) @@ -58,6 +59,7 @@ func (y *YAMLEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args dEnabled, tEnabled, false, + logger.GetDateTimeFormat(), y.castToString(args[0]), args[1:]..., ) @@ -77,11 +79,12 @@ func (y *YAMLEncoder) composeMsgInto( dateEnabled bool, timeEnabled bool, showLogLevel bool, + dateTimeFormat s.DateTimeFormat, msg string, extras ...any, ) { buf.Grow((averageWordLen * len(extras)) + len(msg) + 60) - date, time, unixTs := y.DateTimePrinter.RetrieveDateTime(dateEnabled, timeEnabled) + date, time, unixTs := y.DateTimePrinter.RetrieveDateTime(dateTimeFormat, dateEnabled, timeEnabled) if !showLogLevel { logLevel = "" diff --git a/logs/encoders/yaml_test.go b/logs/encoders/yaml_test.go index 5203959..dbabc4c 100644 --- a/logs/encoders/yaml_test.go +++ b/logs/encoders/yaml_test.go @@ -24,7 +24,7 @@ func decodeYamlLogEntry(t *testing.T, logOutput string) shared.YamlLog { } func TestYAMLEncoder_LogDebug(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -37,7 +37,7 @@ func TestYAMLEncoder_LogDebug(t *testing.T) { } func TestYAMLEncoder_LogInfo(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -50,7 +50,7 @@ func TestYAMLEncoder_LogInfo(t *testing.T) { } func TestYAMLEncoder_LogInfoWithExtras(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -75,7 +75,7 @@ func TestYAMLEncoder_LogInfoWithExtras(t *testing.T) { } func TestYAMLEncoder_LogWarn(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -88,7 +88,7 @@ func TestYAMLEncoder_LogWarn(t *testing.T) { } func TestYAMLEncoder_LogError(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureErrorOutput(func() { @@ -101,7 +101,7 @@ func TestYAMLEncoder_LogError(t *testing.T) { } func TestYAMLEncoder_LogFatalError(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} if os.Getenv("BE_CRASHER") == "1" { @@ -117,7 +117,7 @@ func TestYAMLEncoder_LogFatalError(t *testing.T) { } func TestYAMLEncoder_DateTime(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { encoder.Log(loggerConfig, ll.WarnLvlName, shared.StdOutput, "Test msg") @@ -153,7 +153,7 @@ func TestYAMLEncoder_DateTime(t *testing.T) { } func TestYAMLEncoder_ExtraMessages(t *testing.T) { - yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) lConfig := &test.LoggerConfigMock{DateEnabled: false, TimeEnabled: false, ColorsEnabled: false, ShowLogLevel: false} output := test.CaptureOutput(func() { @@ -183,7 +183,7 @@ func TestYAMLEncoder_ExtraMessages(t *testing.T) { } func TestYAMLEncoder_ShowLogLevelLt(t *testing.T) { - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) loggerConfig := &test.LoggerConfigMock{DateEnabled: true, TimeEnabled: true, ColorsEnabled: true, ShowLogLevel: true} output := test.CaptureOutput(func() { @@ -208,7 +208,7 @@ func TestYAMLEncoder_Color(t *testing.T) { var output string testLog := "my testing Log" originalStdOut := os.Stdout - encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + encoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) lConfig := test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, @@ -247,7 +247,7 @@ func TestYAMLEncoder_ValidYAMLOutput(t *testing.T) { originalStdOut := os.Stdout testLog := "my testing Log" - yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.NewDateTimePrinter()) + yamlEncoder := NewYAMLEncoder(services.NewPrinter(), services.NewYamlMarshaler(), services.GetDateTimePrinter()) lConfig := &test.LoggerConfigMock{ DateEnabled: false, TimeEnabled: false, diff --git a/logs/logger.go b/logs/logger.go index d5b9333..e68755b 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -206,7 +206,6 @@ func (l *Logger) GetDateTimeFormat() s.DateTimeFormat { // SetDateTimeFormat sets the DateTimeFormat of the logger. func (l *Logger) SetDateTimeFormat(format s.DateTimeFormat) *Logger { l.dateTimeFormat = format - l.dateTimePrinter.UpdateDateTimeFormat(format) return l } @@ -236,7 +235,7 @@ func NewLogger() *Logger { logger := &Logger{showLogLevel: true, dateTimeFormat: s.IT} logger.SetLogLvlEnvVariable(ll.DefaultEnvLogLvlVar) logger.printer = services.NewPrinter() - logger.dateTimePrinter = services.NewDateTimePrinter() + logger.dateTimePrinter = services.GetDateTimePrinter() logger.SetEncoder(s.DefaultEncoderType) return logger From 5021f833428c8cf546d314766b78ef2f2a0c6f25 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Tue, 30 Dec 2025 23:51:26 +0100 Subject: [PATCH 09/10] perf: updates the datetime printer to reduce the background goroutines to only 1 --- internal/services/datetime_printer.go | 39 +++++++++++++++------------ makefile | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/internal/services/datetime_printer.go b/internal/services/datetime_printer.go index 204981c..66c415a 100644 --- a/internal/services/datetime_printer.go +++ b/internal/services/datetime_printer.go @@ -27,9 +27,7 @@ var ( ) type DateTimePrinter struct { - timeNow func() time.Time - // Arrays indexed by s.DateTimeFormat (int8) - // Assuming 0=IT, 1=JP, 2=US. UnixTimestamp is handled separately. + timeNow func() time.Time cachedDates [3]atomic.Value cachedTimes [3]atomic.Value currentUnix atomic.Value @@ -54,6 +52,8 @@ func (d *DateTimePrinter) RetrieveDateTime(fmt s.DateTimeFormat, addDate, addTim return dateRes, timeRes, "" } +// init initializes the current timestamp and cached formatted strings, +// then starts background goroutines to keep them updated. func (d *DateTimePrinter) init() { now := d.timeNow() d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10)) @@ -66,19 +66,36 @@ func (d *DateTimePrinter) init() { go d.loopUpdateDate() go d.loopUpdateTime() - go d.loopUpdateUnix() } -// loopUpdateTime updates all time formats every second +// loopUpdateTime updates all time formats, the unix timestamp every second, +// and refreshes the date format if the day has changed. func (d *DateTimePrinter) loopUpdateTime() { + // Initialize lastDay with a value that forces an update on the first iteration + lastDay := -1 + for { now := d.timeNow() + // 1. Update Time Formats (Always) for i := 0; i < 3; i++ { fmt := s.DateTimeFormat(i) d.cachedTimes[i].Store(now.Format(timeFormat[fmt])) } + // 2. Update Unix Timestamp (Always) + d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10)) + + // 3. Update Date Formats (Only if day changed) + if currentDay := now.Day(); currentDay != lastDay { + for i := 0; i < 3; i++ { + fmt := s.DateTimeFormat(i) + d.cachedDates[i].Store(now.Format(dateFormat[fmt])) + } + + lastDay = currentDay + } + nextSecond := now.Truncate(time.Second).Add(time.Second) time.Sleep(time.Until(nextSecond)) } @@ -98,18 +115,6 @@ func (d *DateTimePrinter) loopUpdateDate() { } } -// updateCurrentTime synchronizes with the system clock and updates the DateTimePrinter's -// currentTime property every full second. -func (d *DateTimePrinter) loopUpdateUnix() { - for { - now := d.timeNow() - d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10)) - - nextSecond := now.Truncate(time.Second).Add(time.Second) - time.Sleep(time.Until(nextSecond)) - } -} - // GetDateTimePrinter returns the singleton instance. func GetDateTimePrinter() *DateTimePrinter { dateTimePrinterOnce.Do( diff --git a/makefile b/makefile index 4b0e0c9..854cc38 100644 --- a/makefile +++ b/makefile @@ -12,4 +12,4 @@ test-coverage: # Run benchmark tests test-benchmark: - CGO_ENABLED=1 GOARCH=${ARCH} go test ./test/benchmark_test.go -bench=. -benchmem -benchtime=5s -cpu=8 | grep /op + CGO_ENABLED=1 GOARCH=${ARCH} go test ./test/benchmark_test.go -bench=. -benchmem -benchtime=8s -cpu=8 | grep /op From 2283fecbde2d69ea70b75785d2160226d7d95731 Mon Sep 17 00:00:00 2001 From: arinaldi Date: Wed, 31 Dec 2025 00:03:47 +0100 Subject: [PATCH 10/10] feat: aligns the default encoder color method to the other 2 encoders --- logs/encoders/default.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logs/encoders/default.go b/logs/encoders/default.go index a601165..aac47f8 100644 --- a/logs/encoders/default.go +++ b/logs/encoders/default.go @@ -46,14 +46,15 @@ func (d *DefaultEncoder) Log( // Color formats and prints a colored Log message using the specified color. func (d *DefaultEncoder) Color(logger s.LoggerConfigsInterface, color c.Color, args ...any) { if len(args) > 0 { + dEnabled, tEnabled := logger.GetDateTimeEnabled() msgBuffer := d.getBuffer() msgBuffer.WriteString(color.String()) d.composeMsgInto( msgBuffer, ll.InfoLvlName, - false, - false, + dEnabled, + tEnabled, false, false, logger.GetDateTimeFormat(),