Acionadores do Google Cloud Firestore (1.ª geração)

As funções do Cloud Run podem processar eventos no Firestore no mesmo Google Cloud projeto que a função. Pode ler ou atualizar o Firestore em resposta a estes eventos através das APIs e bibliotecas cliente do Firestore.

Num ciclo de vida típico, uma função do Firestore faz o seguinte:

  1. Aguarda alterações a um documento específico.

  2. É acionado quando ocorre um evento e executa as respetivas tarefas.

  3. Recebe um objeto de dados com um instantâneo do documento afetado. Para eventos write ou update, o objeto de dados contém instantâneos que representam o estado do documento antes e depois do evento de acionamento.

Tipos de eventos

O Firestore suporta eventos create, update, delete e write. O evento write abrange todas as modificações a um documento.

Tipo de evento Acionador
providers/cloud.firestore/eventTypes/document.create (predefinição) Acionado quando um documento é escrito pela primeira vez.
providers/cloud.firestore/eventTypes/document.update Acionado quando já existe um documento e qualquer valor é alterado.
providers/cloud.firestore/eventTypes/document.delete Acionado quando um documento com dados é eliminado.
providers/cloud.firestore/eventTypes/document.write Acionado quando um documento é criado, atualizado ou eliminado.

Os carateres universais são escritos em acionadores com chavetas, da seguinte forma: "projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"

Especificar o caminho do documento

Para acionar a função, especifique um caminho do documento a ouvir. As funções só respondem a alterações de documentos e não podem monitorizar campos específicos nem coleções. Seguem-se alguns exemplos de caminhos de documentos válidos:

  • users/marie: acionador válido. Monitoriza um único documento, /users/marie.

  • users/{username}: acionador válido. Monitoriza todos os documentos do utilizador. Os carateres universais são usados para monitorizar todos os documentos na coleção.

  • users/{username}/addresses: acionador inválido. Refere-se à subcoleçãoaddresses e não a um documento.

  • users/{username}/addresses/home: acionador válido. Monitoriza o documento de morada para todos os utilizadores.

  • users/{username}/addresses/{addressId}: acionador válido. Monitoriza todos os documentos de morada.

Usar carateres universais e parâmetros

Se não souber o documento específico que quer monitorizar, use {wildcard} em vez do ID do documento:

  • users/{username} ouve as alterações a todos os documentos do utilizador.

Neste exemplo, quando qualquer campo em qualquer documento em users é alterado, corresponde a um caráter universal denominado {username}.

Se um documento em users tiver subcoleções e um campo num dos documentos dessas subcoleções for alterado, o {username} caráter universal não é acionado.

As correspondências com carateres universais são extraídas dos caminhos dos documentos. Pode definir quantos carateres universais quiser para substituir IDs de documentos ou de recolha explícitos.

Estrutura de eventos

Este acionador invoca a sua função com um evento semelhante ao apresentado abaixo:

{
    "oldValue": { // Update and Delete operations only
        A Document object containing a pre-operation document snapshot
    },
    "updateMask": { // Update operations only
        A DocumentMask object that lists changed fields.
    },
    "value": {
        // A Document object containing a post-operation document snapshot
    }
}

Cada objeto Document contém um ou mais objetos Value. Consulte a documentação do Value para referências de tipos. Isto é especialmente útil se estiver a usar uma linguagem tipada (como Go) para escrever as suas funções.

Exemplo de código

A função do Google Cloud abaixo imprime os campos de um evento do Cloud Firestore de acionamento:

Node.js

/**
 * Background Function triggered by a change to a Firestore document.
 *
 * @param {!Object} event The Cloud Functions event.
 * @param {!Object} context Cloud Functions event metadata.
 */
exports.helloFirestore = (event, context) => {
  const triggerResource = context.resource;

  console.log(`Function triggered by event on: ${triggerResource}`);
  console.log(`Event type: ${context.eventType}`);

  if (event.oldValue && Object.keys(event.oldValue).length) {
    console.log('\nOld value:');
    console.log(JSON.stringify(event.oldValue, null, 2));
  }

  if (event.value && Object.keys(event.value).length) {
    console.log('\nNew value:');
    console.log(JSON.stringify(event.value, null, 2));
  }
};

Python

import json

def hello_firestore(data, context):
    """Triggered by a change to a Firestore document.
    Args:
        data (dict): The event payload.
        context (google.cloud.functions.Context): Metadata for the event.
    """
    trigger_resource = context.resource

    print("Function triggered by change to: %s" % trigger_resource)

    print("\nOld value:")
    print(json.dumps(data["oldValue"]))

    print("\nNew value:")
    print(json.dumps(data["value"]))

