Skip to content

Commit ae37381

Browse files
authored
feat: add logging support to generated clients (#1577)
This change instruments all generated clients with logging. In order to reduce the amount of codegen this would take I refactored some common logic about executing the request and parsing the response into a common helper. This change should reduce LoC for generated clients as a nice side-effect.
1 parent 2b705af commit ae37381

35 files changed

+298
-187
lines changed

internal/gengapic/client_init_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ func TestClientInit(t *testing.T) {
414414
{Name: "iampb", Path: "cloud.google.com/go/iam/apiv1/iampb"}: true,
415415
{Name: "locationpb", Path: "google.golang.org/genproto/googleapis/cloud/location"}: true,
416416
{Name: "mypackagepb", Path: "github.com/googleapis/mypackage"}: true,
417+
{Path: "log/slog"}: true,
417418
},
418419
wantNumSnps: 6,
419420
},
@@ -436,6 +437,7 @@ func TestClientInit(t *testing.T) {
436437
{Name: "iampb", Path: "cloud.google.com/go/iam/apiv1/iampb"}: true,
437438
{Name: "locationpb", Path: "google.golang.org/genproto/googleapis/cloud/location"}: true,
438439
{Name: "mypackagepb", Path: "github.com/googleapis/mypackage"}: true,
440+
{Path: "log/slog"}: true,
439441
},
440442
wantNumSnps: 6,
441443
},
@@ -453,6 +455,7 @@ func TestClientInit(t *testing.T) {
453455
{Name: "gtransport", Path: "google.golang.org/api/transport/grpc"}: true,
454456
{Name: "mypackagepb", Path: "github.com/googleapis/mypackage"}: true,
455457
{Name: "httptransport", Path: "google.golang.org/api/transport/http"}: true,
458+
{Path: "log/slog"}: true,
456459
},
457460
wantNumSnps: 1,
458461
},
@@ -472,6 +475,7 @@ func TestClientInit(t *testing.T) {
472475
{Path: "context"}: true,
473476
{Path: "google.golang.org/api/option"}: true,
474477
{Path: "google.golang.org/grpc"}: true,
478+
{Path: "log/slog"}: true,
475479
},
476480
wantNumSnps: 6,
477481
},
@@ -489,6 +493,7 @@ func TestClientInit(t *testing.T) {
489493
{Path: "google.golang.org/api/option/internaloption"}: true,
490494
{Path: "google.golang.org/grpc"}: true,
491495
{Path: "net/http"}: true,
496+
{Path: "log/slog"}: true,
492497
},
493498
wantNumSnps: 1,
494499
},
@@ -506,6 +511,7 @@ func TestClientInit(t *testing.T) {
506511
{Path: "net/http"}: true,
507512
{Name: "httptransport", Path: "google.golang.org/api/transport/http"}: true,
508513
{Name: "mypackagepb", Path: "github.com/googleapis/mypackage"}: true,
514+
{Path: "log/slog"}: true,
509515
},
510516
wantNumSnps: 1,
511517
setExt: func() (protoreflect.ExtensionType, interface{}) {

internal/gengapic/gengapic.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ func (g *generator) genAndCommitHelpers(scopes []string) error {
232232
g.reset()
233233
g.imports[pbinfo.ImportSpec{Path: "context"}] = true
234234
g.imports[pbinfo.ImportSpec{Path: "google.golang.org/api/option"}] = true
235+
g.imports[pbinfo.ImportSpec{Path: "github.com/googleapis/gax-go/v2/internallog"}] = true
236+
237+
p("const serviceName = %q", g.serviceConfig.GetName())
238+
p("")
235239

236240
p("// For more information on implementing a client constructor hook, see")
237241
p("// https://github.com/googleapis/google-cloud-go/wiki/Customizing-constructors.")
@@ -257,6 +261,64 @@ func (g *generator) genAndCommitHelpers(scopes []string) error {
257261
}
258262
p(" }")
259263
p("}")
264+
p("")
265+
if containsTransport(g.opts.transports, rest) {
266+
g.imports[pbinfo.ImportSpec{Path: "io"}] = true
267+
g.imports[pbinfo.ImportSpec{Path: "log/slog"}] = true
268+
g.imports[pbinfo.ImportSpec{Path: "net/http"}] = true
269+
g.imports[pbinfo.ImportSpec{Path: "google.golang.org/api/googleapi"}] = true
270+
271+
p("func executeHTTPRequest(ctx context.Context, client *http.Client, req *http.Request, logger *slog.Logger, body []byte, rpc string) ([]byte, error) {")
272+
p(` logger.DebugContext(ctx, "api request", "serviceName", serviceName, "rpcName", rpc, "request", internallog.HTTPRequest(req, body))`)
273+
p(" resp, err := client.Do(req)")
274+
p(" if err != nil{")
275+
p(" return nil, err")
276+
p(" }")
277+
p(" defer resp.Body.Close()")
278+
p(" buf, err := io.ReadAll(resp.Body)")
279+
p(" if err != nil {")
280+
p(" return nil, err")
281+
p(" }")
282+
p(` logger.DebugContext(ctx, "api response", "serviceName", serviceName, "rpcName", rpc, "response", internallog.HTTPResponse(resp, buf))`)
283+
p(" if err = googleapi.CheckResponse(resp); err != nil {")
284+
p(" return nil, err")
285+
p(" }")
286+
p(" return buf, nil")
287+
p("}")
288+
p("")
289+
290+
p("func executeStreamingHTTPRequest(ctx context.Context, client *http.Client, req *http.Request, logger *slog.Logger, body []byte, rpc string) (*http.Response, error) {")
291+
p(` logger.DebugContext(ctx, "api request", "serviceName", serviceName, "rpcName", rpc, "request", internallog.HTTPRequest(req, body))`)
292+
p(" resp, err := client.Do(req)")
293+
p(" if err != nil{")
294+
p(" return nil, err")
295+
p(" }")
296+
p(` logger.DebugContext(ctx, "api response", "serviceName", serviceName, "rpcName", rpc, "response", internallog.HTTPResponse(resp, nil))`)
297+
p(" if err = googleapi.CheckResponse(resp); err != nil {")
298+
p(" return nil, err")
299+
p(" }")
300+
p(" return resp, nil")
301+
p("}")
302+
p("")
303+
}
304+
305+
if containsTransport(g.opts.transports, grpc) {
306+
g.imports[pbinfo.ImportSpec{Path: "github.com/googleapis/gax-go/v2/internallog/grpclog"}] = true
307+
g.imports[pbinfo.ImportSpec{Path: "google.golang.org/grpc"}] = true
308+
g.imports[pbinfo.ImportSpec{Path: "google.golang.org/protobuf/proto"}] = true
309+
310+
p("func executeRPC[I proto.Message, O proto.Message](ctx context.Context, fn func(context.Context, I, ...grpc.CallOption) (O, error), req I, opts []grpc.CallOption, logger *slog.Logger, rpc string) (O, error) {")
311+
p(" var zero O")
312+
p(` logger.DebugContext(ctx, "api request", "serviceName", serviceName, "rpcName", rpc, "request", grpclog.ProtoMessageRequest(ctx, req))`)
313+
p(" resp, err := fn(ctx, req, opts...)")
314+
p(" if err != nil {")
315+
p(" return zero, err")
316+
p(" }")
317+
p(` logger.DebugContext(ctx, "api response", "serviceName", serviceName, "rpcName", rpc, "response", grpclog.ProtoMessageResponse(resp))`)
318+
p(" return resp, err")
319+
p("}")
320+
p("")
321+
}
260322

261323
outFile := filepath.Join(g.opts.outDir, "helpers.go")
262324
g.commit(outFile, g.opts.pkgName)

internal/gengapic/gengapic_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,12 +1128,12 @@ func TestGRPCStubCall(t *testing.T) {
11281128
}{
11291129
{
11301130
name: "foo.FooService.GetFoo",
1131-
want: "c.client.GetFoo(ctx, req, settings.GRPC...)",
1131+
want: `executeRPC(ctx, c.client.GetFoo, req, settings.GRPC, c.logger, "GetFoo")`,
11321132
in: getFoo,
11331133
},
11341134
{
11351135
name: "foo.BarService.GetBar",
1136-
want: "c.barClient.GetBar(ctx, req, settings.GRPC...)",
1136+
want: `executeRPC(ctx, c.barClient.GetBar, req, settings.GRPC, c.logger, "GetBar")`,
11371137
in: getBar,
11381138
},
11391139
} {

internal/gengapic/gengrpc.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (g *generator) emptyUnaryGRPCCall(servName string, m *descriptorpb.MethodDe
162162
func (g *generator) grpcStubCall(method *descriptorpb.MethodDescriptorProto) string {
163163
service := g.descInfo.ParentElement[method]
164164
stub := pbinfo.ReduceServName(service.GetName(), g.opts.pkgName)
165-
return fmt.Sprintf("c.%s.%s(ctx, req, settings.GRPC...)", grpcClientField(stub), method.GetName())
165+
return fmt.Sprintf("executeRPC(ctx, c.%s.%s, req, settings.GRPC, c.logger, %q)", grpcClientField(stub), method.GetName(), method.GetName())
166166
}
167167

168168
func (g *generator) grpcClientOptions(serv *descriptorpb.ServiceDescriptorProto, servName string) error {
@@ -295,6 +295,8 @@ func (g *generator) grpcClientInit(serv *descriptorpb.ServiceDescriptorProto, se
295295

296296
p("// The x-goog-* metadata to be sent with each request.")
297297
p("xGoogHeaders []string")
298+
p("")
299+
p("logger *slog.Logger")
298300

299301
p("}")
300302
p("")
@@ -303,6 +305,7 @@ func (g *generator) grpcClientInit(serv *descriptorpb.ServiceDescriptorProto, se
303305
g.imports[imp] = true
304306

305307
g.grpcClientUtilities(serv, servName, imp, hasRPCForLRO)
308+
g.imports[pbinfo.ImportSpec{Path: "log/slog"}] = true
306309
}
307310

308311
func (g *generator) grpcClientUtilities(serv *descriptorpb.ServiceDescriptorProto, servName string, imp pbinfo.ImportSpec, hasRPCForLRO bool) {
@@ -337,6 +340,7 @@ func (g *generator) grpcClientUtilities(serv *descriptorpb.ServiceDescriptorProt
337340
p(" connPool: connPool,")
338341
p(" %s: %s.New%sClient(connPool),", grpcClientField(servName), imp.Name, serv.GetName())
339342
p(" CallOptions: &client.CallOptions,")
343+
p(" logger: internaloption.GetLogger(opts),")
340344
g.mixinStubsInit()
341345
p("")
342346
p(" }")

0 commit comments

Comments
 (0)