編寫 Genkit Telemetry 外掛程式

OpenTelemetry 支援收集追蹤記錄、指標和記錄檔,您可以編寫設定 Node.js SDK 的遙測外掛程式,讓 Firebase Genkit 將所有遙測資料匯出至任何支援 OpenTelemetry 的系統。

設定

如要控管遙測資料匯出功能,外掛程式的 PluginOptions 必須提供 telemetry 物件,且該物件與 Genkit 設定中的 telemetry 區塊相符。

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

這個物件可提供兩種不同的設定:

  • instrumentation:為 TracesMetrics 提供 OpenTelemetry 設定。
  • 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 提供遙測技術,以供開發人員使用。

檢測

如要控制追蹤記錄和指標的匯出作業,外掛程式必須在 telemetry 物件上提供 instrumentation 屬性,該屬性必須符合 TelemetryConfig 介面:

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

這會提供一個 Partial<NodeSDKConfiguration>,Genkit 架構會用來啟動 NodeSDK。如此一來,外掛程式就能完全控制 Genkit 如何使用 OpenTelemetry 整合功能。

舉例來說,以下遙測設定提供了簡易的記憶體內追蹤記錄和指標匯出工具:

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),
      }),
    };
  },
};

Logger

如要控管 Genkit 架構用來寫入結構化記錄檔資料的記錄器,外掛程式必須在 telemetry 物件上提供符合 LoggerConfig 介面的 logger 屬性:

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 支援將追蹤記錄和時距 ID 複製到 winstonpino 等熱門記錄架構的記錄陳述式。使用 @opentelemetry/auto-instrumentations-node 套件即可自動設定這些 (和其他) 檢測作業,但在某些情況下,您可能需要控制追蹤記錄和時距的欄位名稱和值。為此,您需要為 TelemetryConfig 提供的 NodeSDK 設定提供自訂 LogHook 檢測:

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,將追蹤記錄和時距 ID 寫入記錄訊息中的自訂欄位。

Genkit 架構將保證外掛程式的 TelemetryConfig 會在外掛程式的 LoggerConfig 之前初始化,但您必須確保在 LoggerConfig 初始化之前,不會匯入基礎記錄器。舉例來說,上述 logConfig 可以修改,如下所示:

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 提供實用的診斷工具,有助於找出問題來源。