Go


// Package hello contains a Cloud Function triggered by a Firestore event.
package hello

import (
	"context"
	"fmt"
	"log"
	"time"

	"cloud.google.com/go/functions/metadata"
)

// FirestoreEvent is the payload of a Firestore event.
type FirestoreEvent struct {
	OldValue   FirestoreValue `json:"oldValue"`
	Value      FirestoreValue `json:"value"`
	UpdateMask struct {
		FieldPaths []string `json:"fieldPaths"`
	} `json:"updateMask"`
}

// FirestoreValue holds Firestore fields.
type FirestoreValue struct {
	CreateTime time.Time `json:"createTime"`
	// Fields is the data for this value. The type depends on the format of your
	// database. Log the interface{} value and inspect the result to see a JSON
	// representation of your database fields.
	Fields     interface{} `json:"fields"`
	Name       string      `json:"name"`
	UpdateTime time.Time   `json:"updateTime"`
}

// HelloFirestore is triggered by a change to a Firestore document.
func HelloFirestore(ctx context.Context, e FirestoreEvent) error {
	meta, err := metadata.FromContext(ctx)
	if err != nil {
		return fmt.Errorf("metadata.FromContext: %w", err)
	}
	log.Printf("Function triggered by change to: %v", meta.Resource)
	log.Printf("Old value: %+v", e.OldValue)
	log.Printf("New value: %+v", e.Value)
	return nil
}

Java

import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.logging.Logger;

public class FirebaseFirestore implements RawBackgroundFunction {
  private static final Logger logger = Logger.getLogger(FirebaseFirestore.class.getName());

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  @Override
  public void accept(String json, Context context) {
    JsonObject body = gson.fromJson(json, JsonObject.class);
    logger.info("Function triggered by event on: " + context.resource());
    logger.info("Event type: " + context.eventType());

    if (body != null && body.has("oldValue")) {
      logger.info("Old value:");
      logger.info(body.get("oldValue").getAsString());
    }

    if (body != null && body.has("value")) {
      logger.info("New value:");
      logger.info(body.get("value").getAsString());
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseFirestore;

public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;

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

    public Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by event on {subject}", cloudEvent.Subject);
        _logger.LogInformation("Event type: {type}", cloudEvent.Type);
        MaybeLogDocument("Old value", data.OldValue);
        MaybeLogDocument("New value", data.Value);

        // In this example, we don't need to perform any asynchronous operations, so the
        // method doesn't need to be declared async.
        return Task.CompletedTask;
    }

    /// <summary>
    /// Logs the names and values of the fields in a document in a very simplistic way.
    /// </summary>
    private void MaybeLogDocument(string message, Document document)
    {
        if (document is null)
        {
            return;
        }

        // ConvertFields converts the Firestore representation into a .NET-friendly
        // representation.
        IReadOnlyDictionary<string, object> fields = document.ConvertFields();
        var fieldNamesAndTypes = fields
            .OrderBy(pair => pair.Key)
            .Select(pair => $"{pair.Key}: {pair.Value}");
        _logger.LogInformation(message + ": {fields}", string.Join(", ", fieldNamesAndTypes));
    }
}

Ruby

require "functions_framework"

# Triggered by a change to a Firestore document.
FunctionsFramework.cloud_event "hello_firestore" do |event|
  # The event parameter is a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  payload = event.data

  logger.info "Function triggered by change to: #{event.source}"
  logger.info "Old value: #{payload['oldValue']}"
  logger.info "New value: #{payload['value']}"
end

PHP

use Google\CloudFunctions\CloudEvent;

function firebaseFirestore(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    fwrite($log, 'Event: ' . $cloudevent->getId() . PHP_EOL);
    fwrite($log, 'Event Type: ' . $cloudevent->getType() . PHP_EOL);

    $data = $cloudevent->getData();

    $resource = $data['resource'];
    fwrite($log, 'Function triggered by event on: ' . $resource . PHP_EOL);

    if (isset($data['oldValue'])) {
        fwrite($log, 'Old value: ' . json_encode($data['oldValue']) . PHP_EOL);
    }

    if (isset($data['value'])) {
        fwrite($log, 'New value: ' . json_encode($data['value']) . PHP_EOL);
    }
}

O exemplo abaixo obtém o valor adicionado pelo utilizador, converte a string nessa localização em letras maiúsculas e substitui o valor pela string em letras maiúsculas:

Node.js

const Firestore = require('@google-cloud/firestore');

const firestore = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT,
});

