INTEGRITY Документация

Observability

Dynamic Workers поддерживают логи с помощью console.log() вызовы, исключения и метаданные запроса, зафиксированные во время выполнения. Чтобы получить доступ к этим логам, подключите Tail Worker, колбэк, который выполняется после завершения работы Dynamic Worker и передаёт все собранные логи, исключения и метаданные.

В этом руководстве показано, как выполнить следующее:

Сбор логов с помощью Tail Worker

Чтобы сохранить логи, которые создаёт Dynamic Worker, их нужно перехватить и записать туда, где они смогут храниться. Для этого нужно выполнить три шага:

  1. Включение Workers Logs у Worker-загрузчика, чтобы вывод логов сохранялся.
  2. Определение Tail Worker, который получает логи от Dynamic Worker и записывает их в Workers Logs.
  3. Подключение Tail Worker к Dynamic Worker при его создании.

Включите Workers Logs для Worker-загрузчика

Включить Workers Logs добавив observability настройку в конфигурацию Wrangler у Worker-загрузчика. Однако Workers Logs фиксирует вывод логов только от самого Worker-загрузчика. Dynamic Worker работают отдельно, поэтому их console.log() вызовы не включаются автоматически. Чтобы логи Dynamic Worker попадали в Workers Logs, нужно определить Tail Worker, который получает логи от Dynamic Worker и записывает их в Workers Logs Worker-загрузчика.

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1
  }
}
[observability]
enabled = true
head_sampling_rate = 1

Определение Tail Worker

Когда Dynamic Worker выполняется, среда выполнения собирает все его console.log() вызовы, исключения и метаданные запроса. По умолчанию эти логи удаляются после завершения работы Dynamic Worker.

Чтобы сохранить их, определите Tail Worker для loader Worker. Tail Worker представляет собой класс с tail() метод. Здесь вы пишете код, который определяет, что происходит с логами. Runtime вызывает этот метод после завершения работы Dynamic Worker, передавая всё, что было собрано во время выполнения.

Внутри tail(), вы записываете каждую запись лога в Workers Logs, вызывая console.log() с объектом JSON. Добавьте workerId поле в каждой записи, чтобы можно было определить, какой Dynamic Worker создал каждый лог, и впоследствии фильтровать и искать логи по Dynamic Worker.

import { WorkerEntrypoint } from "cloudflare:workers";

export class DynamicWorkerTail extends WorkerEntrypoint {
	async tail(events) {
		for (const event of events) {
			for (const log of event.logs) {
				console.log({
					source: "dynamic-worker-tail",
					workerId: this.ctx.props.workerId,
					level: log.level,
					message: log.message,
				});
			}
		}
	}
}

Tail Worker считывает workerId от this.ctx.props.workerId. Вы задаёте это значение при подключении Tail Worker к Dynamic Worker на следующем шаге.

Поскольку Tail Worker определяется внутри loader Worker, его console.log() вывод сохраняется в Workers Logs вместе с собственными логами Worker-загрузчика.

Подключение Tail Worker к Dynamic Worker

Когда вы создаёте Dynamic Worker, передайте Tail Worker в tails массив. Это указывает среде выполнения: после завершения этого Dynamic Worker нужно отправить собранные логи в Tail Worker, который вы определили.

Чтобы сослаться на DynamicWorkerTail класс, который вы определили на предыдущем шаге, используйте ctx.exports. ctx является третьим параметром у Worker-загрузчика в fetch(request, env, ctx) обработчик. ctx.exports дает доступ к классам, экспортированным из Worker-загрузчика. Поскольку Dynamic Worker выполняется в отдельном контексте и не может обращаться к классу напрямую, используется ctx.exports.DynamicWorkerTail() для создания ссылки, которую runtime может связать с Dynamic Worker.

Также нужно сообщить Tail Worker, для какого именно Dynamic Worker он ведёт логирование. Поскольку Tail Worker выполняется отдельно от loader Worker, fetch() обработчике, у него нет доступа к вашим локальным переменным. Чтобы передать ему информацию, используйте props опцию при создании экземпляра. props это обычный объект пар ключ-значение, который вы задаете при подключении Tail Worker и который Tail Worker может прочитать в this.ctx.props при выполнении. В этом случае вы передаёте workerId чтобы Tail Worker знал, какой Dynamic Worker создал эти логи.

const worker = env.LOADER.get(workerId, () => ({
	mainModule: WORKER_MAIN,
	modules: {
		[WORKER_MAIN]: WORKER_SOURCE,
	},
	tails: [
		ctx.exports.DynamicWorkerTail({
			props: { workerId },
		}),
	],
}));

return worker.getEntrypoint().fetch(request);

Возврат логов в реальном времени

Описанная выше настройка сохраняет логи для последующего использования, но иногда логи нужны сразу же для разработки в реальном времени. Сложность в том, что Tail Worker и loader Worker fetch() обработчики запускаются раздельно. Логи есть у Tail Worker, но у fetch() обработчик формирует ответ. Нужно общее место, куда Tail Worker может записывать логи, а fetch() обработчик может их прочитать.

A Durable Object хорошо подходит для этой задачи. И Tail Worker, и fetch() обработчик может найти тот же экземпляр Durable Object по имени. Tail Worker записывает в него логи после завершения Dynamic Worker, а fetch() обработчик считывает их и включает в ответ.

Паттерн работает следующим образом:

  1. fetch() обработчик создает сессию логирования в Durable Object перед запуском Dynamic Worker.
  2. Dynamic Worker выполняется и генерирует логи.
  3. После завершения работы Dynamic Worker Tail Worker записывает собранные логи в тот же Durable Object.
  4. fetch() обработчик считывает логи из Durable Object и возвращает их в ответе.
import { exports } from "cloudflare:workers";

// 1. Create a log session before running the Dynamic Worker.
const logSession = exports.LogSession.getByName(workerName);
const logWaiter = await logSession.waitForLogs();

// 2. Run the Dynamic Worker.
const response = await worker.getEntrypoint().fetch(request);

// 3. Wait up to 1 second for the Tail Worker to deliver logs.
const logs = await logWaiter.getLogs(1000);

Полную рабочую реализацию см. в Пример Dynamic Workers Playground ↗.