Ghi một trình bổ trợ Genkit Telemetry

OpenTelemetry hỗ trợ thu thập dấu vết, chỉ số và nhật ký. Bạn có thể mở rộng Firebase Genkit để xuất tất cả dữ liệu đo từ xa sang bất kỳ hệ thống nào có hỗ trợ OpenTelemetry bằng cách viết một trình bổ trợ đo từ xa để định cấu hình Node.js SDK.

Cấu hình

Để kiểm soát tính năng xuất dữ liệu đo từ xa, PluginOptions của trình bổ trợ phải cung cấp Đối tượng telemetry tuân thủ khối telemetry trong cấu hình của Genkit.

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

Đối tượng này có thể cung cấp 2 cấu hình riêng biệt:

  • instrumentation: cung cấp cấu hình OpenTelemetry cho TracesMetrics.
  • logger: cung cấp trình ghi nhật ký cơ bản mà Genkit sử dụng để ghi dữ liệu nhật ký có cấu trúc, bao gồm cả đầu vào và đầu ra của luồng Genkit.

Việc phân tách này hiện cần thiết vì chức năng ghi nhật ký cho Node.js SDK OpenTelemetry vẫn đang phát triển. Tính năng ghi nhật ký được cung cấp riêng để một trình bổ trợ có thể kiểm soát vị trí lưu trữ của dữ liệu được viết rõ ràng.

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;

Với khối mã ở trên, trình bổ trợ của bạn giờ đây sẽ cung cấp cho Genkit một dữ liệu đo từ xa mà các nhà phát triển có thể sử dụng.

Khả năng đo lường

Để kiểm soát việc xuất dấu vết và chỉ số, trình bổ trợ của bạn phải cung cấp Thuộc tính instrumentation trên đối tượng telemetry tuân thủ Giao diện TelemetryConfig:

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

Thao tác này cung cấp Partial<NodeSDKConfiguration> mà sẽ được sử dụng bởi khung Genkit để khởi động NodeSDK. Thao tác này cho phép trình bổ trợ kiểm soát hoàn toàn cách sử dụng tính năng tích hợp OpenTelemetry của Genkit.

Ví dụ: cấu hình đo từ xa sau đây cung cấp một trình xuất chỉ số và dấu vết trong bộ nhớ đơn giản:

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

Trình ghi nhật ký

Để kiểm soát trình ghi nhật ký mà khung Genkit sử dụng để ghi dữ liệu nhật ký có cấu trúc, trình bổ trợ này phải cung cấp thuộc tính logger trên đối tượng telemetry phù hợp với Giao diện LoggerConfig:

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

Hầu hết các khung ghi nhật ký phổ biến đều tuân thủ tiêu chuẩn này. Một trong những khuôn khổ này là winston, cho phép định cấu hình các trình di chuyển có thể trực tiếp đẩy dữ liệu nhật ký đến vị trí bạn chọn.

Ví dụ: để cung cấp một trình ghi nhật ký chiến thắng ghi dữ liệu nhật ký vào bảng điều khiển, bạn có thể cập nhật trình ghi nhật ký trình bổ trợ để sử dụng những tính năng sau:

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

Liên kết nhật ký và dấu vết

Thông thường, bạn nên có các câu lệnh nhật ký tương quan với Dấu vết OpenTelemetry do trình bổ trợ của bạn xuất. Bởi vì các câu lệnh nhật ký không được xuất trực tiếp bằng khung OpenTelemetry, điều này không xảy ra ngoài . Rất may là OpenTelemetry hỗ trợ các công cụ đo lường sẽ sao chép dấu vết và mở rộng mã nhận dạng vào câu lệnh nhật ký cho các khung ghi nhật ký phổ biến như winstonpino. Bằng cách sử dụng gói @opentelemetry/auto-instrumentations-node, bạn có thể định cấu hình các thiết bị đo lường này (và các công cụ khác), nhưng trong một số trường hợp bạn có thể cần kiểm soát tên trường và giá trị cho dấu vết và span. Để làm việc này, bạn cần cung cấp khả năng đo lường LogHook tuỳ chỉnh để cấu hình NodeSDK do TelemetryConfig của bạn cung cấp:

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

Ví dụ này bật tất cả các công cụ đo lường tự động cho OpenTelemetry NodeSDK, sau đó cung cấp một WinstonInstrumentation tuỳ chỉnh ghi dấu vết và mã span vào các trường tuỳ chỉnh trên thông điệp nhật ký.

Khung Genkit sẽ đảm bảo rằng TelemetryConfig của trình bổ trợ sẽ được khởi chạy trước LoggerConfig của trình bổ trợ, nhưng bạn phải chú ý đảm bảo trình ghi nhật ký cơ bản không được nhập cho đến khi LoggerConfig được đã khởi chạy. Ví dụ: bạn có thể sửa đổi mức ghi nhật ký ở trên như sau:

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

Ví dụ đầy đủ

Sau đây là ví dụ đầy đủ về trình bổ trợ dữ liệu đo từ xa đã tạo ở trên. Để một ví dụ thực tế, hãy xem trình bổ trợ @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;

Khắc phục sự cố

Nếu bạn gặp khó khăn khi đưa dữ liệu để hiển thị ở nơi bạn mong đợi, OpenTelemetry cung cấp Công cụ chẩn đoán giúp xác định nguồn gốc vấn đề.