Trace and Transport logs for Google Cloud Client Libraries

2021-12-15

Just about every developer at some stage needs to see the raw-request/response stream to “know” the correct payload is being sent or to observe the raw response exception.

gcloud cli provides the (--log-http) switch for just such a scenario.

How would cloud SDK users log the request/response?

This section covers some flags and configurations you can enable to see the trace logs for both http-based clients (GCS) and gRPC-based clients (PubSub)

For the most part, gRPC clients that are ultimately based in C will honor the standard debug gRPC Environment Variables:

export GRPC_VERBOSITY=DEBUG 
export GRPC_TRACE=all

A notible exception is golang which has some of its own variables as shown in below.


Note, there are much more low-level and comprehensive ways to trace everything.

For example, you can (in golang atleast) decrypt the gRPC traffic automatically with SSLKKEYLOG files and wireshark

or thorough an intercepting proxy.

However, its a fair amount of work to get that setup so this section covers easier, environment-variable based changes.

set HTTPConnection.debuglevel for HTTP clients and the gRPC environment variables

as in:

project='your_project_id'

from http.client import HTTPConnection # py3
# https://docs.python.org/3/library/http.client.html
HTTPConnection.debuglevel = 1

from google.cloud import storage
client = storage.Client(project=project)
for b in client.list_buckets():
   print(b.name)


# export GRPC_VERBOSITY=DEBUG GRPC_TRACE=all
from google.cloud import pubsub_v1
publisher = pubsub_v1.PublisherClient()
project_path = f"projects/{project}"
for topic in publisher.list_topics(request={"project": project_path}):
  print(topic.name)

for gRPC based clients:

export GRPC_GO_LOG_VERBOSITY_LEVEL=99
export GRPC_GO_LOG_SEVERITY_LEVEL=info

for httpClients, you can wrap the client through http.RoundTripper and use the net/http flags

as in:

package main

import (
	"fmt"
	"log"
	"net/http"

	pubsub "cloud.google.com/go/pubsub"
	storage "cloud.google.com/go/storage"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
)

const (
	projectID = "your_project_id"
)

// https://github.com/googleapis/google-cloud-go/issues/487
// https://github.com/grpc/grpc/blob/master/doc/environment_variables.md

// export GRPC_GO_LOG_SEVERITY_LEVEL=info
// export GRPC_GO_LOG_VERBOSITY_LEVEL=99

// https://pkg.go.dev/net/http
// GODEBUG=http2client=0  # disable HTTP/2 client support
// GODEBUG=http2server=0  # disable HTTP/2 server support
// GODEBUG=http2debug=1   # enable verbose HTTP/2 debug logs
// GODEBUG=http2debug=2   # ... even more verbose, with frame dumps
// export GODEBUG=http2debug=2

type wrapped struct {
	base http.RoundTripper
}

func (w wrapped) RoundTrip(r *http.Request) (*http.Response, error) {
	fmt.Println("=======================================")
	fmt.Println("[GCS_REQUEST]")
	fmt.Printf("%v %v\n", r.Method, r.URL.String())
	for h, values := range r.Header {
		for _, v := range values {
			fmt.Printf("%v: %v\n", h, v)
		}
	}
	resp, err := w.base.RoundTrip(r)
	fmt.Println("[GCS_RESPONSE]")
	if err != nil {
		fmt.Printf("GCS_ERROR\n: %v", err)
		return resp, err
	}
	for h, values := range resp.Header {
		for _, v := range values {
			fmt.Printf("%v: %v\n", h, v)
		}
	}
	return resp, err
}
func main() {

	ctx := context.Background()

	hc, err := google.DefaultClient(ctx)
	if err != nil {
		log.Fatalf("%v", err)
	}
	hc.Transport = wrapped{hc.Transport}
	storageClient, err := storage.NewClient(ctx, option.WithHTTPClient(hc))

	//storageClient, err := storage.NewClient(ctx)
	if err != nil {
		fmt.Printf("storage.NewClient: %v", err)
		return
	}
	defer storageClient.Close()

	it := storageClient.Buckets(ctx, projectID)
	for {
		battrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			fmt.Printf("storage.Iterating error: %v", err)
			return
		}
		fmt.Printf("Bucket Name: %s\n", battrs.Name)
	}

	// export GRPC_VERBOSITY=DEBUG
	// export GRPC_TRACE=all
	// export GRPC_GO_LOG_VERBOSITY_LEVEL=99
	// export GRPC_GO_LOG_SEVERITY_LEVEL=info
	pubsubClient, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		fmt.Printf("pubsub.NewClient: %v", err)
		return
	}
	defer pubsubClient.Close()

	pit := pubsubClient.Topics(ctx)
	for {
		topic, err := pit.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			fmt.Printf("pubssub.Iterating error: %v", err)
			return
		}
		fmt.Printf("Topic Name: %s\n", topic.ID())
	}
}

