ronantakizawa/codereview-bench huggingface dataset:
Repository: uber-go/zap
File: zapcore/entry.go (Go)

Code before:
	WriteThenNoop CheckWriteAction = iota
	// WriteThenGoexit runs runtime.Goexit after Write.
	WriteThenGoexit
	// WriteThenPanic causes a panic after Write.
	WriteThenPanic
	// WriteThenFatal causes an os.Exit(1) after Write.
	WriteThenFatal
)

// OnWrite implements the OnWrite method to keep CheckWriteAction compatible
// with the new CheckWriteHook interface which deprecates CheckWriteAction.
func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) {
	switch a {
	case WriteThenGoexit:
		runtime.Goexit()
	case WriteThenPanic:
		panic(ce.Message)
	case WriteThenFatal:
		exit.Exit()
	}
}

var _ CheckWriteHook = WriteThenNoop
var _ CheckWriteHook = WriteThenGoexit
var _ CheckWriteHook = WriteThenPanic
var _ CheckWriteHook = WriteThenFatal

// CheckedEntry is an Entry together with a collection of Cores that have
// already agreed to log it.
//
// CheckedEntry references should be created by calling AddCore or Should on a
// nil *CheckedEntry. References are returned to a pool after Write, and MUST
// NOT be retained after calling their Write method.
type CheckedEntry struct {
	Entry
	ErrorOutput WriteSyncer
	dirty       bool // best-effort detection of pool misuse
	after       CheckWriteHook
	cores       []Core
}

func (ce *CheckedEntry) reset() {
	ce.Entry = Entry{}
	ce.ErrorOutput = nil
	ce.dirty = false
	ce.after = nil
	for i := range ce.cores {
		// don't keep references to cores
		ce.cores[i] = nil
	}
	ce.cores = ce.cores[:0]

Review comment (suggestion):
The check is type-level, not value-level so we can just verify that CheckWriteAction implements the interface.

Code after:
	WriteThenNoop CheckWriteAction = iota
	// WriteThenGoexit runs runtime.Goexit after Write.
	WriteThenGoexit
	// WriteThenPanic causes a panic after Write.
	WriteThenPanic
	// WriteThenFatal causes an os.Exit(1) after Write.
	WriteThenFatal
)

// OnWrite implements the OnWrite method to keep CheckWriteAction compatible
// with the new CheckWriteHook interface which deprecates CheckWriteAction.
func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) {
	switch a {
	case WriteThenGoexit:
		runtime.Goexit()
	case WriteThenPanic:
		panic(ce.Message)
	case WriteThenFatal:
		exit.Exit()
	}
}

var _ CheckWriteHook = CheckWriteAction(0)

// CheckedEntry is an Entry together with a collection of Cores that have
// already agreed to log it.
//
// CheckedEntry references should be created by calling AddCore or Should on a
// nil *CheckedEntry. References are returned to a pool after Write, and MUST
// NOT be retained after calling their Write method.
type CheckedEntry struct {
	Entry
	ErrorOutput WriteSyncer
	dirty       bool // best-effort detection of pool misuse
	after       CheckWriteHook
	cores       []Core
}

func (ce *CheckedEntry) reset() {
	ce.Entry = Entry{}
	ce.ErrorOutput = nil
	ce.dirty = false
	ce.after = nil
	for i := range ce.cores {
		// don't keep references to cores
		ce.cores[i] = nil
	}
	ce.cores = ce.cores[:0]
}

// Write writes the entry to the stored Cores, returns any errors, and returns
Repository: uber-go/zap
File: array_go118_test.go (Go)

Code before:
			wantErr: "stuff failed",
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.desc, func(t *testing.T) {
			t.Parallel()

			tt.give.Key = "k"

			enc := zapcore.NewMapObjectEncoder()
			tt.give.AddTo(enc)

			require.Contains(t, enc.Fields, "k")
			assert.Equal(t, tt.want, enc.Fields["k"])

			// AddTo puts the error in a "%vError" field based on the name of the
			// original field.
			require.Contains(t, enc.Fields, "kError")
			assert.Equal(t, tt.wantErr, enc.Fields["kError"])
		})
	}
}

type stringerObject struct{
	value string
}

func (s stringerObject) String() string {
	return s.value
}

