Написание плагина телеметрии Genkit

OpenTelemetry поддерживает сбор трассировок, метрик и журналов. Firebase Genkit можно расширить для экспорта всех данных телеметрии в любую систему с поддержкой OpenTelemetry, написав плагин телеметрии, который настраивает Node.js SDK.

Конфигурация

Чтобы управлять экспортом телеметрии, PluginOptions вашего плагина должен предоставить объект telemetry , соответствующий блоку telemetry в конфигурации Genkit.

export interface InitializedPlugin {
  ...
  telemetry?: {
    instrumentation?: Provider<TelemetryConfig>;
    logger?: Provider<LoggerConfig>;
  };
}

Этот объект может предоставлять две отдельные конфигурации:

  • instrumentation : обеспечивает конфигурацию OpenTelemetry для Traces и Metrics .
  • logger : предоставляет базовый регистратор, используемый Genkit для записи структурированных данных журнала, включая входные и выходные данные потоков Genkit.

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

import { genkitPlugin, Plugin } from '@genkit-ai/core';

...

export interface MyPluginOptions {
  // [Optional] Your plugin options
}

export const myPlugin: Plugin<[MyPluginOptions] | []> = genkitPlugin(
  'myPlugin',
  async (options?: MyPluginOptions) => {
    return {
      telemetry: {
        instrumentation: {
          id: 'myPlugin',
          value: myTelemetryConfig,
        },
        logger: {
          id: 'myPlugin',
          value: myLogger,
        },
      },
    };
  }
);

export default myPlugin;

Благодаря приведенному выше блоку кода ваш плагин теперь предоставит Genkit конфигурацию телеметрии, которую смогут использовать разработчики.

Инструментарий

Чтобы управлять экспортом трассировок и метрик, ваш плагин должен предоставить свойство instrumentation объекта telemetry , соответствующее интерфейсу TelemetryConfig :

interface TelemetryConfig {
  getConfig(): Partial<NodeSDKConfiguration>;
}

Это предоставляет Partial<NodeSDKConfiguration> , который будет использоваться платформой Genkit для запуска NodeSDK . Это дает плагину полный контроль над тем, как интеграция OpenTelemetry используется Genkit.

Например, следующая конфигурация телеметрии обеспечивает простой экспортер трассировки и метрик в памяти:

