Ver y escribir registros de funciones de Cloud Run

Escribir registros del entorno de ejecución

Cloud Run Functions incluye de forma predeterminada un registro de tiempo de ejecución sencillo. Los registros escritos en stdout o stderr aparecerán automáticamente en la Google Cloud consola. Para registrar información de forma más avanzada, usa las bibliotecas de cliente de Cloud Logging.

De forma predeterminada, la carga útil del registro es una cadena de texto sencilla, como se muestra en los siguientes fragmentos de código. La cadena se almacena en el campo textPayload de la entrada de registro.

Node.js

const functions = require('@google-cloud/functions-framework');

functions.http('helloWorld', (req, res) => {
  console.log('I am a log entry!');
  console.error('I am an error!');
  res.end();
});
La mayoría de las entradas de registro no tienen un nivel de registro asociado. Por ejemplo:

  • Registros emitidos con console.log(), console.info() , console.warn() o console.error()
  • Registros escritos directamente en stdout o stderr

Los mensajes internos del sistema tienen el nivel de registro DEBUG.

Python

def hello_world(data, context):
    """Background Cloud Function.
    Args:
         data (dict): The dictionary with data specific to the given event.
         context (google.cloud.functions.Context): The event metadata.
    """
    print("Hello, stdout!")

  • Los registros de salida estándar o de error estándar no tienen un nivel de registro asociado.
  • Los mensajes internos del sistema tienen el nivel de registro DEBUG.

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

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

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

func init() {
	functions.HTTP("HelloLogging", HelloLogging)
}

// HelloLogging logs messages.
func HelloLogging(w http.ResponseWriter, r *http.Request) {
	log.Println("This is stderr")
	fmt.Println("This is stdout")

	// Structured logging can be used to set severity levels.
	// See https://cloud.google.com/logging/docs/structured-logging.
	fmt.Println(`{"message": "This has ERROR severity", "severity": "error"}`)

	// cloud.google.com/go/logging can optionally be used for additional options.
}
  • Los registros de stdout o stderr no tienen ningún nivel de registro asociado.
  • Los mensajes internos del sistema tienen el nivel de registro DEBUG.

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.logging.Logger;

public class LogHelloWorld implements HttpFunction {

  private static final Logger logger = Logger.getLogger(LogHelloWorld.class.getName());

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    System.out.println("I am a log to stdout!");
    System.err.println("I am a log to stderr!");

    logger.info("I am an info log!");
    logger.warning("I am a warning log!");

    BufferedWriter writer = response.getWriter();
    writer.write("Messages successfully logged!");
  }
}
  • Los registros de stdout o stderr no tienen ningún nivel de registro asociado.
  • Los mensajes internos del sistema tienen el nivel de registro DEBUG.

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace LogHelloWorld;

public class Function : IHttpFunction
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public async Task HandleAsync(HttpContext context)
    {
        Console.WriteLine("I am a log to stdout!");
        Console.Error.WriteLine("I am a log to stderr!");

        _logger.LogInformation("I am an info log!");
        _logger.LogWarning("I am a warning log!");

        await context.Response.WriteAsync("Messages successfully logged!", context.RequestAborted);
    }
}
  • El texto escrito en stdout (por ejemplo, a través de Console.WriteLine) y stderr (por ejemplo, a través de Console.Error.WriteLine) no tiene un nivel de registro.
  • Los niveles de registro de ASP.NET Core se asignan a los niveles de Cloud Logging de la siguiente manera:
    • LogLevel.Trace y LogLevel.Debug map a Cloud Logging DEBUG.
    • LogLevel.Information se asigna a Cloud Logging INFO.
    • LogLevel.Warning se asigna a Cloud Logging WARNING.
    • LogLevel.Error se asigna a Cloud Logging ERROR.
    • LogLevel.Critical se asigna a Cloud Logging CRITICAL.

Ruby

require "functions_framework"

FunctionsFramework.http "log-helloworld" do |_request|
  # Any output sent to either stdout or stderr will be captured and written to
  # the function's logs.
  puts "Hello, stdout!"
  warn "Hello, stderr!"

  # Return the response body as a string.
  "Hello, world!"
