-
Notifications
You must be signed in to change notification settings - Fork 66
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
Add context logger; use context logger for HTTP client request log #444
Conversation
req := zanzibar.NewClientHTTPRequest(ctx, "bar", "echo", client) | ||
|
||
err = req.WriteJSON("POST", baseURL+"/bar/echo", nil, myJson{}) | ||
assert.NoError(t, err) | ||
res, err := req.Do(context.Background()) | ||
res, err := req.Do() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this, as long as ctx inside req is not mutated
will the behavior for deadline change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see that there is a difference for deadline propagation. It was redundant before since there were no mutations done to the context, but now WriteJSON
modifies the context so it's actually confusing / misleading to pass context into Do()
.
runtime/context.go
Outdated
@@ -23,7 +23,10 @@ package zanzibar | |||
import ( | |||
"context" | |||
|
|||
"github.com/uber/zanzibar/internal/runtime" | |||
|
|||
"github.com/pborman/uuid" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I heard https://github.com/satori/go.uuid is more popular now
runtime/context.go
Outdated
@@ -34,6 +37,13 @@ const ( | |||
routingDelegateKey = contextFieldKey("rd") | |||
) | |||
|
|||
const ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not open to extension in this PR? 😿
runtime/context.go
Outdated
|
||
// WithLogFields is a helper function to add the log field to the context | ||
func WithLogFields(ctx context.Context, fields ...zap.Field) context.Context { | ||
lg := Logger(ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: just use log :)
runtime/context.go
Outdated
logFieldRequestMethod = "method" | ||
logFieldRequestURL = "url" | ||
logFieldRequestStartTime = "timestamp-started" | ||
logFieldRequestHeaderPrefix = "Request-Header" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: seems like you're implicating case sensitive, is this intended ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this change is quite different from what I thought it is supposed to be. Don't we want something like logger.Info(ctx, msg, fields...)
?
It is also noticeable that this changes creates 2 child logger per client request, which, @cl1337 have shown in #425, is not a good idea performance wise.
func (req *ClientHTTPRequest) Do( | ||
ctx context.Context, | ||
) (*ClientHTTPResponse, error) { | ||
func (req *ClientHTTPRequest) Do() (*ClientHTTPResponse, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since now the Do
method no longer takes the context parameter and ctx
is a private field on ClientHTTPRequest
, we should update the comments here and here to explicitly call out that the context given during the constructor call is attached to the ClientHTTPRequest
and all its methods behave according to that context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the comments, but it was sort of implied before (since started
and startTime
make this class not reusable).
runtime/context.go
Outdated
@@ -81,3 +91,27 @@ func GetRoutingDelegateFromCtx(ctx context.Context) string { | |||
} | |||
return "" | |||
} | |||
|
|||
// Logger returns a request-scoped logger, a logger with request headers added as log fields. | |||
func Logger(ctx context.Context) *zap.Logger { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just looking at the function signature, I had the false impression that a new logger is created. Maybe something like GetLoggerFrom
is more descriptive.
runtime/context.go
Outdated
return logger | ||
} | ||
|
||
// WithLogFields is a helper function to add the log field to the context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is misleading, the function actually adds log fields to the logger on the context (also involves a child logger creation) instead of "add the log field to the context". The function name WithLogFields
with its signature does create an conventional impression that is consistent with what the comment says, but that is not what really happens here.
runtime/client_http_response.go
Outdated
@@ -156,27 +156,18 @@ func (res *ClientHTTPResponse) finish() { | |||
} | |||
|
|||
// write logs | |||
res.req.Logger.Info( | |||
Logger(res.req.ctx).Info( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite different from what we discussed, I thought what we want is logger.Info(ctx, msg, fields...)
.
@@ -149,24 +149,40 @@ func (req *ClientHTTPRequest) WriteJSON( | |||
httpReq.Header.Set("Content-Type", "application/json") | |||
|
|||
req.httpReq = httpReq | |||
req.ctx = WithLogFields(req.ctx, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WithLogFields
creates a child logger and this is inside the WriteJSON
call, which means we are creating a child logger per client request here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. The context child logger should be created once for the client and we register all the context fields that we need to fetch from context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lu's comment refers to an earlier diff where we were creating a new *zap.Logger
each time. Now instead we are just mutating the []zap.Fields
each time.
runtime/client_http_request.go
Outdated
|
||
logFields = append(logFields, zap.String(fmt.Sprintf("%s-%s", logFieldRequestHeaderPrefix, k), v[0])) | ||
} | ||
ctx = WithLogFields(ctx, logFields...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is another callsite of WithLogFields
, plus the one in WriteJSON
, we are creating 2 child loggers per client request.
4d1c19b
to
8ab384d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you plan to have the context logger in http endpoint in a separate PR?
runtime/context.go
Outdated
} | ||
|
||
// ContextLogger is a logger that extracts some log fields from the context before passing through to underlying zap logger. | ||
type ContextLogger interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it. We should write some tests for the ContextLogger
, it will be great to have some benchmark tests to compare with vanilla zap.Logger so that we don't have any surprises.
runtime/context.go
Outdated
log *zap.Logger | ||
} | ||
|
||
func (c *contextLogger) joinFields(ctx context.Context, userFields []zap.Field) []zap.Field { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this function almost identical to WithLogFields
, maybe reuse.
runtime/context.go
Outdated
return fields | ||
} | ||
|
||
func (c *contextLogger) Debug(ctx context.Context, msg string, userFields ...zap.Field) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the linter complain missing comments for public methods?
@@ -61,6 +61,7 @@ func NewClientHTTPRequest( | |||
MethodName: methodName, | |||
client: client, | |||
Logger: client.loggers[methodName], | |||
ContextLogger: NewContextLogger(client.loggers[methodName]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we have a proper context logger, we will no longer need to create a child logger per method as in https://github.com/uber/zanzibar/blob/master/runtime/http_client.go#L62-L68, we only need to include the method name in the context log fields. That could be in different PR tho (please create an issue to remind us if you decide to have that done separately).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it'll be done in a separate diff.
) | ||
|
||
const ( | ||
logFieldRequestMethod = "method" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How should we differentiate the field between client and endpoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm preserving these log fields as they were before, open to better names. endpoint-http-method
, endpoint-url
, etc. seem better to me. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works, we should also think about how these can be customizable so that user can define log fields.
8ab384d
to
7ffd245
Compare
Adds a new logger to
DefaultDeps
that has an API like:Cases for log fields:
NewContextLogger()
should have this field already populated in zap fields.NewContextLogger
with endpoint's zap logger preallocated to haveendpointID
fieldclient.loggers[methodName]
. In the future we will need to injectendpointID
into context like other request fields to support logs emitted by user code, which makesclients.loggers
redundant.zanzibar/runtime.WithLogFields(ctx, ...)
to mutatecontext.Context
.zanzibar/runtime.WithLogFields(ctx, ...)
to mutatecontext.Context
similar to (3).WithLogFields(ctx)
API similar to (3).This diff leverages previously written tests (#441).