// Converts strings added to /messages/{pushId}/original to uppercase
exports.makeUpperCase = event => {
  const resource = event.value.name;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = event.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();

  if (curValue !== newValue) {
    console.log(`Replacing value: ${curValue} --> ${newValue}`);

    return affectedDoc.set({
      original: newValue,
    });
  } else {
    // Value is already upper-case
    // Don't perform a(nother) write to avoid infinite loops
    console.log('Value is already upper-case.');
  }
};

Python

from google.cloud import firestore

client = firestore.Client()


# Converts strings added to /messages/{pushId}/original to uppercase
def make_upper_case(data, context):
    path_parts = context.resource.split("/documents/")[1].split("/")
    collection_path = path_parts[0]
    document_path = "/".join(path_parts[1:])

    affected_doc = client.collection(collection_path).document(document_path)

    cur_value = data["value"]["fields"]["original"]["stringValue"]
    new_value = cur_value.upper()

    if cur_value != new_value:
        print(f"Replacing value: {cur_value} --> {new_value}")
        affected_doc.set({"original": new_value})
    else:
        # Value is already upper-case
        # Don't perform a second write (which can trigger an infinite loop)
        print("Value is already upper-case.")

Go


// Package upper contains a Firestore Cloud Function.
package upper

import (
	"context"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"cloud.google.com/go/firestore"
	firebase "firebase.google.com/go/v4"
)

// FirestoreEvent is the payload of a Firestore event.
type FirestoreEvent struct {
	OldValue   FirestoreValue `json:"oldValue"`
	Value      FirestoreValue `json:"value"`
	UpdateMask struct {
		FieldPaths []string `json:"fieldPaths"`
	} `json:"updateMask"`
}

// FirestoreValue holds Firestore fields.
type FirestoreValue struct {
	CreateTime time.Time `json:"createTime"`
	// Fields is the data for this value. The type depends on the format of your
	// database. Log an interface{} value and inspect the result to see a JSON
	// representation of your database fields.
	Fields     MyData    `json:"fields"`
	Name       string    `json:"name"`
	UpdateTime time.Time `json:"updateTime"`
}

// MyData represents a value from Firestore. The type definition depends on the
// format of your database.
type MyData struct {
	Original struct {
		StringValue string `json:"stringValue"`
	} `json:"original"`
}

// GOOGLE_CLOUD_PROJECT is automatically set by the Cloud Functions runtime.
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")

// client is a Firestore client, reused between function invocations.
var client *firestore.Client

func init() {
	// Use the application default credentials.
	conf := &firebase.Config{ProjectID: projectID}

	// Use context.Background() because the app/client should persist across
	// invocations.
	ctx := context.Background()

	app, err := firebase.NewApp(ctx, conf)
	if err != nil {
		log.Fatalf("firebase.NewApp: %v", err)
	}

	client, err = app.Firestore(ctx)
	if err != nil {
		log.Fatalf("app.Firestore: %v", err)
	}
}

// MakeUpperCase is triggered by a change to a Firestore document. It updates
// the `original` value of the document to upper case.
func MakeUpperCase(ctx context.Context, e FirestoreEvent) error {
	fullPath := strings.Split(e.Value.Name, "/documents/")[1]
	pathParts := strings.Split(fullPath, "/")
	collection := pathParts[0]
	doc := strings.Join(pathParts[1:], "/")

	curValue := e.Value.Fields.Original.StringValue
	newValue := strings.ToUpper(curValue)
	if curValue == newValue {
		log.Printf("%q is already upper case: skipping", curValue)
		return nil
	}
	log.Printf("Replacing value: %q -> %q", curValue, newValue)

	data := map[string]string{"original": newValue}
	_, err := client.Collection(collection).Doc(doc).Set(ctx, data)
	if err != nil {
		return fmt.Errorf("Set: %w", err)
	}
	return nil
}

Java