end

Las entradas de registro no tienen ningún nivel de registro asociado.

PHP

use Psr\Http\Message\ServerRequestInterface;

function helloLogging(ServerRequestInterface $request): string
{
    // Code running in Google Cloud Functions itself writes log entries to
    // Cloud Logging. (Default log severity level is INFO.)
    $log = fopen('php://stderr', 'wb');
    fwrite($log, "Log entry from fwrite().\n");

    // You can also specify a severity level explicitly using structured logs.
    // See this page for a list of log severity values:
    //   https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
    fwrite($log, json_encode([
      'message' => 'Structured log with error severity',
      'severity' => 'error'
    ]) . PHP_EOL);

    // This will log to standard error, which will appear in Cloud Logging
    error_log('error_log logs in Cloud Functions!');

    // This will log an error message and immediately terminate the function execution
    // trigger_error('fatal errors are logged!');

    // For HTTP functions, this is added to the HTTP response
    // For CloudEvent functions, this does nothing
    var_dump('var_dump goes to HTTP response for HTTP functions');

    // You can also dump variables using var_export() and forward
    // the resulting string to Cloud Logging via an fwrite() call.
    $entry = var_export('var_export output can be captured.', true);
    fwrite($log, $entry);

    // Functions must return a String or PSR-7 Response object
    return '';
}

Escribir registros estructurados

Los registros de texto predeterminados descritos anteriormente no tienen ningún nivel de registro asociado. Si quieres incluir niveles de registro u otros campos específicos en tus entradas de registro, puedes escribir registros en stdout o stderr en forma de una sola línea de JSON serializado. Las funciones de Cloud Run recogen y analizan esta línea, y la colocan en el campo jsonPayload en lugar de en textPayload. En los fragmentos de código de abajo se muestra cómo escribir registros estructurados de este tipo.

Node.js


// Uncomment and populate this variable in your code:
// const project = 'The project ID of your function or Cloud Run service';

// Build structured log messages as an object.
const globalLogFields = {};

// Add log correlation to nest all log messages beneath request log in Log Viewer.
// (This only works for HTTP-based invocations where `req` is defined.)
if (typeof req !== 'undefined') {
  const traceHeader = req.header('X-Cloud-Trace-Context');
  if (traceHeader && project) {
    const [trace] = traceHeader.split('/');
    globalLogFields['logging.googleapis.com/trace'] =
      `projects/${project}/traces/${trace}`;
  }
}

// Complete a structured log entry.
const entry = Object.assign(
  {
    severity: 'NOTICE',
    message: 'This is the default display field.',
    // Log viewer accesses 'component' as 'jsonPayload.component'.
    component: 'arbitrary-property',
  },
  globalLogFields
);

// Serialize to a JSON string and output.
console.log(JSON.stringify(entry));

Python

La compatibilidad con el registro estructurado está disponible en Python 3.8 y versiones posteriores.

# Uncomment and populate this variable in your code:
# PROJECT = 'The project ID of your Cloud Run service';

# Build structured log messages as an object.
global_log_fields = {}

# Add log correlation to nest all log messages.
# This is only relevant in HTTP-based contexts, and is ignored elsewhere.
# (In particular, non-HTTP-based Cloud Functions.)
request_is_defined = "request" in globals() or "request" in locals()
if request_is_defined and request:
    trace_header = request.headers.get("X-Cloud-Trace-Context")

    if trace_header and PROJECT:
        trace = trace_header.split("/")
        global_log_fields[
            "logging.googleapis.com/trace"
        ] = f"projects/{PROJECT}/traces/{trace[0]}"

# Complete a structured log entry.
entry = dict(
    severity="NOTICE",
    message="This is the default display field.",
    # Log viewer accesses 'component' as jsonPayload.component'.
    component="arbitrary-property",
    **global_log_fields,
)

print(json.dumps(entry))

Go

La estructura de cada entrada de registro se proporciona mediante un tipo Entry:


// Entry defines a log entry.
type Entry struct {
	Message  string `json:"message"`
	Severity string `json:"severity,omitempty"`
	Trace    string `json:"logging.googleapis.com/trace,omitempty"`

	// Logs Explorer allows filtering and display of this as `jsonPayload.component`.
	Component string `json:"component,omitempty"`
}

