Wrapping output methods

May 15, 2026

Let’s talk about a feature I’ve never used, or even knew existed… I mentioned a while back that the AddSource field of HandlerOptions controls whether the log output includes the source code position of the log call. But what if that log call is wrapped by a helper, obscuring the meaningful source position?

Wrapping output methods

The logger functions use reflection over the call stack to find the file name and line number of the logging call within the application. This can produce incorrect source information for functions that wrap slog. For instance, if you define this function in file mylog.go:

func Infof(logger *slog.Logger, format string, args ...any) {
    logger.Info(fmt.Sprintf(format, args...))
}

and you call it like this in main.go:

Infof(slog.Default(), "hello, %s", "world")

then slog will report the source file as mylog.go, not main.go.

A correct implementation of Infof will obtain the source location (pc) and pass it to NewRecord. The Infof function in the package-level example called “wrapping” demonstrates how to do this.

This section of the docs just explains the problem, in a bit more detail than I did… then points you to the Example for the solution. Let’s look at that Example. Follow the link for the full example, executable in your browser. Here I’ll just quote the most interesting bit:

	var pcs [1]uintptr
	runtime.Callers(2, pcs[:]) // skip [Callers, Infof]
	r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf(format, args...), pcs[0])
	_ = logger.Handler().Handle(context.Background(), r)

What you see here is the “low-level” construction of a log record, which gives you fine-grained control over the details. The detail that matters here, is the caller’s stack frame.

What is a log Record? That’s the next topic we’ll discuss!


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Back after an unannounced absence

Hey everyone… I dropped the ball! A combination of unexpected family events, prepping for a conference, and some travel, meant I haven’t been writing for much longer than I like. But I’m back! So where were we? Oh that’s right… performance considerations with log/slog. We had looked at using the fmt.Stringer interface to avoid eager processing with slog.TextHandler. But let’s now look at a more general solution: Performance considerations …


Lazy attribute evaluation for JSONHandler

As I was writing yesterday’s post, a portion of the GoDoc confused me. I’ve now spent over 3 hours with Claude trying to parse the prose grammatically, build test cases, and make general sense of it. I think I finally have… Here’s hoping! So, yesterday we saw how you can lazy-evaluate some values when using TextHandler. But the proposed solution (pass a fmt.Stringer rather than a literal string) has other, likely uninintended, consequences if you’re using JSONHandler:


Attribute evaluation

log.With isn’t the only trick available for improving performance of logging. Many values you may want to pass to a logger need to be calculated. And sometimes that calculation is expensive. And if a log is omitted, because it’s a debug log, and our logger is only configured for info-and-up level, that calculation should be skipped. Performance considerations … The arguments to a log call are always evaluated, even if the log event is discarded.

Get daily content like this in your inbox!

Subscribe