Cloud Functions invocations are not automatically traced by default, but you can enable distributed tracing to visualize request flows across services.

Let’s look at a simple Node.js Cloud Function that calls another Cloud Function and a Cloud Storage bucket.

// index.js
const { Storage } = require('@google-cloud/storage');
const { HttpFunction, CloudEvent } = require('@google-cloud/functions-framework');

const storage = new Storage();

// Function to be called by another function
const targetFunction: HttpFunction = async (req, res) => {
  console.log('Target function received request.');
  res.status(200).send('Hello from target function!');
};

// Function that calls another function and Cloud Storage
const mainFunction: HttpFunction = async (req, res) => {
  console.log('Main function started.');

  // Call another Cloud Function
  const targetFunctionUrl = process.env.TARGET_FUNCTION_URL; // e.g., "https://us-central1-my-project.cloudfunctions.net/targetFunction"
  if (!targetFunctionUrl) {
    console.error('TARGET_FUNCTION_URL environment variable not set.');
    return res.status(500).send('Configuration error.');
  }

  try {
    const response = await fetch(targetFunctionUrl, {
      method: 'GET',
      headers: {
        // Important: propagate trace context if available
        'traceparent': req.headers['traceparent'] || '',
        'tracestate': req.headers['tracestate'] || '',
      }
    });
    const targetResult = await response.text();
    console.log(`Target function response: ${targetResult}`);
  } catch (error) {
    console.error('Error calling target function:', error);
    return res.status(500).send('Error calling target function.');
  }

  // Access Cloud Storage
  const bucketName = process.env.BUCKET_NAME; // e.g., "my-storage-bucket"
  if (!bucketName) {
    console.error('BUCKET_NAME environment variable not set.');
    return res.status(500).send('Configuration error.');
  }

  try {
    const [files] = await storage.bucket(bucketName).getFiles({ maxResults: 1 });
    console.log(`Found ${files.length} file(s) in bucket ${bucketName}.`);
    if (files.length > 0) {
      console.log(`First file: ${files[0].name}`);
    }
  } catch (error) {
    console.error('Error accessing Cloud Storage:', error);
    return res.status(500).send('Error accessing Cloud Storage.');
  }

  console.log('Main function finished.');
  res.status(200).send('Main function processed request.');
};

// Export the functions
module.exports = {
  mainFunction,
  targetFunction,
};

Deployment Configuration (package.json and gcloud command):

// package.json
{
  "name": "cloud-functions-trace-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^3.0.0",
    "@google-cloud/storage": "^6.0.0",
    "node-fetch": "^2.6.7" // Or use native fetch in Node.js 18+
  },
  "scripts": {
    "start": "functions-framework --target=mainFunction"
  },
  "author": "",
  "license": "ISC"
}

Deploy with:

gcloud functions deploy mainFunction \
  --runtime nodejs18 \
  --trigger-http \
  --allow-unauthenticated \
  --entry-point mainFunction \
  --set-env-vars TARGET_FUNCTION_URL="YOUR_TARGET_FUNCTION_URL",BUCKET_NAME="YOUR_BUCKET_NAME" \
  --region=us-central1 \
  --project=YOUR_PROJECT_ID

gcloud functions deploy targetFunction \
  --runtime nodejs18 \
  --trigger-http \
  --allow-unauthenticated \
  --entry-point targetFunction \
  --region=us-central1 \
  --project=YOUR_PROJECT_ID

When you invoke mainFunction (e.g., via HTTP POST or GET), Cloud Functions automatically generates a trace ID. This trace ID is then propagated to any downstream calls made by the function, provided that the calling code explicitly forwards the trace context. In our example, the fetch call to targetFunctionUrl includes the traceparent and tracestate headers, which is crucial for linking spans. The Cloud Storage client library also automatically integrates with Cloud Trace when tracing is enabled for the project.

The magic happens behind the scenes. When a Cloud Function is invoked, the Google Cloud SDK (which your function runs within) intercepts the request. If tracing is enabled for your project, it creates a root span for the function’s execution. As the function executes and calls other Google Cloud services (like Cloud Storage) or other Cloud Functions (via HTTP requests that propagate the trace headers), new spans are created, linked to the parent span. These spans represent discrete units of work.

The key levers you control are:

  1. Enabling Cloud Trace for your project: This is a project-level setting in the Google Cloud Console under "Trace" -> "Settings". Without this, no traces will be generated.
  2. Propagating Trace Context: For HTTP calls between services (like one Cloud Function calling another), you must forward the traceparent and tracestate headers. The traceparent header contains the trace ID, span ID, and sampling information. The tracestate header carries vendor-specific trace information. If you don’t forward these, the downstream service won’t know it’s part of the same trace.
  3. Using Supported Client Libraries: For Google Cloud services, ensure you’re using the official client libraries. They are instrumented to automatically generate spans when Cloud Trace is enabled.
  4. Environment Variables: As shown, you’ll need to configure things like the URL of the function to call or the bucket name.

The most surprising true thing about distributed tracing in Cloud Functions is how easily you can break the trace by simply forgetting to forward the traceparent and tracestate headers in custom HTTP calls. The underlying infrastructure wants to trace everything, but it relies on those headers being present to stitch the distributed request together. If they’re missing, the downstream service starts a new, independent trace, and you lose the end-to-end visibility for that specific invocation.

After successfully tracing your Cloud Functions, you’ll likely want to explore how to filter and analyze traces to pinpoint performance bottlenecks or errors.

Want structured learning?

Take the full Cloud-functions course →