func TestStringers(t *testing.T) {
	t.Parallel()

	tests := []struct {
		desc string
		give Field
		want []any
	}{
		{
			desc: "Stringers",
			give: Stringers("", []stringerObject{
				{value: "foo"},
				{value: "bar"},
				{value: "baz"},
			}),
			want: []any{
				"foo",
				"bar",

Review comment (suggestion):
nit: lint 
```suggestion
type stringerObject struct {
```

Code after:
			},
			wantErr: "stuff failed",
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.desc, func(t *testing.T) {
			t.Parallel()

			tt.give.Key = "k"

			enc := zapcore.NewMapObjectEncoder()
			tt.give.AddTo(enc)

			require.Contains(t, enc.Fields, "k")
			assert.Equal(t, tt.want, enc.Fields["k"])

			// AddTo puts the error in a "%vError" field based on the name of the
			// original field.
			require.Contains(t, enc.Fields, "kError")
			assert.Equal(t, tt.wantErr, enc.Fields["kError"])
		})
	}
}

type stringerObject struct {
	value string
}

func (s stringerObject) String() string {
	return s.value
}

func TestStringers(t *testing.T) {
	t.Parallel()

	tests := []struct {
		desc string
		give Field
		want []any
	}{
		{
			desc: "Stringers",
			give: Stringers("", []stringerObject{
				{value: "foo"},
				{value: "bar"},
				{value: "baz"},
			}),
			want: []any{
				"foo",
Repository: uber-go/zap
File: sink.go (Go)

Code before:
import (
	"errors"
	"fmt"
	"io"
	"net/url"
	"os"
	"strings"
	"sync"

	"go.uber.org/zap/zapcore"
)

const schemeFile = "file"

var _sinkRegistry = newSinkRegistry()

// Sink defines the interface to write to and close logger destinations.
type Sink interface {
	zapcore.WriteSyncer
	io.Closer
}

type sinkRegistry struct {
	mu        sync.Mutex
	factories map[string]func(*url.URL) (Sink, error) // keyed by scheme
	openFile  func(string, int, os.FileMode) (*os.File, error)
}

type errSinkNotFound struct {
	scheme string
}

func (e *errSinkNotFound) Error() string {
	return fmt.Sprintf("no sink found for scheme %q", e.scheme)
}

type nopCloserSink struct{ zapcore.WriteSyncer }

func (nopCloserSink) Close() error { return nil }

func newSinkRegistry() *sinkRegistry {
	sr := &sinkRegistry{
		factories: make(map[string]func(*url.URL) (Sink, error)),
		openFile:  os.OpenFile,
	}
	sr.RegisterSink(schemeFile, sr.newFileSinkFromURL)
	return sr
}

// RegisterScheme registers the given factory for the specific scheme.
func (sr *sinkRegistry) RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error {

Review comment (suggestion):
consider a comment adding that this is the same signature as os.OpenFile.

```suggestion
	openFile  func(string, int, os.FileMode) (*os.File, error) // == os.OpenFile
```

Code after:
import (
	"errors"
	"fmt"
	"io"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"go.uber.org/zap/zapcore"
)

const schemeFile = "file"

var _sinkRegistry = newSinkRegistry()

// Sink defines the interface to write to and close logger destinations.
type Sink interface {
	zapcore.WriteSyncer
	io.Closer
}

type errSinkNotFound struct {
	scheme string
}

func (e *errSinkNotFound) Error() string {
	return fmt.Sprintf("no sink found for scheme %q", e.scheme)
}

type nopCloserSink struct{ zapcore.WriteSyncer }

func (nopCloserSink) Close() error { return nil }

type sinkRegistry struct {
	mu        sync.Mutex
	factories map[string]func(*url.URL) (Sink, error)          // keyed by scheme
	openFile  func(string, int, os.FileMode) (*os.File, error) // type matches os.OpenFile
}

func newSinkRegistry() *sinkRegistry {
	sr := &sinkRegistry{
		factories: make(map[string]func(*url.URL) (Sink, error)),
		openFile:  os.OpenFile,
	}
	sr.RegisterSink(schemeFile, sr.newFileSinkFromURL)
	return sr
}

// RegisterScheme registers the given factory for the specific scheme.
Repository: uber-go/zap
File: sink_windows_test.go (Go)

Code before:
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//go:build windows

package zap

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestWindowsPaths(t *testing.T) {
	sr := newSinkRegistry()

	var openFilename string
	sr.openFile = func(filename string, _ int, _ os.FileMode) (*os.File, error) {
		openFilename = filename
		return nil, assert.AnError
	}

	// See https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
	tests := []struct {
		msg  string
		path string
	}{
		{
			msg:  "local path with drive",
			path: `c:\log.json`,
		},
		{
			msg:  "local path with drive using forward slash",
			path: `c:/log.json`,
		},
		{
			msg:  "local path without drive",
			path: `\Temp\log.json`,
		},
		{
			msg:  "unc path",
			path: `\\Server2\Logs\log.json`,
		},
	}


Review comment (suggestion):
Not a blocker, but a function shared between subtests that modifies its lexical scope smells a bit.

How do you feel about pushing this setup inside the test logic as well:

```
t.Run(..., func(t *testing.T) {
  sr := newSinkRegistry()

  var openFilename string
  sr.openFile = func(...) (*os.File, error) {
    openFilename = filename
    return nil, assert.AnError
  }

  // ...

  assert.Equal(t, tt.path, openFilename, ...)
})
```

Code after:
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//go:build windows

package zap

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestWindowsPaths(t *testing.T) {
	// See https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
	tests := []struct {
		msg  string
		path string
	}{
		{
			msg:  "local path with drive",
			path: `c:\log.json`,
		},
		{
			msg:  "local path with drive using forward slash",
			path: `c:/log.json`,
		},
		{
			msg:  "local path without drive",
			path: `\Temp\log.json`,
		},
		{
			msg:  "unc path",
			path: `\\Server2\Logs\log.json`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.msg, func(t *testing.T) {
			sr := newSinkRegistry()

			openFilename := "<not called>"
			sr.openFile = func(filename string, _ int, _ os.FileMode) (*os.File, error) {
				openFilename = filename
				return nil, assert.AnError
Repository: uber-go/zap
File: exp/zapslog/example_test.go (Go)

Code before:
import (
	"context"
	"net"
	"time"

	"go.uber.org/zap"
	"golang.org/x/exp/slog"

	"go.uber.org/zap/exp/zapslog"
)

type Password string

func (p Password) LogValue() slog.Value {
	return slog.StringValue("REDACTED")
}

func Example_slog() {
	logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel))
	defer logger.Sync()

	sl := zapslog.New(logger)

	sl.Info("user", "name", "Al", "secret", Password("secret"))
	sl.Error("oops", net.ErrClosed, "status", 500)
	sl.LogAttrs(context.TODO(), slog.LevelError, "oops",
		slog.Any("err", net.ErrClosed), slog.Int("status", 500))
	sl.Info("message",
		slog.Group("group",
			slog.Float64("pi", 3.14),
			slog.Duration("1min", time.Minute),
		),
	)
	sl.WithGroup("s").LogAttrs(context.TODO(), slog.LevelWarn, "warn", slog.Uint64("u", 1), slog.Any("m", map[string]any{
		"foo": "bar",
	}))
	sl.LogAttrs(context.TODO(), slog.LevelDebug, "not show up")

	// Output:
	// {"level":"info","msg":"user","name":"Al","secret":"REDACTED"}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}}
	// {"level":"warn","msg":"warn","s":{"u":1,"m":{"foo":"bar"}}}
}


Review comment (suggestion):
We don't need to use context.TODO.
We can use context.Background instead -- it represents the process' lifetime, which is appropriate here.

```suggestion
	ctx := context.Backgrond()
	sl.LogAttrs(ctx, slog.LevelError, "oops",
```

Code after:
	"net"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/exp/zapslog"
	"golang.org/x/exp/slog"
)

type Password string

func (p Password) LogValue() slog.Value {
	return slog.StringValue("REDACTED")
}

func Example_slog() {
	logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel))
	defer logger.Sync()

	sl := slog.New(zapslog.NewHandler(logger.Core()))
	ctx := context.Background()

	sl.Info("user", "name", "Al", "secret", Password("secret"))
	sl.Error("oops", "err", net.ErrClosed, "status", 500)
	sl.LogAttrs(
		ctx,
		slog.LevelError,
		"oops",
		slog.Any("err", net.ErrClosed),
		slog.Int("status", 500),
	)
	sl.Info("message",
		slog.Group("group",
			slog.Float64("pi", 3.14),
			slog.Duration("1min", time.Minute),
		),
	)
	sl.WithGroup("s").LogAttrs(
		ctx,
		slog.LevelWarn,
		"warn msg", // message
		slog.Uint64("u", 1),
		slog.Any("m", map[string]any{
			"foo": "bar",
		}))
	sl.LogAttrs(ctx, slog.LevelDebug, "not show up")

	// Output:
	// {"level":"info","msg":"user","name":"Al","secret":"REDACTED"}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}}
Repository: uber-go/zap
File: exp/zapslog/example_test.go (Go)

Code before:
	"go.uber.org/zap/exp/zapslog"
)

type Password string

func (p Password) LogValue() slog.Value {
	return slog.StringValue("REDACTED")
}

func Example_slog() {
	logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel))
	defer logger.Sync()

	sl := zapslog.New(logger)

	sl.Info("user", "name", "Al", "secret", Password("secret"))
	sl.Error("oops", net.ErrClosed, "status", 500)
	sl.LogAttrs(context.TODO(), slog.LevelError, "oops",
		slog.Any("err", net.ErrClosed), slog.Int("status", 500))
	sl.Info("message",
		slog.Group("group",
			slog.Float64("pi", 3.14),
			slog.Duration("1min", time.Minute),
		),
	)
	sl.WithGroup("s").LogAttrs(context.TODO(), slog.LevelWarn, "warn", slog.Uint64("u", 1), slog.Any("m", map[string]any{
		"foo": "bar",
	}))
	sl.LogAttrs(context.TODO(), slog.LevelDebug, "not show up")

	// Output:
	// {"level":"info","msg":"user","name":"Al","secret":"REDACTED"}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}}
	// {"level":"warn","msg":"warn","s":{"u":1,"m":{"foo":"bar"}}}
}


Review comment (suggestion):
```suggestion
	sl.WithGroup("s").LogAttrs(ctx, slog.LevelWarn, "warn", slog.Uint64("u", 1), slog.Any("m", map[string]any{
```

Code after:
type Password string

func (p Password) LogValue() slog.Value {
	return slog.StringValue("REDACTED")
}

func Example_slog() {
	logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel))
	defer logger.Sync()

	sl := slog.New(zapslog.NewHandler(logger.Core()))
	ctx := context.Background()

	sl.Info("user", "name", "Al", "secret", Password("secret"))
	sl.Error("oops", "err", net.ErrClosed, "status", 500)
	sl.LogAttrs(
		ctx,
		slog.LevelError,
		"oops",
		slog.Any("err", net.ErrClosed),
		slog.Int("status", 500),
	)
	sl.Info("message",
		slog.Group("group",
			slog.Float64("pi", 3.14),
			slog.Duration("1min", time.Minute),
		),
	)
	sl.WithGroup("s").LogAttrs(
		ctx,
		slog.LevelWarn,
		"warn msg", // message
		slog.Uint64("u", 1),
		slog.Any("m", map[string]any{
			"foo": "bar",
		}))
	sl.LogAttrs(ctx, slog.LevelDebug, "not show up")

	// Output:
	// {"level":"info","msg":"user","name":"Al","secret":"REDACTED"}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}}
	// {"level":"warn","msg":"warn msg","s":{"u":1,"m":{"foo":"bar"}}}
}
Repository: uber-go/zap
File: exp/zapslog/example_test.go (Go)

Code before:
type Password string

func (p Password) LogValue() slog.Value {
	return slog.StringValue("REDACTED")
}

func Example_slog() {
	logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel))
	defer logger.Sync()

	sl := zapslog.New(logger)

	sl.Info("user", "name", "Al", "secret", Password("secret"))
	sl.Error("oops", net.ErrClosed, "status", 500)
	sl.LogAttrs(context.TODO(), slog.LevelError, "oops",
		slog.Any("err", net.ErrClosed), slog.Int("status", 500))
	sl.Info("message",
		slog.Group("group",
			slog.Float64("pi", 3.14),
			slog.Duration("1min", time.Minute),
		),
	)
	sl.WithGroup("s").LogAttrs(context.TODO(), slog.LevelWarn, "warn", slog.Uint64("u", 1), slog.Any("m", map[string]any{
		"foo": "bar",
	}))
	sl.LogAttrs(context.TODO(), slog.LevelDebug, "not show up")

	// Output:
	// {"level":"info","msg":"user","name":"Al","secret":"REDACTED"}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}}
	// {"level":"warn","msg":"warn","s":{"u":1,"m":{"foo":"bar"}}}
}


