Repository: uber-go/zap
File: exp/zapslog/slog.go (Go)

Code before:
	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{
			Key:   attr.Key,
			Value: attr.Value.Resolve(),
		})
	default:
		return zap.Any(attr.Key, attr.Value.Any())
	}
}

// convertSlogLevel maps slog Levels to zap Levels.
// Note that there is some room between slog levels while zap levels are continuous, so we can't 1:1 map them.
// See also https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md?pli=1#levels
func convertSlogLevel(l slog.Level) zapcore.Level {
	switch {
	case l >= slog.LevelError:
		return zapcore.ErrorLevel
	case l >= slog.LevelWarn:
		return zapcore.WarnLevel
	case l >= slog.LevelInfo:
		return zapcore.InfoLevel
	default:
		return zapcore.DebugLevel
	}
}

func (h *handler) Enabled(ctx context.Context, level slog.Level) bool {
	return h.logger.Core().Enabled(convertSlogLevel(level))
}


Review comment (performance):
This will resolve the LogValuer eagerly, which will hurt anyone using LogValuer to defer expensive operations.

Ideally, we want to use a wrapper struct that calls LogValue() at serialization time,
but that may not be 100% straightforward because a LogValuer doesn't know if it's a group or a primitive type until calling LogValue(),
whereas Zap wants to know that in advance.
This may require a lazy field-equivalent in Zap.
We can defer designing that to a follow-up. 
CC @mway @prashantv @sywhang 

Code after:
	// 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)

// 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 {
		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: