aboutsummaryrefslogtreecommitdiffstats
path: root/logging
diff options
context:
space:
mode:
authorMarc Pervaz Boocha <mboocha@sudomsg.com>2026-02-24 23:43:59 +0530
committerMarc Pervaz Boocha <mboocha@sudomsg.com>2026-02-24 23:43:59 +0530
commit594b5972ff33970c3d03da9a679b10e64dd98080 (patch)
tree9386b0f152df31785273cbdaf992011e0a0524bb /logging
parentSplit net packages (diff)
downloadkit-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.go159
-rw-r--r--logging/log_test.go106
-rw-r--r--logging/sinks/encoding.go2
-rw-r--r--logging/sinks/writer.go4
-rw-r--r--logging/sinks/writer_test.go3
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)