Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ctxzap: add convenience functions #407

Closed
CAFxX opened this issue Apr 7, 2021 · 6 comments
Closed

ctxzap: add convenience functions #407

CAFxX opened this issue Apr 7, 2021 · 6 comments

Comments

@CAFxX
Copy link
Contributor

CAFxX commented Apr 7, 2021

In our codebases some very common patterns are emerging.

Either:

func foo(ctx context.Context, ...) (...) {
	logger := ctxzap.Extract(ctx)

	// later on

	if err := bar(...); err != nil {
		logger.Error("bar failed", zap.Error(err))
	}

	// other similar uses of logger follow...
}

or, especially if there is a single use of logger:

func foo(ctx context.Context, ...) (...) {
	// later on

	if err := bar(...); err != nil {
		ctxzap.Extract(ctx).Error("bar failed", zap.Error(err))
	}
}

The latter is not terrible, but it's repetitive and noisy. The former has some non-obvious interactions with other related functions (see below for details). And the existence of both ways to do the same thing leads to overhead (and sometimes bikeshedding) during code reviews. 😁

I would like to suggest, in the same spirit as fmt, math/rand, net, net/http, and other packages in the standard library, to define convenience short-form versions of the most common zap.Logger methods (Debug, Info, Warn, Error), similar to:

// Error is equivalent to calling Error on the zap.Logger in the context.
func Error(ctx context.Context, msg string, fields ...zap.Field) {
	Extract(ctx).Error(msg, fields...)
}

this would help unify the logging idiom (in most cases) to:

func foo(ctx context.Context, ...) (...) {
	// later on

	if err := bar(...); err != nil {
		ctxzap.Error(ctx, "bar failed", zap.Error(err))
	}

	// other similar uses follow...
}

This may superficially look like a small improvement, but it compounds quickly for large/complex services. This is especially true for time spent in style discussions during code review, that in the past have also overlooked interactions between the "single Extract call" style (the first one at the top) and its non-obvious interactions with ctxzap.AddFields, ctxzap.ToContext, and other calls to ctxzap.Extract.

@johanbrandhorst
Copy link
Collaborator

Thanks for your issue! I think this sounds reasonable, though it's effectively the same as just replacing all your logger.XXX call sites with ctxzap.Extract(ctx).XXX, right?

@CAFxX
Copy link
Contributor Author

CAFxX commented Apr 7, 2021

Correct. The main benefit comes from not having to argue about how this should be done over PRs, as well as indicating to users the golden path that prevents them from shooting their own foot (see the bit about interactions with other functions).

Reducing visual noise is the icing on the cake.

@johanbrandhorst
Copy link
Collaborator

I'm not familiar enough with the package so say, but do we risk introducing confusing user behaviour? What happens if there's no logger in the context if you call ctxzap.Error?

@CAFxX
Copy link
Contributor Author

CAFxX commented Apr 7, 2021

Extract already takes care of that by returning a nop Logger @johanbrandhorst

l, ok := ctx.Value(ctxMarkerKey).(*ctxLogger)
if !ok || l == nil {
return nullLogger
}

nullLogger = zap.NewNop()

@johanbrandhorst
Copy link
Collaborator

That's not terribly comforting but I guess we're not making things worse. Would you like to make this contribution?

@CAFxX
Copy link
Contributor Author

CAFxX commented Apr 9, 2021

@johanbrandhorst opened #408

CAFxX added a commit to CAFxX/go-grpc-middleware that referenced this issue Apr 9, 2021
CAFxX added a commit to CAFxX/go-grpc-middleware that referenced this issue Apr 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants