Как записывать и просматривать журналы

Ведение журналов – важный инструмент для отладки и мониторинга кода. Cloud Functions позволяет использовать SDK регистратора для Node.js или Python, а также стандартный объект console для веб-разработки.

Cloud Logging – это платный сервис. Если вы превысите бесплатную квоту, с вас может взиматься плата. Подробнее о ценах на Cloud Logging…

Запись журналов

Как использовать SDK Cloud Functions для ведения журнала

SDK регистратора Cloud Functions предоставляет стандартный интерфейс для передачи статуса функций в Cloud Logging. Этот SDK позволяет регистрировать события со структурированными данными, что упрощает анализ и мониторинг.

Импортируйте из подпакета logger:

Node.js

// All available logging functions
const {
  log,
  info,
  debug,
  warn,
  error,
  write,
} = require("firebase-functions/logger");

Python

from firebase_functions import logger
  • Команды logger.log() имеют уровень ведения журнала INFO.

  • Команды logger.info() имеют уровень ведения журнала INFO.

  • Команды logger.warn() имеют уровень ведения журнала WARNING.

  • logger.error() команд имеют уровень журнала ERROR.

  • Команды logger.debug() имеют уровень ведения журнала DEBUG.

  • Внутренние системные сообщения имеют уровень журнала DEBUG.

В этом примере показана функция, которая записывает базовый журнал:

Node.js

exports.helloWorld = onRequest((request, response) => {
  // sends a log to Cloud Logging
  log("Hello logs!");

  response.send("Hello from Firebase!");
});

Python

@https_fn.on_request()
def hello_world(req: https_fn.Request) -> https_fn.Response:
    # sends a log to Cloud Logging
    logger.log("Hello logs!")

    return https_fn.Response("Hello from Firebase!")

Используйте разные уровни ведения журнала для разных типов журналов в коде функции. Структурированные данные можно прикрепить к журналу в качестве последнего аргумента. Вот пример того, как функция может использовать каждый тип журнала:

Node.js

exports.getInspirationalQuote = onRequest(async (request, response) => {
  const db = getFirestore();
  const today = new Date();
  const quoteOfTheMonthRef = db
      .collection("quotes")
      .doc(`${today.getFullYear()}`)
      .collection("months")
      .doc(`${today.getMonth()}`);

  const DEFAULT_QUOTE =
      "You miss 100% of the shots you don't take. -Wayne Gretzky";
  let quote;
  try {
    const quoteOfTheMonthDocSnap = await quoteOfTheMonthRef.get();

    // Attach relevant debugging information with debug()
    debug("Monthly quote fetch result", {
      docRef: quoteOfTheMonthRef.path,
      exists: quoteOfTheMonthDocSnap.exists,
      createTime: quoteOfTheMonthDocSnap.createTime,
    });

    if (quoteOfTheMonthDocSnap.exists) {
      quote = quoteOfTheMonthDocSnap.data().text;
    } else {
      // Use warn() for lower-severity issues than error()
      warn("Quote not found for month, sending default instead", {
        docRef: quoteOfTheMonthRef.path,
        dateRequested: today.toLocaleDateString("en-US"),
      });

      quote = DEFAULT_QUOTE;
    }
  } catch (err) {
    // Attach an error object as the second argument
    error("Unable to read quote from Firestore, sending default instead",
        err);

    quote = DEFAULT_QUOTE;
  }

  // Attach relevant structured data to any log
  info("Sending a quote!", {quote: quote});
  response.json({inspirationalQuote: quote});
});

Python

@https_fn.on_request()
def get_inspirational_quote(req: https_fn.Request) -> https_fn.Response:
    firestore_client = firestore.client()
    today = datetime.date.today()
    quote_of_the_month_ref = (
        firestore_client.collection("quotes")
        .doc(str(today.year))
        .collection("months")
        .doc(str(today.month))
    )

    default_quote = "Python has been an important part of Google since the beginning, and remains so as the system grows and evolves."

    quote = None
    try:
        quote_of_the_month = quote_of_the_month_ref.get()

        # Attach relevant debugging information with debug()
        logger.debug(
            "Monthly quote fetch result",
            docRef=quote_of_the_month.path,
            exists=quote_of_the_month.exists,
            createTime=quote_of_the_month.createTime,
        )

        if quote_of_the_month.exists:
            quote = quote_of_the_month.to_dict()["text"]
        else:
            # Use warn() for lower-severity issues than error()
            logger.warn(
                "Quote not found for month, sending default instead",
                doc_reference=quote_of_the_month.path,
                date_requested=today.strftime("%Y-%m-%d"),
            )
            quote = default_quote
    except:
        e = sys.exc_info()[0]
        # Attach an error object as the second argument
        logger.error("Unable to read quote from Firestore, sending default instead", error=e)
        quote = default_quote

    # Attach relevant structured data to any log
    logger.info("Sending a quote!", quote=quote)
    return https_fn.Response("Hello from Firebase!")

С помощью logger.write() вы можете создавать записи в журнале с дополнительными уровнями серьезности: CRITICAL, ALERT и EMERGENCY. Подробнее о LogSeverity…

Node.js

exports.appHasARegression = onRegressionAlertPublished((event) => {
  write({
    // write() lets you set additional severity levels
    // beyond the built-in logger functions
    severity: "EMERGENCY",
    message: "Regression in production app",
    issue: event.data.payload.issue,
    lastOccurred: event.data.payload.resolveTime,
  });
});

Python

@crashlytics_fn.on_regression_alert_published()
def app_has_regression(alert: crashlytics_fn.CrashlyticsRegressionAlertEvent) -> None:
    logger.write(
        severity="EMERGENCY",
        message="Regression in production app",
        issue=alert.data.payload.issue,
        last_occurred=alert.data.payload.resolve_time,
    )
    print(alert)

Использование console.log

Для ведения журналов из функции рекомендуется использовать SDK регистратора для вашей платформы. В Node.js можно использовать стандартные вызовы JavaScript для ведения журналов, например console.log и console.error, но сначала необходимо подключить специальный модуль, чтобы исправить стандартные методы для правильной работы:

require("firebase-functions/logger/compat");

После того как вы подключите модуль совместимости, вы сможете использовать методы console.log() в своем коде:

exports.helloError = functions.https.onRequest((request, response) => {
  console.log('I am a log entry!');
  response.send('Hello World...');
});
  • Команды console.log() имеют уровень ведения журнала INFO.
  • Команды console.info() имеют уровень ведения журнала INFO.
  • console.warn() команд имеют уровень журнала ERROR.
  • console.error() команд имеют уровень журнала ERROR.
  • Внутренние системные сообщения имеют уровень журнала DEBUG.

Просмотр журналов

Журналы Cloud Functions можно просматривать в консоли Google Cloud, интерфейсе Cloud Logging или с помощью инструмента командной строки firebase.

Использование интерфейса командной строки Firebase

Чтобы посмотреть журналы с помощью инструмента firebase, используйте команду functions:log:

firebase functions:log

Чтобы посмотреть журналы для определенной функции, укажите ее название в качестве аргумента:

firebase functions:log --only <FUNCTION_NAME>

Чтобы узнать обо всех возможностях просмотра журналов, ознакомьтесь со справкой по functions:log:

firebase help functions:log

Использование консоли Google Cloud

Вы можете просматривать журналы функций в консоли Google Cloud.

Использование интерфейса Cloud Logging

Вы можете просматривать журналы для Cloud Functions в интерфейсе Cloud Logging.

Анализ журналов

Cloud Logging предлагает мощный набор инструментов для анализа журналов, которые можно использовать для мониторинга Cloud Functions.

Диаграммы и оповещения

Создав показатели на основе данных журналов для отслеживания функций, вы можете создавать диаграммы и оповещения на основе этих показателей. Например, вы можете создать диаграмму для визуализации задержки во времени или создать оповещение, чтобы сообщить вам, если определенная ошибка возникает слишком часто.

Подробную информацию о том, как использовать показатели на основе данных журналов в диаграммах и правилах оповещений, можно найти в статье Создание диаграмм и оповещений.

Как использовать идентификаторы выполнения

По умолчанию функции Cloud Run (второе поколение) поддерживают параллельное выполнение нескольких запросов в рамках одного экземпляра функции. Это означает, что журналы разных запросов могут чередоваться, что затрудняет отслеживание хода одного выполнения.

Чтобы помочь вам в этом, функции, развернутые с помощью интерфейса командной строки Firebase версии 13.33.0 и более поздних, автоматически развертываются с возможностью связывания идентификатора выполнения с каждой записью в журнале, созданной во время обработки этого выполнения.

Идентификатор выполнения уникально идентифицирует все журналы, связанные с одним запросом, обрабатываемым вашей функцией. Изменять код не нужно – идентификатор выполнения будет автоматически добавлен в журналы.

Чтобы отключить регистрацию идентификатора выполнения в записях журнала, задайте для переменной среды LOG_EXECUTION_ID значение false в файле dotenv.

Как найти и сопоставить журналы по идентификатору выполнения

Вы можете проверять и сопоставлять журналы по идентификатору выполнения в Cloud Logs Explorer.

  1. Разверните запись в журнале функции. Идентификатор выполнения находится в структурированных данных журнала, вложенных в ярлыки как labels.execution_id.

  2. Нажмите на значение execution_id и в раскрывающемся меню выберите "Показать совпадающие записи", чтобы посмотреть все остальные журналы, связанные с выполнением этой функции.

С помощью идентификатора выполнения можно сгруппировать все сообщения в журнале, связанные с одним запросом, даже если функция обрабатывает несколько запросов одновременно.

Как улучшить видимость журналов с помощью настраиваемых полей сводки

Чтобы идентификатор выполнения было проще найти в Logs Explorer, вы можете добавить его в качестве [пользовательского поля сводки][cloud-logging-preference]. После того как вы добавите идентификатор выполнения в качестве поля сводки, каждая запись в журнале будет отображать идентификатор выполнения в виде чипа в начале строки журнала, как это было в функциях первого поколения.

Чтобы добавить идентификатор выполнения в поле сводки:

  1. Нажмите на значение идентификатора выполнения в структурированной записи в журнале в разделе labels.execution_id.

  2. В раскрывающемся меню выберите "Добавить поле в строку сводки".

Теперь в каждой записи журнала в поле сводки на видном месте отображается executionId, что упрощает идентификацию и группировку журналов, связанных с определенным идентификатором выполнения.