internal/cpu, internal/goarch,
internal/goexperiment, internal/goos,
internal/goversion, internal/nettrace, internal/platform,
+ log/internal,
maps, slices, unicode/utf8, unicode/utf16, unicode,
unsafe;
< NET;
# logging - most packages should not import; http and up is allowed
- FMT
+ FMT, log/internal
< log;
log, log/slog !< crypto/tls, database/sql, go/importer, testing;
FMT,
encoding, encoding/json,
- log,
+ log, log/internal,
log/slog/internal, log/slog/internal/buffer,
slices
< log/slog
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package internal contains definitions used by both log and log/slog.
+package internal
+
+// DefaultOutput holds a function which calls the default log.Logger's
+// output function.
+// It allows slog.defaultHandler to call into an unexported function of
+// the log package.
+var DefaultOutput func(pc uintptr, data []byte) error
import (
"fmt"
"io"
+ "log/internal"
"os"
"runtime"
"sync"
// paths it will be 2.
func (l *Logger) Output(calldepth int, s string) error {
calldepth++ // +1 for this frame.
- return l.output(calldepth, func(b []byte) []byte {
+ return l.output(0, calldepth, func(b []byte) []byte {
return append(b, s...)
})
}
-func (l *Logger) output(calldepth int, appendOutput func([]byte) []byte) error {
+// output can take either a calldepth or a pc to get source line information.
+// It uses the pc if it is non-zero.
+func (l *Logger) output(pc uintptr, calldepth int, appendOutput func([]byte) []byte) error {
if l.isDiscard.Load() {
return nil
}
var file string
var line int
if flag&(Lshortfile|Llongfile) != 0 {
- var ok bool
- _, file, line, ok = runtime.Caller(calldepth)
- if !ok {
- file = "???"
- line = 0
+ if pc == 0 {
+ var ok bool
+ _, file, line, ok = runtime.Caller(calldepth)
+ if !ok {
+ file = "???"
+ line = 0
+ }
+ } else {
+ fs := runtime.CallersFrames([]uintptr{pc})
+ f, _ := fs.Next()
+ file = f.File
+ if file == "" {
+ file = "???"
+ }
+ line = f.Line
}
}
return err
}
+func init() {
+ internal.DefaultOutput = func(pc uintptr, data []byte) error {
+ return std.output(pc, 0, func(buf []byte) []byte {
+ return append(buf, data...)
+ })
+ }
+}
+
// Print calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...any) {
- l.output(2, func(b []byte) []byte {
+ l.output(0, 2, func(b []byte) []byte {
return fmt.Append(b, v...)
})
}
// Printf calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, v ...any) {
- l.output(2, func(b []byte) []byte {
+ l.output(0, 2, func(b []byte) []byte {
return fmt.Appendf(b, format, v...)
})
}
// Println calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Println.
func (l *Logger) Println(v ...any) {
- l.output(2, func(b []byte) []byte {
+ l.output(0, 2, func(b []byte) []byte {
return fmt.Appendln(b, v...)
})
}
// Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...any) {
- std.output(2, func(b []byte) []byte {
+ std.output(0, 2, func(b []byte) []byte {
return fmt.Append(b, v...)
})
}
// Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...any) {
- std.output(2, func(b []byte) []byte {
+ std.output(0, 2, func(b []byte) []byte {
return fmt.Appendf(b, format, v...)
})
}
// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...any) {
- std.output(2, func(b []byte) []byte {
+ std.output(0, 2, func(b []byte) []byte {
return fmt.Appendln(b, v...)
})
}
type defaultHandler struct {
ch *commonHandler
- // log.Output, except for testing
- output func(calldepth int, message string) error
+ // internal.DefaultOutput, except for testing
+ output func(pc uintptr, data []byte) error
}
-func newDefaultHandler(output func(int, string) error) *defaultHandler {
+func newDefaultHandler(output func(uintptr, []byte) error) *defaultHandler {
return &defaultHandler{
ch: &commonHandler{json: false},
output: output,
state := h.ch.newHandleState(buf, true, " ", nil)
defer state.free()
state.appendNonBuiltIns(r)
-
- // skip [h.output, defaultHandler.Handle, handlerWriter.Write, log.Output]
- return h.output(4, buf.String())
+ return h.output(r.PC, *buf)
}
func (h *defaultHandler) WithAttrs(as []Attr) Handler {
} {
t.Run(test.name, func(t *testing.T) {
var got string
- var h Handler = newDefaultHandler(func(_ int, s string) error {
- got = s
+ var h Handler = newDefaultHandler(func(_ uintptr, b []byte) error {
+ got = string(b)
return nil
})
if test.with != nil {
import (
"context"
"log"
+ loginternal "log/internal"
"log/slog/internal"
"runtime"
"sync/atomic"
var defaultLogger atomic.Value
func init() {
- defaultLogger.Store(New(newDefaultHandler(log.Output)))
+ defaultLogger.Store(New(newDefaultHandler(loginternal.DefaultOutput)))
}
// Default returns the default Logger.
"internal/testenv"
"io"
"log"
+ loginternal "log/internal"
"path/filepath"
"regexp"
"runtime"
// tests might change the default logger using SetDefault. Also ensure we
// restore the default logger at the end of the test.
currentLogger := Default()
- SetDefault(New(newDefaultHandler(log.Output)))
+ SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
t.Cleanup(func() {
SetDefault(currentLogger)
})
t.Run("wrap default handler", func(t *testing.T) {
// It should be possible to wrap the default handler and get the right output.
- // But because the call depth to log.Output is hard-coded, the source line is wrong.
- // We want to use the pc inside the Record, but there is no way to give that to
- // the log package.
- //
- // TODO(jba): when slog lives under log in the standard library, we can
- // move the bulk of log.Logger.Output to a function in an internal
- // package, so both log and slog can call it.
- //
- // While slog lives in exp, we punt.
- t.Skip("skip until this package is in the standard library")
+ // This works because the default handler uses the pc in the Record
+ // to get the source line, rather than a call depth.
logger := New(wrappingHandler{Default().Handler()})
logger.Info("msg", "d", 4)
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg d=4`)