"time"
)
-const timeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,9}(Z|[+-]\d{2}:\d{2})`
+// textTimeRE is a regexp to match log timestamps for Text handler.
+// This is RFC3339Nano with the fixed 3 digit sub-second precision.
+const textTimeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|[+-]\d{2}:\d{2})`
+
+// jsonTimeRE is a regexp to match log timestamps for Text handler.
+// This is RFC3339Nano with an arbitrary sub-second precision.
+const jsonTimeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})`
func TestLogTextHandler(t *testing.T) {
ctx := context.Background()
check := func(want string) {
t.Helper()
if want != "" {
- want = "time=" + timeRE + " " + want
+ want = "time=" + textTimeRE + " " + want
}
checkLogOutput(t, buf.String(), want)
buf.Reset()
// log.Logger's output goes through the handler.
SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{AddSource: true})))
log.Print("msg2")
- checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3}"? msg=msg2`)
+ checkLogOutput(t, slogbuf.String(), "time="+textTimeRE+` level=INFO source=.*logger_test.go:\d{3}"? msg=msg2`)
// The default log.Logger always outputs at Info level.
slogbuf.Reset()
}
}
+ defer SetDefault(Default()) // restore
logger := New(h)
SetDefault(logger)
}
}
+// Test defaultHandler minimum level without calling slog.SetDefault.
+func TestLogLoggerLevelForDefaultHandler(t *testing.T) {
+ // Revert any changes to the default logger, flags, and level of log and slog.
+ currentLogLoggerLevel := logLoggerLevel.Level()
+ currentLogWriter := log.Writer()
+ currentLogFlags := log.Flags()
+ t.Cleanup(func() {
+ logLoggerLevel.Set(currentLogLoggerLevel)
+ log.SetOutput(currentLogWriter)
+ log.SetFlags(currentLogFlags)
+ })
+
+ var logBuf bytes.Buffer
+ log.SetOutput(&logBuf)
+ log.SetFlags(0)
+
+ for _, test := range []struct {
+ logLevel Level
+ logFn func(string, ...any)
+ want string
+ }{
+ {LevelDebug, Debug, "DEBUG a"},
+ {LevelDebug, Info, "INFO a"},
+ {LevelInfo, Debug, ""},
+ {LevelInfo, Info, "INFO a"},
+ } {
+ SetLogLoggerLevel(test.logLevel)
+ test.logFn("a")
+ checkLogOutput(t, logBuf.String(), test.want)
+ logBuf.Reset()
+ }
+}
+
+// Test handlerWriter minimum level by calling slog.SetDefault.
+func TestLogLoggerLevelForHandlerWriter(t *testing.T) {
+ removeTime := func(_ []string, a Attr) Attr {
+ if a.Key == TimeKey {
+ return Attr{}
+ }
+ return a
+ }
+
+ // Revert any changes to the default logger. This is important because other
+ // tests might change the default logger using SetDefault. Also ensure we
+ // restore the default logger at the end of the test.
+ currentLogger := Default()
+ currentLogLoggerLevel := logLoggerLevel.Level()
+ currentLogWriter := log.Writer()
+ currentFlags := log.Flags()
+ t.Cleanup(func() {
+ SetDefault(currentLogger)
+ logLoggerLevel.Set(currentLogLoggerLevel)
+ log.SetOutput(currentLogWriter)
+ log.SetFlags(currentFlags)
+ })
+
+ var logBuf bytes.Buffer
+ log.SetOutput(&logBuf)
+ log.SetFlags(0)
+ SetLogLoggerLevel(LevelError)
+ SetDefault(New(NewTextHandler(&logBuf, &HandlerOptions{ReplaceAttr: removeTime})))
+ log.Print("error")
+ checkLogOutput(t, logBuf.String(), `level=ERROR msg=error`)
+}
+
func TestLoggerError(t *testing.T) {
var buf bytes.Buffer
h := NewTextHandler(&buf, nil)
ll := NewLogLogger(h, LevelWarn)
ll.Print("hello")
- checkLogOutput(t, buf.String(), "time="+timeRE+` level=WARN msg=hello`)
+ checkLogOutput(t, buf.String(), "time="+textTimeRE+` level=WARN msg=hello`)
}
func TestLoggerNoOps(t *testing.T) {
in any
out string
}{
- {(*panicTextAndJsonMarshaler)(nil), `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":null}`},
- {panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":"!PANIC: unexpected EOF"}`},
- {panicTextAndJsonMarshaler{"panicking"}, `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":"!PANIC: panicking"}`},
- {panicTextAndJsonMarshaler{42}, `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":"!PANIC: 42"}`},
+ {(*panicTextAndJsonMarshaler)(nil), `{"time":"` + jsonTimeRE + `","level":"INFO","msg":"msg","p":null}`},
+ {panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `{"time":"` + jsonTimeRE + `","level":"INFO","msg":"msg","p":"!PANIC: unexpected EOF"}`},
+ {panicTextAndJsonMarshaler{"panicking"}, `{"time":"` + jsonTimeRE + `","level":"INFO","msg":"msg","p":"!PANIC: panicking"}`},
+ {panicTextAndJsonMarshaler{42}, `{"time":"` + jsonTimeRE + `","level":"INFO","msg":"msg","p":"!PANIC: 42"}`},
} {
Info("msg", "p", pt.in)
checkLogOutput(t, logBuf.String(), pt.out)