// String renders an entry structure to the JSON format expected by Cloud Logging.
func (e Entry) String() string {
	if e.Severity == "" {
		e.Severity = "INFO"
	}
	out, err := json.Marshal(e)
	if err != nil {
		log.Printf("json.Marshal: %v", err)
	}
	return string(out)
}

Cuando se registra una estructura Entry, se llama al método String para serializarla en el formato JSON que espera Cloud Logging:


func init() {
	// Disable log prefixes such as the default timestamp.
	// Prefix text prevents the message from being parsed as JSON.
	// A timestamp is added when shipping logs to Cloud Logging.
	log.SetFlags(0)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	// Uncomment and populate this variable in your code:
	// projectID = "The project ID of your Cloud Run service"

	// Derive the traceID associated with the current request.
	var trace string
	if projectID != "" {
		traceHeader := r.Header.Get("X-Cloud-Trace-Context")
		traceParts := strings.Split(traceHeader, "/")
		if len(traceParts) > 0 && len(traceParts[0]) > 0 {
			trace = fmt.Sprintf("projects/%s/traces/%s", projectID, traceParts[0])
		}
	}

	log.Println(Entry{
		Severity:  "NOTICE",
		Message:   "This is the default display field.",
		Component: "arbitrary-property",
		Trace:     trace,
	})

	fmt.Fprintln(w, "Hello Logger!")
}

Java

Para habilitar el registro JSON con Logback y SLF4J, habilita Logstash JSON Encoder en tu configuración de logback.xml.

// Build structured log messages as an object.
Object globalLogFields = null;

// Add log correlation to nest all log messages beneath request log in Log Viewer.
// TODO(developer): delete this code if you're creating a Cloud
//                  Function and it is *NOT* triggered by HTTP.
String traceHeader = req.headers("x-cloud-trace-context");
if (traceHeader != null && project != null) {
  String trace = traceHeader.split("/")[0];
  globalLogFields =
      kv(
          "logging.googleapis.com/trace",
          String.format("projects/%s/traces/%s", project, trace));
}
// -- End log correlation code --

// Create a structured log entry using key value pairs.
// For instantiating the "logger" variable, see
// https://cloud.google.com/run/docs/logging#run_manual_logging-java
logger.error(
    "This is the default display field.",
    kv("component", "arbitrary-property"),
    kv("severity", "NOTICE"),
    globalLogFields);
<configuration>
  <appender name="jsonConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
      <!-- Ignore default logging fields -->
      <fieldNames>
        <timestamp>[ignore]</timestamp>
        <version>[ignore]</version>
        <logger>[ignore]</logger>
        <thread>[ignore]</thread>
        <level>[ignore]</level>
        <levelValue>[ignore]</levelValue>
      </fieldNames>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="jsonConsoleAppender"/>
  </root>
</configuration>

Procesar campos JSON especiales en mensajes

Cuando proporcionas datos estructurados como un diccionario JSON, se eliminan algunos campos especiales del jsonPayload y se escriben en el campo correspondiente del LogEntry generado, tal como se describe en la documentación sobre campos especiales.

Por ejemplo, si tu JSON incluye una propiedad severity, se elimina del jsonPayload y aparece como severity de la entrada de registro. La propiedad message se usa como texto principal de la entrada de registro, si está presente.

Escribir registros con bibliotecas de cliente

Las bibliotecas de cliente de Cloud Logging ofrecen una forma alternativa de escribir registros. Con estas bibliotecas, puedes usar los mecanismos de registro estándar de tu lenguaje de programación e integrarlos con varios frameworks de registro compatibles. Las bibliotecas de cliente también simplifican el proceso de rellenar los campos JSON especiales, ya que capturan automáticamente cierta información y proporcionan interfaces para rellenar los campos de forma adecuada.

