Send a JSON Payload with labels using Google Cloud Logging Python library

Question:

I’m using the google-cloud-logging Python client library with Cloud Functions. I’m following recommendations and using the standard Python logging library. v3+ of this library supports logging JSON payloads and setting custom metadata.

I wish to both set a custom label and log a JSON payload. The docs mention that sending a custom JSON payload can be achieved using the json_fields extra argument. Example:

import logging

data_dict = {"hello": "world"}
logging.info("message field", extra={"json_fields": data_dict})

With regard to sending a label the docs once again point to the extra argument. Example:

my_labels = {"foo": "bar"}
my_http = {"requestUrl": "localhost"}
my_trace = "01234"

logging.info(
    "hello", extra={"labels": my_labels, "http_request": my_http, "trace": my_trace}
)

Following the guidance above I’m under the impression that this should work in achieving both:

image_metrics = {
    'total_images_sourced': 10,
    'total_images': 8
}

# Write custom metrics to Cloud Logging
logging.info("image_metrics", extra={
    "labels": {"type": "image"},
    "json_fields": image_metrics  
})

However, the resulting logs only output a text payload. Neither the JSON payload nor the label is set.

enter image description here

How can I send both the JSON payload as well as the label?

Update 1: This seems to work correctly when testing locally via the Functions Framework but when I deploy the exact same codebase to update the Cloud Function and run again, it result in the text payload and no label.

Asked By: ianyoung

||

Answers:

This turned out to be an issue with my logging setup. I was attaching two logging handlers to my root logger; the Cloud Logging handler for integration with GCP and the Streaming handler for outputting to the console.

import logging

import google.cloud.logging
from google.cloud.logging.handlers import CloudLoggingHandler

log_name = 'my_log_name'

# Root logger
logger = logging.getLogger(log_name)
logger.setLevel(logging.DEBUG)

# Stream handler to log messages in the console.
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)

# Cloud Logging handler to log messages in GCP.
gcloud_logging_client = google.cloud.logging.Client()
gcloud_logging_handler = CloudLoggingHandler(
    gcloud_logging_client, name=log_name
)

# Add handlers
logger.addHandler(gcloud_logging_handler)
logger.addHandler(stream_handler)

This saw my log messages printed to the console and also sent in the correct format to Cloud Logging when developing locally. However,
the StreamHandler only displays unformatted log messages (not JSON payloads) and the Cloud Logging Handler is unreliable on serverless workloads. This meant that my JSON payloads were being displayed as plain text messages.

It turns out that the Google python logging library provides an easier way to send logs to Google Cloud using:

client = google.cloud.logging.Client()
client.setup_logging()

When using setup_logging() it chooses the right handler based on where your code is running meaning you don’t need to manage the handlers yourself. Using this instead sees my log messages sent and interpreted correctly in Cloud Logging both from local development and when deployed as a Cloud Function.

The one thing that was missing was a way to see log messages in the console when developing locally. I therefore modified my code to this:

import logging

client = google.cloud.logging.Client()
client.setup_logging()

log = logging.getLogger()

if __name__ == '__main__':
    # Add a stream handler to log messages to the console.
    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(logging.INFO)
    log.addHandler(stream_handler)

It uses the recommended setup_logging() method whilst also including the stream handler for local development. Since if __name__ == '__main__': is never true in a Cloud Function environment it makes for a safe place to put things that only apply to local development. This sees my log messages outputting to the console as well.

One thing to note is that the stream handler doesn’t support structured JSON log messages so it will only output the message part (plain text) of a structured log message. For my case this isn’t an issue however.

Answered By: ianyoung