Review comment (bug):
```suggestion
	sl.LogAttrs(ctx, slog.LevelDebug, "not show up")
```

Code after:
	return slog.StringValue("REDACTED")
}

func Example_slog() {
	logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel))
	defer logger.Sync()

	sl := slog.New(zapslog.NewHandler(logger.Core()))
	ctx := context.Background()

	sl.Info("user", "name", "Al", "secret", Password("secret"))
	sl.Error("oops", "err", net.ErrClosed, "status", 500)
	sl.LogAttrs(
		ctx,
		slog.LevelError,
		"oops",
		slog.Any("err", net.ErrClosed),
		slog.Int("status", 500),
	)
	sl.Info("message",
		slog.Group("group",
			slog.Float64("pi", 3.14),
			slog.Duration("1min", time.Minute),
		),
	)
	sl.WithGroup("s").LogAttrs(
		ctx,
		slog.LevelWarn,
		"warn msg", // message
		slog.Uint64("u", 1),
		slog.Any("m", map[string]any{
			"foo": "bar",
		}))
	sl.LogAttrs(ctx, slog.LevelDebug, "not show up")

	// Output:
	// {"level":"info","msg":"user","name":"Al","secret":"REDACTED"}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"error","msg":"oops","err":"use of closed network connection","status":500}
	// {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}}
	// {"level":"warn","msg":"warn msg","s":{"u":1,"m":{"foo":"bar"}}}
}
Repository: uber-go/zap
File: exp/zapslog/slog.go (Go)