Puedes usar bibliotecas de cliente para escribir registros con la API Cloud Logging de forma síncrona o asíncrona. Algunas bibliotecas de cliente también permiten escribir registros estructurados directamente en stdout o stderr. Ten en cuenta que, si escribes registros de forma asíncrona, es posible que se pierdan entradas de registro si la función finaliza de forma inesperada. Ten en cuenta también que el registro síncrono con la API de Logging aumenta el tiempo de ejecución de las funciones, ya que requiere esperar a que se completen las llamadas a la API.

Ver registros del entorno de ejecución

En esta sección se describen las opciones que tiene para ver los registros de tiempo de ejecución.

Usar la herramienta de línea de comandos

Los registros de las funciones de Cloud Run se pueden ver en la interfaz de usuario de Cloud Logging y con la CLI de Google Cloud.

Para ver los registros con gcloud CLI, usa el comando gcloud functions logs read:

gcloud functions logs read

Para ver los registros de una función específica, proporciona el nombre de la función como argumento:

gcloud functions logs read FUNCTION_NAME

Para ver los registros de una ejecución específica, haz lo siguiente:

gcloud functions logs read FUNCTION_NAME --execution-id EXECUTION_ID

Para ver todas las opciones de visualización de registros, consulta la documentación de gcloud functions logs read.

Usar el panel de control de Logging

También puedes ver los registros de tiempo de ejecución de las funciones de Cloud Run en la Google Cloud consola.

Usar la API Logging

Los registros del tiempo de ejecución también se pueden escribir y recuperar a través de la API de Cloud Logging. Las bibliotecas de cliente de Cloud Logging ofrecen una interfaz idiomática para la API Logging:

Node.js

Para obtener más información, consulta la referencia de la biblioteca de cliente de Node.js.
// Imports the Google Cloud client library
const {Logging} = require('@google-cloud/logging');

// Creates a client
const logging = new Logging();

/**
 * TODO(developer): Uncomment the following line to run the code.
 */
// const logName = 'Name of the log from which to list entries, e.g. my-log';

const log = logging.log(logName);

async function printEntryMetadata() {
  // List the most recent entries for a given log
  // See https://googleapis.dev/nodejs/logging/latest/Logging.html#getEntries
  const [entries] = await log.getEntries();
  console.log('Logs:');
  entries.forEach(entry => {
    const metadata = entry.metadata;
    console.log(`${metadata.timestamp}:`, metadata[metadata.payload]);
  });
}
printEntryMetadata();

Python

Para obtener más información, consulta la referencia de la biblioteca de cliente de Python.
def list_entries(logger_name):
    """Lists the most recent entries for a given logger."""
    logging_client = logging.Client()
    logger = logging_client.logger(logger_name)

    print("Listing entries for logger {}:".format(logger.name))

    for entry in logger.list_entries():
        timestamp = entry.timestamp.isoformat()
        print("* {}: {}".format(timestamp, entry.payload))

Go

Para obtener más información, consulta la referencia de la biblioteca de cliente de Go.
import (
	"context"
	"fmt"
	"log"
	"time"

	"cloud.google.com/go/logging"
	"cloud.google.com/go/logging/logadmin"
	"google.golang.org/api/iterator"
)

func getEntries(projectID string) ([]*logging.Entry, error) {
	ctx := context.Background()
	adminClient, err := logadmin.NewClient(ctx, projectID)
	if err != nil {
		log.Fatalf("Failed to create logadmin client: %v", err)
	}
	defer adminClient.Close()

	var entries []*logging.Entry
	const name = "log-example"
	lastHour := time.Now().Add(-1 * time.Hour).Format(time.RFC3339)

	iter := adminClient.Entries(ctx,
		// Only get entries from the "log-example" log within the last hour.
		logadmin.Filter(fmt.Sprintf(`logName = "projects/%s/logs/%s" AND timestamp > "%s"`, projectID, name, lastHour)),
		// Get most recent entries first.
		logadmin.NewestFirst(),
	)

	// Fetch the most recent 20 entries.
	for len(entries) < 20 {
		entry, err := iter.Next()
		if err == iterator.Done {
			return entries, nil
		}
		if err != nil {
			return nil, err
		}
		entries = append(entries, entry)
	}
	return entries, nil
}

Java