Use Logger with io.grpc and com.google.api.client

as in:

package com.test;

import com.google.cloud.pubsub.v1.TopicAdminClient;
import com.google.cloud.pubsub.v1.TopicAdminClient.ListTopicsPagedResponse;
import com.google.cloud.pubsub.v1.TopicAdminSettings;
import com.google.cloud.storage.Bucket;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.pubsub.v1.ListTopicsRequest;
import com.google.pubsub.v1.ProjectName;
import com.google.pubsub.v1.Topic;

import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class TestApp {
	public static void main(String[] args) {
		TestApp tc = new TestApp();
	}

	public TestApp() {
		try {

			ConsoleHandler consoleHandler = new ConsoleHandler();
			consoleHandler.setLevel(Level.ALL);
			consoleHandler.setFormatter(new SimpleFormatter());

			Logger logger = Logger.getLogger("com.google.api.client");
			logger.setLevel(Level.ALL);
			logger.addHandler(consoleHandler);


			Logger gl = Logger.getLogger("io.grpc");
			gl.setLevel(Level.FINE);
			gl.addHandler(consoleHandler);

			Storage storage_service = StorageOptions.newBuilder().build().getService();
			for (Bucket b : storage_service.list().iterateAll()) {
				System.out.println(b);
			}

			TopicAdminClient topicClient = TopicAdminClient.create(TopicAdminSettings.newBuilder().build());

			ListTopicsRequest listTopicsRequest = ListTopicsRequest.newBuilder()
					.setProject(ProjectName.format("your_project_id"))
					.build();

			ListTopicsPagedResponse response = topicClient.listTopics(listTopicsRequest);
			Iterable<Topic> topics = response.iterateAll();
			for (Topic topic : topics)
				System.out.println(topic);

		} catch (Exception ex) {
			System.out.println("Error: " + ex);
		}
	}

}
# for http
export DEBUG=retry-request
export NODE_DEBUG=http

# for gRPC
export GRPC_VERBOSITY=DEBUG
export GRPC_TRACE=all

as in:

var log4js = require("log4js");
var logger = log4js.getLogger();

var { GoogleAuth } = require('google-auth-library');

const {PubSub} = require('@google-cloud/pubsub');
const {Storage} = require('@google-cloud/storage');

async function main() {
	// export DEBUG=retry-request
	// export NODE_DEBUG=http
	var gcs = new Storage();
	const [buckets] = await  gcs.getBuckets();
  	buckets.forEach(bucket => logger.info(bucket.name));

	// export GRPC_VERBOSITY=DEBUG
	// export GRPC_TRACE=all
	const pubSubClient = new PubSub({
		projectId: 'your_project_id'
	});

	const [topics] = await pubSubClient.getTopics();
	topics.forEach(topic => logger.info(topic.name));
}

main().catch(console.error);

For gRPC use the standard environment variable

for http its still a TODO

http Uses System.Net.Http; but i don’t know how to enable logging via env-var or config file

using System;

using Google.Cloud.Storage.V1;
using Google.Cloud.PubSub.V1;
using System.Threading.Tasks;

using Google.Api.Gax.ResourceNames;
using Google.Api.Gax.Grpc;

namespace main
{
    class Program
    {
        const string projectID = "your_project_id";
        [STAThread]
        static void Main(string[] args)
        {
            new Program().Run().Wait();
        }

        private Task Run()
        {
            var client = StorageClient.Create();

            // TODO
            // Uses System.Net.Http; but i don't know how to enable logging via env-var or config file
            // https://github.com/googleapis/google-api-dotnet-client/blob/main/Src/Support/Google.Apis.Core/Http/HttpClientFactory.cs#L18
            foreach (var b in client.ListBuckets(projectID))
                Console.WriteLine(b.Name);
            // export GRPC_VERBOSITY=DEBUG
            // export GRPC_TRACE=all
            PublisherServiceApiClient publisher = PublisherServiceApiClient.Create();
            ProjectName projectName = ProjectName.FromProject(projectID);

            foreach (Topic t in publisher.ListTopics(projectName))
                Console.WriteLine(t.MessageStoragePolicy);
            return Task.CompletedTask;
        }
    }
}

This site supports webmentions. Send me a mention via this form.