grpc/grpc-go · critical

cannot use component logger as grpclog logger

Error message

cannot use component logger as grpclog logger

What it means

grpclog.SetLoggerV2 (grpclog/loggerv2.go:35) installs the process-wide gRPC logger. componentData (grpclog/component.go:26) is the internal per-component logger returned by grpclog.Component(name): it does NOT log on its own, it delegates to the global logger and prefixes a component name. Setting a componentData as the global logger would create a recursive delegation (global -> component -> global) and break the contract, so SetLoggerV2 explicitly type-asserts against *componentData and panics at line 36-38.

Solutions

  1. Pass a logger built with grpclog.NewLoggerV2 / NewLoggerV2WithVerbosity, or an adapter for logrus/zap/zaplogr that implements grpclog.LoggerV2.
  2. If you want component-prefixed output, configure the underlying global logger with prefixes; do not feed a Component() back into SetLoggerV2.
  3. Check the concrete type is not the internal component logger before calling (it is unexported, so the practical check is: never pass a value returned by grpclog.Component).

Example fix

// before
grpclog.SetLoggerV2(grpclog.Component("myapp")) // panic: component logger

// after
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr))
// or with an adapter, e.g.:
// grpclog.SetLoggerV2(zaplogger.New(zap.L()))
Defensive patterns

Strategy: validation

Validate before calling

// Component() returns the internal *componentData (unexported), so the only safe rule is:
// never pass a grpclog.Component(...) result to SetLoggerV2.
func setGlobalLogger(l grpclog.LoggerV2) {
    // l must come from NewLoggerV2 or an external adapter, not Component().
    grpclog.SetLoggerV2(l)
}

Type guard

// componentData is unexported, so callers cannot name it directly.
// There is no public marker; the practical guard is: never pass a value
// obtained from grpclog.Component(...) into SetLoggerV2.
func looksLikeComponentLogger(l grpclog.LoggerV2) bool { return false }

Prevention

When it happens

Trigger: Calling grpclog.SetLoggerV2(grpclog.Component("myapp")) - i.e. passing the result of Component() instead of a real logger implementation.

Common situations: Wanting named/prefixed logging and reaching for Component() in the wrong place; copy-pasting a logging setup snippet that assigned the component logger variable to SetLoggerV2; confusing the global-logger setter with the component-logger factory.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/6ed7f38863dc18fb. Report an issue: GitHub.

Appendix: source

Thrown at grpclog/loggerv2.go:37

package grpclog

import (
	"io"
	"os"
	"strconv"
	"strings"

	"google.golang.org/grpc/grpclog/internal"
)

// LoggerV2 does underlying logging work for grpclog.
type LoggerV2 internal.LoggerV2

// SetLoggerV2 sets logger that is used in grpc to a V2 logger.
// Not mutex-protected, should be called before any gRPC functions.
func SetLoggerV2(l LoggerV2) {
	if _, ok := l.(*componentData); ok {
		panic("cannot use component logger as grpclog logger")
	}
	internal.LoggerV2Impl = l
	internal.DepthLoggerV2Impl, _ = l.(internal.DepthLoggerV2)
}

// NewLoggerV2 creates a loggerV2 with the provided writers.
// Fatal logs will be written to errorW, warningW, infoW, followed by exit(1).
// Error logs will be written to errorW, warningW and infoW.
// Warning logs will be written to warningW and infoW.
// Info logs will be written to infoW.
func NewLoggerV2(infoW, warningW, errorW io.Writer) LoggerV2 {
	return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{})
}

// NewLoggerV2WithVerbosity creates a loggerV2 with the provided writers and
// verbosity level.
func NewLoggerV2WithVerbosity(infoW, warningW, errorW io.Writer, v int) LoggerV2 {
	return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{Verbosity: v})

View on GitHub (pinned to 0c51461d27)