import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.SetOptions;
import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FirebaseFirestoreReactive implements RawBackgroundFunction {

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  private static final Logger logger = Logger.getLogger(FirebaseFirestoreReactive.class.getName());
  private static final Firestore FIRESTORE = FirestoreOptions.getDefaultInstance().getService();

  private final Firestore firestore;

  public FirebaseFirestoreReactive() {
    this(FIRESTORE);
  }

  FirebaseFirestoreReactive(Firestore firestore) {
    this.firestore = firestore;
  }

  @Override
  public void accept(String json, Context context) {
    // Get the recently-written value
    JsonObject body = gson.fromJson(json, JsonObject.class);
    JsonObject tempJson = body.getAsJsonObject("value");

    // Verify that value.fields.original.stringValue exists
    String currentValue = null;
    if (tempJson != null) {
      tempJson = tempJson.getAsJsonObject("fields");
    }
    if (tempJson != null) {
      tempJson = tempJson.getAsJsonObject("original");
    }
    if (tempJson != null && tempJson.has("stringValue")) {
      currentValue = tempJson.get("stringValue").getAsString();
    }
    if (currentValue == null) {
      throw new IllegalArgumentException("Malformed JSON: " + json);
    }

    // Convert recently-written value to ALL CAPS
    String newValue = currentValue.toUpperCase(Locale.getDefault());

    // Update Firestore DB with ALL CAPS value
    Map<String, String> newFields = Map.of("original", newValue);

    String affectedDoc = context.resource().split("/documents/")[1].replace("\"", "");

    if (!currentValue.equals(newValue)) {
      // The stored value needs to be updated
      // Write the upper-cased value to Firestore
      logger.info(String.format("Replacing value: %s --> %s", currentValue, newValue));
      try {
        FIRESTORE.document(affectedDoc).set(newFields, SetOptions.merge()).get();
      } catch (ExecutionException | InterruptedException e) {
        logger.log(Level.SEVERE, "Error updating Firestore document: " + e.getMessage(), e);
      }
    } else {
      // The stored value is already upper-case, and doesn't need updating.
      // (Don't perform a "second" write, since that could trigger an infinite loop.)
      logger.info(String.format("Value is already upper-case."));
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Firestore;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace FirestoreReactive;

public class Startup : FunctionsStartup
{
    public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
        services.AddSingleton(FirestoreDb.Create());
}

// Register the startup class to provide the Firestore dependency.
[FunctionsStartup(typeof(Startup))]
public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;
    private readonly FirestoreDb _firestoreDb;

    public Function(ILogger<Function> logger, FirestoreDb firestoreDb) =>
        (_logger, _firestoreDb) = (logger, firestoreDb);

    public async Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        // Get the recently-written value. This expression will result in a null value
        // if any of the following is true:
        // - The event doesn't contain a "new" document
        // - The value doesn't contain a field called "original"
        // - The "original" field isn't a string
        string currentValue = data.Value?.ConvertFields().GetValueOrDefault("original") as string;
        if (currentValue is null)
        {
            _logger.LogWarning($"Event did not contain a suitable document");
            return;
        }

        string newValue = currentValue.ToUpperInvariant();
        if (newValue == currentValue)
        {
            _logger.LogInformation("Value is already upper-cased; no replacement necessary");
            return;
        }

        // The CloudEvent subject is "documents/x/y/...".
        // The Firestore SDK FirestoreDb.Document method expects a reference relative to
        // "documents" (so just the "x/y/..." part). This may be simplified over time.
        if (cloudEvent.Subject is null || !cloudEvent.Subject.StartsWith("documents/"))
        {
            _logger.LogWarning("CloudEvent subject is not a document reference.");
            return;
        }
        string documentPath = cloudEvent.Subject.Substring("documents/".Length);

        _logger.LogInformation("Replacing '{current}' with '{new}' in '{path}'", currentValue, newValue, documentPath);
        await _firestoreDb.Document(documentPath).UpdateAsync("original", newValue, cancellationToken: cancellationToken);
    }
}

Ruby

require "functions_framework"

FunctionsFramework.on_startup do
  # Lazily construct a Firestore client when needed, and reuse it on
  # subsequent calls.
  set_global :firestore_client do
    require "google/cloud/firestore"
    Google::Cloud::Firestore.new project_id: ENV["GOOGLE_CLOUD_PROJECT"]
  end
end

# Converts strings added to /messages/{pushId}/original to uppercase
FunctionsFramework.cloud_event "make_upper_case" do |event|
  # Event-triggered Ruby functions receive a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  # The Firebase event payload can be obtained from the event data.
  cur_value = event.data["value"]["fields"]["original"]["stringValue"]

  # Compute new value and determine whether it needs to be modified.
  # If the value is already upper-case, don't perform another write,
  # to avoid infinite loops.
  new_value = cur_value.upcase
  if cur_value == new_value
    logger.info "Value is already upper-case"
    return
  end

  # Use the Firestore client library to update the value.
  # The document name can be obtained from the event subject.
  logger.info "Replacing value: #{cur_value} --> #{new_value}"
  doc_name = event.subject.split("documents/").last
  affected_doc = global(:firestore_client).doc doc_name
  new_doc_data = { original: new_value }
  affected_doc.set new_doc_data, merge: false
end

PHP

use Google\Cloud\Firestore\FirestoreClient;
use Google\CloudFunctions\CloudEvent;

function firebaseReactive(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
    $data = $cloudevent->getData();

    $resource = $data['value']['name'];

    $db = new FirestoreClient();

    $docPath = explode('/documents/', $resource)[1];

    $affectedDoc = $db->document($docPath);

    $curValue = $data['value']['fields']['original']['stringValue'];
    $newValue = strtoupper($curValue);

    if ($curValue !== $newValue) {
        fwrite($log, 'Replacing value: ' . $curValue . ' --> ' . $newValue . PHP_EOL);

        $affectedDoc->set(['original' => $newValue]);
    } else {
        // Value is already upper-case
        // Don't perform another write (it might cause an infinite loop)
        fwrite($log, 'Value is already upper-case.' . PHP_EOL);
    }
}

Implementar a sua função

O seguinte comando gcloud implementa uma função acionada por eventos de escrita no documento /messages/{pushId}:

gcloud functions deploy FUNCTION_NAME \
  --no-gen2 \
  --entry-point ENTRY_POINT \
  --runtime RUNTIME \
  --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID \
  --trigger-event "providers/cloud.firestore/eventTypes/document.write" \
  --trigger-resource "projects/YOUR_PROJECT_ID/databases/(default)/documents/messages/{pushId}"
Argumento Descrição
FUNCTION_NAME O nome registado da função do Cloud que está a implementar. Pode ser o nome de uma função no seu código-fonte ou uma string arbitrária. Se FUNCTION_NAME for uma string arbitrária, tem de incluir a flag --entry-point.
--entry-point ENTRY_POINT O nome de uma função ou classe no seu código-fonte. Opcional, a menos que não tenha usado FUNCTION_NAME para especificar a função no código fonte a ser executada durante a implementação. Nesse caso, tem de usar --entry-point para indicar o nome da função executável.
--runtime RUNTIME O nome do tempo de execução que está a usar. Para ver uma lista completa, consulte a gcloud referência.
--set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID O identificador exclusivo do projeto como uma variável do ambiente de tempo de execução.
--trigger-event NAME O tipo de evento que a função vai monitorizar (um dos seguintes: write, create, update ou delete).
--trigger-resource NAME O caminho da base de dados totalmente qualificado que a função vai ouvir. Tem de estar em conformidade com o seguinte formato: "projects/YOUR_PROJECT_ID/databases/(default)/documents/PATH" O texto {pushId} é um parâmetro de carateres universais descrito acima em Especificar o caminho do documento.

Limitações

Tenha em atenção as seguintes limitações para acionadores do Firestore para funções do Cloud Run:

  • As funções do Cloud Run (1.ª geração) requerem uma base de dados "(default)" existente no modo nativo do Firestore. Não suporta bases de dados com nome do Firestore nem o modo Datastore. Use as funções do Cloud Run (2.ª geração) para configurar eventos nesses casos.
  • A configuração entre projetos com funções do Cloud Run e o acionador do Firestore é uma limitação. Para configurar funções do Cloud Run com acionadores do Firestore, estas têm de estar no mesmo projeto.
  • A ordenação não é garantida. As alterações rápidas podem acionar invocações de funções numa ordem inesperada.
  • Os eventos são entregues, pelo menos, uma vez, mas um único evento pode resultar em várias invocações de funções. Evite depender de mecanismos de execução única e escreva funções idempotentes.
  • O Firestore no modo Datastore requer funções do Cloud Run (2.ª geração). As funções do Cloud Run (1.ª geração) não suportam o modo Datastore.
  • Um acionador está associado a uma única base de dados. Não pode criar um acionador que corresponda a várias bases de dados.
  • A eliminação de uma base de dados não elimina automaticamente os acionadores dessa base de dados. O acionador deixa de enviar eventos, mas continua a existir até eliminar o acionador.
  • Se um evento correspondente exceder o tamanho máximo do pedido, o evento pode não ser entregue às funções do Cloud Run (1.ª geração).
    • Os eventos não entregues devido ao tamanho do pedido são registados nos registos da plataforma e contam para a utilização de registos do projeto.
    • Pode encontrar estes registos no Explorador de registos com a mensagem "Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen..." de error gravidade. Pode encontrar o nome da função no campo functionName. Se o campo receiveTimestamp ainda estiver dentro de uma hora a partir de agora, pode inferir o conteúdo real do evento lendo o documento em questão com uma captura de ecrã antes e depois da data/hora.
    • Para evitar esta cadência, pode:
      • Migre e atualize para as funções do Cloud Run (2.ª geração)
      • Reduza o tamanho do documento
      • Elimine as funções do Cloud Run em questão
    • Pode desativar o próprio registo através de exclusões, mas tenha em atenção que os eventos ofensivos continuam a não ser enviados.