Cloud Functions’ default logging can become a dense, unstructured mess when you’re dealing with complex data.
Let’s say you’ve got a function that processes an incoming webhook, performs some database lookups, and then triggers another service. Without structured logging, your stdout and stderr streams in Cloud Logging would look like this:
Received webhook for order 12345.
Fetching order details from database...
Order 12345 found: { status: "processing", items: ["widget", "gadget"] }
Updating order status to "fulfilled"...
Triggering fulfillment service for order 12345...
Fulfillment service call successful.
Order 12345 marked as fulfilled.
This is fine for simple functions, but imagine dozens of these logs, interleaved with other function executions, and you’re trying to find all logs related to order 12345 or all errors during fulfillment. It’s a needle in a haystack.
Structured logging turns that unstructured text into a machine-readable format, typically JSON. This allows Cloud Logging to parse the data, create indexed fields, and enable powerful filtering and analysis.
Here’s how you can achieve this in your Cloud Function (Node.js example):
// index.js
const functions = require('@google-cloud/functions-framework');
// Helper function to log structured data
function logStructured(level, message, data = {}) {
const logEntry = {
severity: level.toUpperCase(), // e.g., INFO, WARNING, ERROR
message: message,
...data, // Spread any additional data fields
// You can add custom fields here too
customField: 'myValue'
};
console.log(JSON.stringify(logEntry));
}
functions.http('processOrder', (req, res) => {
const orderId = req.body.orderId;
logStructured('INFO', 'Received order processing request', { orderId: orderId, requestBody: req.body });
if (!orderId) {
logStructured('ERROR', 'Missing orderId in request body', { requestBody: req.body });
res.status(400).send('Missing orderId');
return;
}
// Simulate database lookup
setTimeout(() => {
const orderDetails = {
orderId: orderId,
status: 'processing',
items: ['widget', 'gadget'],
timestamp: new Date().toISOString()
};
logStructured('INFO', 'Order details fetched from database', { orderId: orderId, orderDetails: orderDetails });
// Simulate fulfillment service call
setTimeout(() => {
const fulfillmentResult = {
success: true,
trackingNumber: 'TRK123XYZ'
};
logStructured('INFO', 'Fulfillment service call completed', { orderId: orderId, fulfillmentResult: fulfillmentResult });
// Final status update
logStructured('INFO', 'Order successfully processed and fulfilled', { orderId: orderId, trackingNumber: fulfillmentResult.trackingNumber });
res.status(200).send(`Order ${orderId} processed.`);
}, 1000);
}, 1000);
});
When this function runs, the logs in Cloud Logging will appear as distinct JSON objects, not a single block of text. For example, the "Received order processing request" log would look something like this in the Cloud Logging UI:
{
"message": "Received order processing request",
"severity": "INFO",
"orderId": "12345",
"requestBody": {
"orderId": "12345",
"items": ["widget", "gadget"]
},
"customField": "myValue",
"logging.googleapis.com/trace": "projects/your-project-id/traces/your-trace-id",
"logging.googleapis.com/operation": {
"id": "your-operation-id",
"producer": "cloudfunctions.googleapis.com",
"firstGenId": "your-first-gen-id"
}
}
Notice how orderId, requestBody, and customField are now top-level fields. Cloud Logging automatically indexes many of these fields, allowing you to query them directly.
The core idea is to JSON.stringify() your log data before console.log()ing it. You can structure your log entries with standard fields like severity and message, and then add any custom fields that are relevant to your application’s state or the event being logged. Common practice is to include identifiers like orderId, userId, transactionId, or any specific payload data that helps you trace execution or debug issues.
The logStructured helper function makes this pattern reusable and ensures consistency across your function. You can define specific levels (INFO, WARNING, ERROR, DEBUG) and pass arbitrary data objects to enrich the log entry.
The most surprising true thing about structured logging in Cloud Functions is that you don’t need any special libraries or configuration beyond standard Node.js console.log and JSON.stringify. The Google Cloud Logging agent automatically detects JSON payloads on stdout and parses them.
This allows for incredibly powerful querying. Instead of grepping through text, you can use the Cloud Logging query language:
- To find all logs for a specific order:
jsonPayload.orderId="12345" - To find all errors related to missing
orderId:jsonPayload.message="Missing orderId in request body" - To find all successful fulfillment events:
jsonPayload.message="Order successfully processed and fulfilled" - To combine conditions:
jsonPayload.orderId="12345" AND severity="ERROR"
The next step in mastering Cloud Functions logging is to explore log-based metrics, where you can create metrics from your structured logs to trigger alerts or track aggregated behavior.