Viết trình bổ trợ Genkit từ xa

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 đo lường từ xa nào có hỗ trợ OpenTelemetry bằng cách viết một trình bổ trợ đo lường từ xa để định cấu hình SDK Node.js.

Cấu hình

Để kiểm soát việc xuất dữ liệu đo từ xa, PluginOptions của trình bổ trợ phải cung cấp đối tượng telemetry phù hợp với 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 hai 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ả dữ liệu đầu vào và đầu ra của luồng Genkit.

Việc phân tách này hiện là cần thiết vì chức năng ghi nhật ký cho SDK OpenTelemetry của Node.js vẫn đang trong quá trình phát triển. Việc ghi nhật ký được cung cấp riêng biệt để trình bổ trợ có thể kiểm soát vị trí ghi dữ liệu một cách 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 hiện sẽ cung cấp cho Genkit một lượng 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 phù hợp với giao diện TelemetryConfig:

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

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

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ợ phải cung cấp một 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 theo quy tắc này. Một trong những khung như vậy là winston, cho phép định cấu hình các trình truyền tải 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 trình ghi nhật ký Winton nhằm 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ý của trình bổ trợ để sử dụng các nội dung 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ữ liệu theo dõi

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 mà trình bổ trợ của bạn xuất. Do khung OpenTelemetry không trực tiếp xuất các câu lệnh nhật ký, nên điều này sẽ không xảy ra ngay từ đầu. May mắn là OpenTelemetry hỗ trợ các công cụ sao chép mã dấu vết và mã span vào câu lệnh nhật ký cho các khung ghi nhật ký phổ biến như winstonpino. Khi sử dụng gói @opentelemetry/auto-instrumentations-node, bạn có thể tự động định cấu hình các công cụ đ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. Để thực hiện việc này, bạn cần cung cấp một khả năng đo lường LogHook tuỳ chỉnh cho 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ả khả năng đo lường tự động cho NodeSDK OpenTelemetry, sau đó cung cấp một WinstonInstrumentation tuỳ chỉnh để ghi mã theo dõi và mã span vào các trường tuỳ chỉnh trong 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 động trước LoggerConfig của trình bổ trợ. Tuy nhiên, bạn phải cẩn thận đảm bảo rằng trình ghi nhật ký cơ bản không được nhập cho đến khi LoggerConfig khởi động. Ví dụ: bạn có thể sửa đổi logcatConfig ở 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ợ đo lường từ xa được tạo ở trên. Để xem 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 trong việc thu thập dữ liệu để hiển thị ở vị trí mong muốn, thì OpenTelemetry sẽ cung cấp công cụ chẩn đoán hữu ích giúp xác định nguồn gốc sự cố.