diff options
| author | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2026-02-24 23:43:59 +0530 |
|---|---|---|
| committer | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2026-02-24 23:43:59 +0530 |
| commit | 594b5972ff33970c3d03da9a679b10e64dd98080 (patch) | |
| tree | 9386b0f152df31785273cbdaf992011e0a0524bb /logging | |
| parent | Split net packages (diff) | |
| download | kit-594b5972ff33970c3d03da9a679b10e64dd98080.tar kit-594b5972ff33970c3d03da9a679b10e64dd98080.tar.gz kit-594b5972ff33970c3d03da9a679b10e64dd98080.tar.bz2 kit-594b5972ff33970c3d03da9a679b10e64dd98080.tar.lz kit-594b5972ff33970c3d03da9a679b10e64dd98080.tar.xz kit-594b5972ff33970c3d03da9a679b10e64dd98080.tar.zst kit-594b5972ff33970c3d03da9a679b10e64dd98080.zip | |
Misc Fixes
Diffstat (limited to 'logging')
| -rw-r--r-- | logging/log.go | 159 | ||||
| -rw-r--r-- | logging/log_test.go | 106 | ||||
| -rw-r--r-- | logging/sinks/encoding.go | 2 | ||||
| -rw-r--r-- | logging/sinks/writer.go | 4 | ||||
| -rw-r--r-- | logging/sinks/writer_test.go | 3 |
5 files changed, 132 insertions, 142 deletions
diff --git a/logging/log.go b/logging/log.go index 9f94bc0..a9d4bf4 100644 --- a/logging/log.go +++ b/logging/log.go @@ -15,6 +15,7 @@ import ( "time" "go.sudomsg.com/kit/logging/sinks" + "go.sudomsg.com/kit/net" ) type ctxLoggerKeyType struct{} @@ -116,6 +117,66 @@ func RecoverAndLog(ctx context.Context, msg string, err any) { handler.Handle(ctx, r) } +type LogFormat int + +const ( + FormatText LogFormat = iota + FormatJSON + FormatMessage +) + +func (s LogFormat) String() string { + switch s { + case FormatJSON: + return "json" + case FormatText: + return "text" + case FormatMessage: + return "message" + default: + return fmt.Sprintf("LogFormat(%d)", s) + } +} + +func (s *LogFormat) Set(str string) error { + switch strings.ToLower(str) { + case "text": + *s = FormatText + case "json": + *s = FormatJSON + case "message": + *s = FormatMessage + default: + return fmt.Errorf("invalid LogFormat %q", str) + } + return nil +} + +func (s LogFormat) AppendText(b []byte) ([]byte, error) { + return append(b, s.String()...), nil +} + +func (s LogFormat) MarshalText() ([]byte, error) { + return s.AppendText(nil) +} + +func (s *LogFormat) UnmarshalText(b []byte) error { + return s.Set(string(b)) +} + +func newEncoder(s LogFormat) (sinks.Encoder, error) { + switch s { + case FormatText: + return sinks.LogFmtEncoder, nil + case FormatJSON: + return sinks.JSONEncoder, nil + case FormatMessage: + return sinks.MessageEncoder, nil + default: + return nil, fmt.Errorf("invalid LogFormat: %q", s) + } +} + // LogSink defines where log output should be written. // // It is used to configure the log destination in Setup. @@ -133,7 +194,9 @@ const ( // If configured, the File field must be a valid path for writing logs. SinkFile - SinkSyslog + SinkSyslog //TODO + + SinkHost //TODO ) func (s LogSink) String() string { @@ -179,53 +242,6 @@ func (s *LogSink) UnmarshalText(b []byte) error { return s.Set(string(b)) } -type LogFormat int - -const ( - FormatText LogFormat = iota - FormatJSON - FormatMessage -) - -func (s LogFormat) String() string { - switch s { - case FormatJSON: - return "json" - case FormatText: - return "text" - case FormatMessage: - return "message" - default: - return fmt.Sprintf("LogSink(%d)", s) - } -} - -func (s *LogFormat) Set(str string) error { - switch strings.ToLower(str) { - case "text": - *s = FormatText - case "json": - *s = FormatJSON - case "message": - *s = FormatMessage - default: - return fmt.Errorf("invalid LogSink %q", str) - } - return nil -} - -func (s LogFormat) AppendText(b []byte) ([]byte, error) { - return append(b, s.String()...), nil -} - -func (s LogFormat) MarshalText() ([]byte, error) { - return s.AppendText(nil) -} - -func (s *LogFormat) UnmarshalText(b []byte) error { - return s.Set(string(b)) -} - // LogConfig holds configuration for the logging setup. // // Level specifies the minimum level for logs (e.g., slog.LevelInfo). @@ -238,10 +254,14 @@ func (s *LogFormat) UnmarshalText(b []byte) error { // File specifies the output file path when SinkFile is selected. // It is required if SinkFile is used. type LogConfig struct { - Level slog.Level `toml:"level"` - Sink LogSink `toml:"sink"` - Format LogFormat `toml:"format"` - File string `toml:"file"` + Level slog.Level `toml:"level"` + Sink LogSink `toml:"sink"` + Format LogFormat `toml:"format"` + File string `toml:"file"` + Tag string `toml:"tag"` + Network net.NetType `toml:"network"` + Address string `toml:"address"` + Facility int `toml:"facility"` } func init() { @@ -276,26 +296,12 @@ func Setup(cfg LogConfig) error { return nil } -// New is same as Setup but returns the logger instead of setting up the global logger -func New(cfg LogConfig) (*slog.Logger, error) { - var encoder func(slog.Record, func(string, slog.Value) (slog.Value, bool)) (string, error) - switch cfg.Format { - case FormatText: - encoder = sinks.LogFmtEncoder - case FormatJSON: - encoder = sinks.JSONEncoder - case FormatMessage: - encoder = sinks.MessageEncoder - default: - return nil, fmt.Errorf("invalid LogFormat: %v", cfg.Format) - } - - var sink sinks.Sink +func newSink(cfg LogConfig, encoder sinks.Encoder) (sinks.Sink, error) { switch cfg.Sink { case SinkStdout: - sink = sinks.NewWriterSink(os.Stdout, encoder, cfg.Level) + return sinks.NewWriterSink(os.Stdout, encoder, cfg.Level), nil case SinkStderr: - sink = sinks.NewWriterSink(os.Stderr, encoder, cfg.Level) + return sinks.NewWriterSink(os.Stderr, encoder, cfg.Level), nil case SinkFile: if cfg.File == "" { return nil, fmt.Errorf("log sink set to 'file' but no file path provided") @@ -304,11 +310,24 @@ func New(cfg LogConfig) (*slog.Logger, error) { if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } - sink = sinks.NewWriterSink(f, encoder, cfg.Level) + return sinks.NewWriterSink(f, encoder, cfg.Level), nil default: return nil, fmt.Errorf("invalid LogSink %v", cfg.Sink) } +} + +// New is same as Setup but returns the logger instead of setting up the global logger +func New(cfg LogConfig) (*slog.Logger, error) { + encoder, err := newEncoder(cfg.Format) + if err != nil { + return nil, err + } + + sink, err := newSink(cfg, encoder) + if err != nil { + return nil, err + } handler := sinks.NewSinkHandler(sink) logger := slog.New(handler) return logger, nil diff --git a/logging/log_test.go b/logging/log_test.go index 41ee398..5efed19 100644 --- a/logging/log_test.go +++ b/logging/log_test.go @@ -1,7 +1,9 @@ package logging_test import ( + "encoding" "errors" + "fmt" "log/slog" "strings" "testing" @@ -141,18 +143,18 @@ func TestWithGroup(t *testing.T) { } } -func TestLogSink(t *testing.T) { - t.Parallel() +type Enum interface { + comparable + fmt.Stringer + encoding.TextMarshaler +} + +func EnumTester[T Enum](t *testing.T, testCases []T) { + t.Helper() t.Run("RoundTrip", func(t *testing.T) { t.Parallel() - testCases := []logging.LogSink{ - logging.SinkStdout, - logging.SinkStderr, - logging.SinkFile, - } - for _, original := range testCases { t.Run(original.String(), func(t *testing.T) { t.Run("case same", func(t *testing.T) { @@ -162,8 +164,12 @@ func TestLogSink(t *testing.T) { t.Fatalf("MarshalText failed: %v", err) } - var decoded logging.LogSink - if err := decoded.UnmarshalText(text); err != nil { + var decoded T + dec, ok := any(&decoded).(encoding.TextUnmarshaler) + if !ok { + t.Fatal("Does not implement TextUnmarshaler") + } + if err := dec.UnmarshalText(text); err != nil { t.Fatalf("UnmarshalText failed: %v", err) } @@ -179,11 +185,15 @@ func TestLogSink(t *testing.T) { t.Fatalf("MarshalText failed: %v", err) } - var decoded logging.LogSink - if err := decoded.UnmarshalText([]byte(strings.ToUpper(string(text)))); err != nil { + var decoded T + dec, ok := any(&decoded).(encoding.TextUnmarshaler) + if !ok { + t.Fatal("Does not implement TextUnmarshaler") + } + if err := dec.UnmarshalText([]byte(strings.ToUpper(string(text)))); err != nil { t.Fatalf("UnmarshalText failed: %v", err) } - + t.Log(decoded) if decoded != original { t.Fatalf("Round-trip mismatch: got %v, want %v", decoded, original) } @@ -198,7 +208,7 @@ func TestLogSink(t *testing.T) { t.Parallel() input := "invalid" - var decoded logging.LogSink + var decoded logging.LogFormat if err := decoded.UnmarshalText([]byte(input)); err == nil { t.Errorf("expected error for input %q, got none", input) @@ -206,66 +216,22 @@ func TestLogSink(t *testing.T) { }) } -func TestLogFormat(t *testing.T) { +func TestLogSink(t *testing.T) { t.Parallel() - t.Run("RoundTrip", func(t *testing.T) { - t.Parallel() - - testCases := []logging.LogFormat{ - logging.FormatText, - logging.FormatJSON, - } - - for _, original := range testCases { - t.Run(original.String(), func(t *testing.T) { - t.Run("case same", func(t *testing.T) { - t.Run(original.String(), func(t *testing.T) { - text, err := original.MarshalText() - if err != nil { - t.Fatalf("MarshalText failed: %v", err) - } - - var decoded logging.LogFormat - if err := decoded.UnmarshalText(text); err != nil { - t.Fatalf("UnmarshalText failed: %v", err) - } - - if decoded != original { - t.Fatalf("Round-trip mismatch: got %v, want %v", decoded, original) - } - }) - }) - t.Run("case not same", func(t *testing.T) { - t.Run(original.String(), func(t *testing.T) { - text, err := original.MarshalText() - if err != nil { - t.Fatalf("MarshalText failed: %v", err) - } - - var decoded logging.LogFormat - if err := decoded.UnmarshalText([]byte(strings.ToUpper(string(text)))); err != nil { - t.Fatalf("UnmarshalText failed: %v", err) - } - - if decoded != original { - t.Fatalf("Round-trip mismatch: got %v, want %v", decoded, original) - } - }) - }) - - }) - } + EnumTester(t, []logging.LogSink{ + logging.SinkStdout, + logging.SinkStderr, + logging.SinkFile, }) +} - t.Run("Invalid", func(t *testing.T) { - t.Parallel() - - input := "invalid" - var decoded logging.LogFormat +func TestLogFormat(t *testing.T) { + t.Parallel() - if err := decoded.UnmarshalText([]byte(input)); err == nil { - t.Errorf("expected error for input %q, got none", input) - } + EnumTester(t, []logging.LogFormat{ + logging.FormatText, + logging.FormatJSON, + logging.FormatMessage, }) } diff --git a/logging/sinks/encoding.go b/logging/sinks/encoding.go index e1c558a..42848ec 100644 --- a/logging/sinks/encoding.go +++ b/logging/sinks/encoding.go @@ -57,6 +57,8 @@ func RecordAll(r slog.Record) iter.Seq2[string, slog.Value] { } } +type Encoder func(rec slog.Record, ReplaceAttr func(string, slog.Value) (slog.Value, bool)) (string, error) + func LogFmtEncoder(rec slog.Record, ReplaceAttr func(string, slog.Value) (slog.Value, bool)) (string, error) { str := []string{} for key, value := range RecordAll(rec) { diff --git a/logging/sinks/writer.go b/logging/sinks/writer.go index a0b42cb..93f3423 100644 --- a/logging/sinks/writer.go +++ b/logging/sinks/writer.go @@ -10,13 +10,13 @@ import ( type WriterSink struct { writer io.Writer mu sync.Mutex - encoder func(slog.Record, func(string, slog.Value) (slog.Value, bool)) (string, error) + encoder Encoder level slog.Level } var _ Sink = &WriterSink{} -func NewWriterSink(w io.Writer, encoder func(slog.Record, func(string, slog.Value) (slog.Value, bool)) (string, error), level slog.Leveler) *WriterSink { +func NewWriterSink(w io.Writer, encoder Encoder, level slog.Leveler) *WriterSink { return &WriterSink{ writer: w, encoder: encoder, diff --git a/logging/sinks/writer_test.go b/logging/sinks/writer_test.go index 4089b06..0c8f79e 100644 --- a/logging/sinks/writer_test.go +++ b/logging/sinks/writer_test.go @@ -22,6 +22,9 @@ func TestWriterHandler(t *testing.T) { func(t *testing.T) map[string]any { t.Helper() + if buf.Len() == 0 { + t.Fatal("buffer is empty; no log was written") + } b := buf.Bytes() r := bytes.NewReader(b) dec := json.NewDecoder(r) |