Code before:

//go:build zapslog

package zapslog

import (
	"context"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"golang.org/x/exp/slog"
)

type handler struct {
	logger *zap.Logger
}

var _ slog.Handler = (*handler)(nil)

// group holds all the Attrs saved in a slog.GroupValue.
type group struct {
	attrs []slog.Attr
}

func (g *group) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	for _, attr := range g.attrs {
		convertAttrToField(attr).AddTo(enc)
	}
	return nil
}

func convertAttrToField(attr slog.Attr) zapcore.Field {
	switch attr.Value.Kind() {
	case slog.KindBool:
		return zap.Bool(attr.Key, attr.Value.Bool())
	case slog.KindDuration:
		return zap.Duration(attr.Key, attr.Value.Duration())
	case slog.KindFloat64:
		return zap.Float64(attr.Key, attr.Value.Float64())
	case slog.KindInt64:
		return zap.Int64(attr.Key, attr.Value.Int64())
	case slog.KindString:
		return zap.String(attr.Key, attr.Value.String())
	case slog.KindTime:
		return zap.Time(attr.Key, attr.Value.Time())
	case slog.KindUint64:
		return zap.Uint64(attr.Key, attr.Value.Uint64())
	case slog.KindGroup:
		return zap.Object(attr.Key, &group{attrs: attr.Value.Group()})
	case slog.KindLogValuer:
		return convertAttrToField(slog.Attr{

Review comment (suggestion):
Two things:

- We don't need a struct here. Just a `type group []slog.Attr` should be enough.
- 'group' is too general a name for package-level scope. We should qualify it to something more specific that is less likely to collide with a variable or package name elsewhere. `groupObject` may be better.

```suggestion
// groupObject holds all the Attrs saved in a slog.GroupValue.
type groupObject []slog.Attr

func (gs groupObject) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	for _, attr := range gs {
```

Code after:

package zapslog

import (
	"context"
	"runtime"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"golang.org/x/exp/slog"
)

// Handler implements the slog.Handler by writing to a zap Core.
type Handler struct {
	core      zapcore.Core
	name      string // logger name
	addSource bool
}

// HandlerOptions are options for a Zap-based [slog.Handler].
type HandlerOptions struct {
	// LoggerName is used for log entries received from slog.
	//
	// Defaults to empty.
	LoggerName string

	// AddSource configures the handler to annotate each message with the filename,
	// line number, and function name.
	// AddSource is false by default to skip the cost of computing
	// this information.
	AddSource bool
}

// New builds a [Handler] that writes to the supplied [zapcore.Core].
// This handler may be supplied to [slog.New] to create a new [slog.Logger].
func (opts HandlerOptions) New(core zapcore.Core) *Handler {
	return &Handler{
		core:      core,
		name:      opts.LoggerName,
		addSource: opts.AddSource,
	}
}

// NewHandler builds a [Handler] that writes to the supplied [zapcore.Core]
// with the default options.
func NewHandler(core zapcore.Core) *Handler {
	var opts HandlerOptions
	return opts.New(core)
}

var _ slog.Handler = (*Handler)(nil)