import { AggregationTemporality, InMemoryMetricExporter, MetricReader, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { AlwaysOnSampler, BatchSpanProcessor, InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';
import { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
import { Resource } from '@opentelemetry/resources';
import { TelemetryConfig } from '@genkit-ai/core';

...

const myTelemetryConfig: TelemetryConfig = {
  getConfig(): Partial<NodeSDKConfiguration> {
    return {
      resource: new Resource({}),
      spanProcessor: new BatchSpanProcessor(new InMemorySpanExporter()),
      sampler: new AlwaysOnSampler(),
      instrumentations: myPluginInstrumentations,
      metricReader: new PeriodicExportingMetricReader({
        exporter: new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE),
      }),
    };
  },
};

Регистратор

Чтобы управлять средством ведения журнала, используемым платформой Genkit для записи структурированных данных журнала, плагин должен предоставить свойство logger в объекте telemetry , соответствующее интерфейсу LoggerConfig :

interface LoggerConfig {
  getLogger(env: string): any;
}
{
  debug(...args: any);
  info(...args: any);
  warn(...args: any);
  error(...args: any);
  level: string;
}

Этому соответствуют большинство популярных платформ ведения журналов. Одной из таких платформ является winston , которая позволяет настраивать транспортеры, которые могут напрямую передавать данные журнала в выбранное вами место.

Например, чтобы предоставить средство ведения журнала Winston, которое записывает данные журнала в консоль, вы можете обновить свой подключаемый модуль ведения журнала, чтобы использовать следующее:

import * as winston from 'winston';

...

const myLogger: LoggerConfig = {
  getLogger(env: string) {
    return winston.createLogger({
      transports: [new winston.transports.Console()],
      format: winston.format.printf((info): string => {
        return `[${info.level}] ${info.message}`;
      }),
    });
  }
};

Связывание журналов и трассировок

Часто желательно, чтобы ваши операторы журнала коррелировали с трассировками OpenTelemetry, экспортируемыми вашим плагином. Поскольку операторы журнала не экспортируются платформой OpenTelemetry напрямую, это не происходит сразу после установки. К счастью, OpenTelemetry поддерживает инструменты, которые копируют идентификаторы трассировки и диапазона в операторы журнала для популярных платформ ведения журналов, таких как Winston и Pino . Используя пакет @opentelemetry/auto-instrumentations-node , вы можете настроить эти (и другие) инструменты автоматически, но в некоторых случаях вам может потребоваться контролировать имена и значения полей для трассировок и диапазонов. Для этого вам необходимо предоставить специальный инструментарий LogHook для конфигурации NodeSDK, предоставленной вашим TelemetryConfig :

import { Instrumentation } from '@opentelemetry/instrumentation';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston';
import { Span } from '@opentelemetry/api';

const myPluginInstrumentations: Instrumentation[] =
  getNodeAutoInstrumentations().concat([
    new WinstonInstrumentation({
      logHook: (span: Span, record: any) => {
        record['my-trace-id'] = span.spanContext().traceId;
        record['my-span-id'] = span.spanContext().spanId;
        record['is-trace-sampled'] = span.spanContext().traceFlags;
      },
    }),
  ]);

В примере включены все автоматические инструменты для OpenTelemetry NodeSDK , а затем предоставляется пользовательский WinstonInstrumentation , который записывает идентификаторы трассировки и диапазона в настраиваемые поля сообщения журнала.

Платформа Genkit гарантирует, что TelemetryConfig вашего плагина будет инициализирован до LoggerConfig вашего плагина, но вы должны позаботиться о том, чтобы базовый регистратор не был импортирован до тех пор, пока не будет инициализирован LoggerConfig. Например, приведенный выше loggingConfig можно изменить следующим образом:

const myLogger: LoggerConfig = {
  async getLogger(env: string) {
    // Do not import winston before calling getLogger so that the NodeSDK
    // instrumentations can be registered first.
    const winston = await import('winston');

    return winston.createLogger({
      transports: [new winston.transports.Console()],
      format: winston.format.printf((info): string => {
        return `[${info.level}] ${info.message}`;
      }),
    });
  },
};

Полный пример

Ниже приведен полный пример плагина телеметрии, созданного выше. В качестве реального примера взгляните на плагин @genkit-ai/google-cloud .

import {
  genkitPlugin,
  LoggerConfig,
  Plugin,
  TelemetryConfig,
} from '@genkit-ai/core';
import { Span } from '@opentelemetry/api';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Instrumentation } from '@opentelemetry/instrumentation';
import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston';
import { Resource } from '@opentelemetry/resources';
import {
  AggregationTemporality,
  InMemoryMetricExporter,
  PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
import { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
import {
  AlwaysOnSampler,
  BatchSpanProcessor,
  InMemorySpanExporter,
} from '@opentelemetry/sdk-trace-base';

export interface MyPluginOptions {
  // [Optional] Your plugin options
}

const myPluginInstrumentations: Instrumentation[] =
  getNodeAutoInstrumentations().concat([
    new WinstonInstrumentation({
      logHook: (span: Span, record: any) => {
        record['my-trace-id'] = span.spanContext().traceId;
        record['my-span-id'] = span.spanContext().spanId;
        record['is-trace-sampled'] = span.spanContext().traceFlags;
      },
    }),
  ]);

const myTelemetryConfig: TelemetryConfig = {
  getConfig(): Partial<NodeSDKConfiguration> {
    return {
      resource: new Resource({}),
      spanProcessor: new BatchSpanProcessor(new InMemorySpanExporter()),
      sampler: new AlwaysOnSampler(),
      instrumentations: myPluginInstrumentations,
      metricReader: new PeriodicExportingMetricReader({
        exporter: new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE),
      }),
    };
  },
};

const myLogger: LoggerConfig = {
  async getLogger(env: string) {
    // Do not import winston before calling getLogger so that the NodeSDK
    // instrumentations can be registered first.
    const winston = await import('winston');

    return winston.createLogger({
      transports: [new winston.transports.Console()],
      format: winston.format.printf((info): string => {
        return `[${info.level}] ${info.message}`;
      }),
    });
  },
};

export const myPlugin: Plugin<[MyPluginOptions] | []> = genkitPlugin(
  'myPlugin',
  async (options?: MyPluginOptions) => {
    return {
      telemetry: {
        instrumentation: {
          id: 'myPlugin',
          value: myTelemetryConfig,
        },
        logger: {
          id: 'myPlugin',
          value: myLogger,
        },
      },
    };
  }
);

export default myPlugin;

Поиск неисправностей

Если у вас возникли проблемы с отображением данных там, где вы ожидаете, OpenTelemetry предоставляет полезный инструмент диагностики , который помогает найти источник проблемы.