‘Can I get that in writing?’ Email logs from Cloud Run jobs and other services
The problem
While we all wish we had time to keep an eye on dashboards, nothing beats a well timed email to catch one’s attention. Not many services in Google Cloud have direct email integration and for that reason I originally built Pubsub2Inbox (and yes, it’s going to be another Pubsub2Inbox article).
This time I was looking for a job-agnostic way of getting logs from Cloud Run jobs (a very neat solution for recurrent automation tasks) to my email so I can keep up on what they are doing. If you haven’t tried Cloud Run jobs yet, they’re great — very flexible and no infrastructure required.
Cloud Run jobs log their output (ie. stdout
and stderr
) into Cloud Logging, but it’s not trivial to catch this output from different kinds of containers. Also, I really didn’t want each script to have an email solution of its own.
The solution
Pubsub2Inbox already has an email output. It can send email via:
- Sendgrid API
- Microsoft Graph API
- Custom SMTP server (now easier than ever with Direct VPC egress)
But it doesn’t have to be email only! You could also send the logs via Slack or Google Chat (and we can even send SMS!).
I also wanted to get some information from the Cloud Run job execution and the actual logs from Cloud Logging (so talking to run.googleapis.com
and logging.googleapis.com
), so I wrote two new processors: cloudrun
and logging
.
Finally we need a way to trigger Pub/Sub. While it’s trivial to do with gcloud pubsub topics publish
, I wanted a more lightweight and easy way as all we need to do is push a simple message to a Pub/Sub queue. This is done just with a few curl
commands:
# Grab a token from the metadata endpoint
TOKEN=$(curl -sH "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" | jq -r ".access_token")
# Encode the task information in JSON and wrap it in base64
MESSAGE=$(echo -n "{\"job\":\"${CLOUD_RUN_JOB}\",\"execution\":\"${CLOUD_RUN_EXECUTION}\",\"index\":${CLOUD_RUN_TASK_INDEX},\"attempt\":${CLOUD_RUN_TASK_ATTEMPT}}" )
MESSAGE_B64=$(echo $MESSAGE | base64 | tr -d \\n)
# Post a message to the public topic
curl -sH "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-X POST \
-d "{'messages':[{'data':'${MESSAGE_B64}'}]}" \
"https://pubsub.googleapis.com/v1/${PUBSUB_TOPIC}:publish"
So that’s pretty easy. For the demo, we’ll be just using a simple container based on Google’s base images where we add the good old Unix fortune
command, a tool that’s two years older than me.
For the Pubsub2Inbox configuration, we fetch the job details with cloudrun
processor to get information such as when the job was completed and then fetch the logs using an entries.list
request to Cloud Logging with the appropriate filter. Finally, we put the log entries in the email and send it either via SendGrid or a custom SMTP server (the demo can use smtp-relay.gmail.com
but setting it up is cumbersome and requires “Less Secure Apps” support which is deprecated and will be removed by the end of 2024).
As always, full Terraform code is provided. Just clone the repository from https://github.com/GoogleCloudPlatform/pubsub2inbox/tree/main and navigate to examples/cloud-run-jobs
.
You’ll need to build the container. I suggest setting up an Artifact Registry in the project and pushing it there:
docker build \
-t europe-west4-docker.pkg.dev/your-project-id/your-repository/cloud-run-example:latest \
.
docker push \
europe-west4-docker.pkg.dev/your-project-id/your-repository/cloud-run-example:latest
There you should create a terraform.tfvars
file with content similar to:
project_id = "your-project-id-here"
region = "europe-west4"
job_container = "europe-west4-docker.pkg.dev/your-project-id/your-repository/cloud-run-example:latest"
sender = "the-sender@example.com"
recipient = "your-email@example.com"
sendgrid_api_token = "SG....."
You can now run terraform init
and then terraform apply
. The example will create a Pub/Sub topic, a sample Cloud Run job and deploy Pubsub2Inbox as a Cloud Function v2 (I’ve always been partial to V2s).
After everything has been created, you can go into Cloud Console and click execute on the sample job that was created:
If you check the Cloud Function, you should see that it’s executing and sending the email:
Finally, check your mailbox and receive your fortune:
You could extend this in multiple ways:
- Pass Cloud Storage links in the Pub/Sub messages and add them as attachments
- Pass recipient information from the job as well
Did you find this useful? Reach out to me through the usual channels — like my T̶w̶i̶t̶t̶e̶r̶ X account: https://twitter.com/rosmo
PS. Some of the fortunes can be quite dark, so tread carefully.