Para obtener más información, consulta la referencia de la biblioteca de cliente de Java.
import com.google.api.gax.paging.Page;
import com.google.cloud.logging.LogEntry;
import com.google.cloud.logging.Logging;
import com.google.cloud.logging.Logging.EntryListOption;
import com.google.cloud.logging.LoggingOptions;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class ListLogEntries {

  public static void main(String[] args) throws Exception {
    // TODO(developer): Replace the variable value with valid log name before running the sample
    // or provide it as an argument.
    String logName = args.length > 0 ? args[0] : "test-log";

    try (Logging logging = LoggingOptions.getDefaultInstance().getService()) {

      // When composing a filter, using indexed fields, such as timestamp, resource.type, logName
      // and
      // others can help accelerate the results
      // Full list of indexed fields here:
      // https://cloud.google.com/logging/docs/view/advanced-queries#finding-quickly
      // This sample restrict the results to only last minute to minimize number of API calls
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
      calendar.add(Calendar.MINUTE, -1);
      DateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
      String logFilter =
          "logName=projects/"
              + logging.getOptions().getProjectId()
              + "/logs/"
              + logName
              + " AND timestamp>=\""
              + rfc3339.format(calendar.getTime())
              + "\"";

      // List all log entries
      Page<LogEntry> entries = logging.listLogEntries(EntryListOption.filter(logFilter));
      while (entries != null) {
        for (LogEntry logEntry : entries.iterateAll()) {
          System.out.println(logEntry);
        }
        entries = entries.getNextPage();
      }
    }
  }
}

C#

private void ListLogEntries(string logId)
{
    var client = LoggingServiceV2Client.Create();
    LogName logName = new LogName(s_projectId, logId);
    ProjectName projectName = new ProjectName(s_projectId);
    var results = client.ListLogEntries(Enumerable.Repeat(projectName, 1), $"logName={logName.ToString()}",
        "timestamp desc", callSettings: _retryAWhile);
    foreach (var row in results)
    {
        Console.WriteLine($"{row.TextPayload.Trim()}");
    }
}

Ruby

require "google/cloud/logging"

# log_name = "my_log_name"
logging = Google::Cloud::Logging.new
entries = logging.entries filter: "logName:#{log_name}",
                          max:    1000,
                          order:  "timestamp desc"

entries.each do |entry|
  puts "[#{entry.timestamp}] #{entry.log_name} #{entry.payload.inspect}"
end

PHP

use Google\Cloud\Logging\LoggingClient;

/**
 * Print the timestamp and entry for the project and logger.
 *
 * @param string $projectId The Google project ID.
 * @param string $loggerName The name of the logger.
 */
function list_entries($projectId, $loggerName)
{
    $logging = new LoggingClient(['projectId' => $projectId]);
    $loggerFullName = sprintf('projects/%s/logs/%s', $projectId, $loggerName);
    $oneDayAgo = date(\DateTime::RFC3339, strtotime('-24 hours'));
    $filter = sprintf(
        'logName = "%s" AND timestamp >= "%s"',
        $loggerFullName,
        $oneDayAgo
    );
    $options = [
        'filter' => $filter,
    ];
    $entries = $logging->entries($options);

    // Print the entries
    foreach ($entries as $entry) {
        /* @var $entry \Google\Cloud\Logging\Entry */
        $entryInfo = $entry->info();
        if (isset($entryInfo['textPayload'])) {
            $entryText = $entryInfo['textPayload'];
        } else {
            $entryPayload = [];
            foreach ($entryInfo['jsonPayload'] as $key => $value) {
                $entryPayload[] = "$key: $value";
            }
            $entryText = '{' . implode(', ', $entryPayload) . '}';
        }
        printf('%s : %s' . PHP_EOL, $entryInfo['timestamp'], $entryText);
    }
}

Para ver otras opciones de registro de Java, consulta Registro de Java.

Responder a registros del entorno de ejecución

Puedes responder a eventos de Cloud Logging reenviando sus registros a una función de Cloud Run. Para obtener más información, consulta la página Activadores de terceros con Cloud Logging.

Ver registros de imágenes de compilación

También puedes ver los registros del paso Crear imagen del proceso de implementación. Haz clic en el enlace para obtener más información.