diff --git a/options.go b/options.go new file mode 100644 index 0000000000000000000000000000000000000000..fd76beead27c0ba15f7f62630315e1165bc4cb97 --- /dev/null +++ b/options.go @@ -0,0 +1,25 @@ +package slog + +import ( + "golang.org/x/exp/slog" + "io" +) + +type Options struct { + Leveler slog.Leveler + Writer io.Writer +} + +type Option func(*Options) + +func WithLeveler(leveler slog.Leveler) Option { + return func(o *Options) { + o.Leveler = leveler + } +} + +func WithWriter(writer io.Writer) Option { + return func(o *Options) { + o.Writer = writer + } +} diff --git a/slog.go b/slog.go new file mode 100644 index 0000000000000000000000000000000000000000..603449a1ece5de8094ed77c2ad54866c9cb6b835 --- /dev/null +++ b/slog.go @@ -0,0 +1,51 @@ +package slog + +import ( + "golang.org/x/exp/slog" + "os" + "strings" +) + +func New(opts ...Option) *slog.Logger { + options := &Options{ + Leveler: slog.LevelDebug, + Writer: os.Stdout, + } + for _, opt := range opts { + opt(options) + } + + // Attributes modified for datadog schema via HandlerOptions.ReplaceAttr + // Per recommendation at https://pkg.go.dev/golang.org/x/exp/slog#JSONHandler + // See datadog: https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#reserved-attributes + replaceAttr := func(groups []string, attr slog.Attr) slog.Attr { + if attr.Key == "msg" { + attr.Key = "message" + } + if attr.Key == "time" { + attr.Key = "date" + } + if attr.Key == "level" { + attr.Key = "status" + attr.Value = slog.StringValue(strings.ToLower(attr.Value.String())) + } + return attr + } + + handlerOptions := &slog.HandlerOptions{ + Level: options.Leveler, + AddSource: true, + ReplaceAttr: replaceAttr, + } + logHandler := slog.NewJSONHandler(options.Writer, handlerOptions) + logger := slog.New(logHandler) + + hostname, err := os.Hostname() + if err != nil { + logger.Error("Failed to get hostname.", slog.String("error", err.Error())) + } else { + logger = logger.With("host", hostname) + } + + return logger +} diff --git a/slog_test.go b/slog_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f1007b4f7b80d663c81c02dff0ce18c252a375fd --- /dev/null +++ b/slog_test.go @@ -0,0 +1,34 @@ +package slog + +import ( + "bufio" + "bytes" + "encoding/json" + "golang.org/x/exp/slog" + "testing" +) + +func TestNew(t *testing.T) { + var buf bytes.Buffer + + logger := New(WithWriter(&buf)) + logger.Info("Foo", slog.String("bar", "baz")) + logger.Error("Bar", slog.String("baz", "qux")) + + scanner := bufio.NewScanner(&buf) + + // Scan the first log. + scanner.Scan() + infoLog := scanner.Bytes() + var infoLogMap map[string]any + if err := json.Unmarshal(infoLog, &infoLogMap); err != nil { + t.Errorf("Failed to unmarshal info log: %s", err.Error()) + } + t.Log(infoLogMap) + if infoLogMap["status"] != "info" { + t.Errorf("Expected info log level, got %s", infoLogMap["level"]) + } + if infoLogMap["bar"] != "baz" { + t.Errorf("Expected bar=baz, got %s", infoLogMap["bar"]) + } +} diff --git a/src/main/main.go b/src/main/main.go deleted file mode 100644 index c2e8c45cfad7e02a901368d2f9a8e2bbd4fe8edb..0000000000000000000000000000000000000000 --- a/src/main/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "gitlab.nexdev.uk/pub/go-slog/src/slog" -) - -func main() { - logger := slog.InitLogger() - logger.Info("Logger test.") -} diff --git a/src/slog/slog.go b/src/slog/slog.go deleted file mode 100644 index 8c582411a64a6dad92e31e61af66ff99ab245465..0000000000000000000000000000000000000000 --- a/src/slog/slog.go +++ /dev/null @@ -1,41 +0,0 @@ -package slog - -import ( - "golang.org/x/exp/slog" - "os" - "strings" -) - -func InitLogger() *slog.Logger { - // Could set logger level via ENV vars in future. - programLevel := new(slog.LevelVar) - programLevel.Set(slog.LevelDebug) - - // Attributes modified for datadog schema via HandlerOptions.ReplaceAttr - // Per recommendation at https://pkg.go.dev/golang.org/x/exp/slog#JSONHandler - // See datadog: https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#reserved-attributes - loggerReplace := func(groups []string, a slog.Attr) slog.Attr { - if a.Key == "msg" { - a.Key = "message" - } - if a.Key == "time" { - a.Key = "date" - } - if a.Key == "level" { - a.Key = "status" - a.Value = slog.StringValue(strings.ToLower(a.Value.String())) - } - return a - } - logger := slog.New(slog.NewJSONHandler(os.Stdout, - &slog.HandlerOptions{Level: programLevel, AddSource: true, ReplaceAttr: loggerReplace})) - hostname, hostnameErr := os.Hostname() - if hostnameErr == nil { - logger = logger.With("host", hostname) - } else { - logger.Error("Failed to get hostname.", slog.String("error", hostnameErr.Error())) - } - - logger.Info("Initialised.") - return logger -}