Menulis Plugin Telemetry Genkit

OpenTelemetry mendukung pengumpulan trace, metrik, dan log. Firebase Genkit dapat diperluas untuk mengekspor semua data telemetri ke semua sistem yang mendukung OpenTelemetry dengan menulis plugin telemetri yang mengonfigurasi SDK Node.js.

Konfigurasi

Untuk mengontrol ekspor telemetri, PluginOptions plugin Anda harus menyediakan objek telemetry yang sesuai dengan blok telemetry dalam konfigurasi Genkit.

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

Objek ini dapat menyediakan dua konfigurasi terpisah:

  • instrumentation: menyediakan konfigurasi OpenTelemetry untuk Traces dan Metrics.
  • logger: menyediakan logger dasar yang digunakan oleh Genkit untuk menulis data log terstruktur, termasuk input dan output alur Genkit.

Pemisahan ini saat ini diperlukan karena fungsi logging untuk Node.js OpenTelemetry SDK masih dalam pengembangan. Logging disediakan secara terpisah sehingga plugin dapat mengontrol tempat data ditulis secara eksplisit.

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;

Dengan blok kode di atas, plugin Anda kini akan menyediakan Genkit dengan konfigurasi telemetri yang dapat digunakan oleh developer.

Instrumentasi

Untuk mengontrol ekspor trace dan metrik, plugin Anda harus menyediakan properti instrumentation pada objek telemetry yang sesuai dengan antarmuka TelemetryConfig:

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

Proses ini menyediakan Partial<NodeSDKConfiguration> yang akan digunakan oleh framework Genkit untuk memulai NodeSDK. Hal ini memberi plugin kontrol penuh atas penggunaan integrasi OpenTelemetry oleh Genkit.

Misalnya, konfigurasi telemetri berikut menyediakan trace dalam memori dan pengekspor metrik yang sederhana:

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

Untuk mengontrol logger yang digunakan oleh framework Genkit untuk menulis data log terstruktur, plugin harus menyediakan properti logger pada objek telemetry yang sesuai dengan antarmuka LoggerConfig:

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

Kerangka kerja {i>logging<i} paling populer sesuai dengan ini. Salah satu framework tersebut adalah winston, yang memungkinkan konfigurasi transporter yang dapat langsung mengirim data log ke lokasi pilihan Anda.

Misalnya, untuk menyediakan winton logger yang menulis data log ke konsol, Anda dapat mengupdate logger plugin untuk menggunakan kode berikut:

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

Menautkan log dan Rekaman Aktivitas

Sering kali, sebaiknya laporan log Anda berkorelasi dengan trace OpenTelemetry yang diekspor oleh plugin. Karena laporan log tidak diekspor oleh framework OpenTelemetry secara langsung, hal ini tidak terjadi begitu saja. Untungnya, OpenTelemetry mendukung instrumentasi yang akan menyalin ID rekaman aktivitas dan span ke laporan log untuk framework logging populer seperti winston dan pino. Dengan paket @opentelemetry/auto-instrumentations-node, Anda dapat mengonfigurasi instrumentasi ini (dan lainnya) secara otomatis, tetapi dalam beberapa kasus, Anda mungkin perlu mengontrol nama dan nilai kolom untuk trace dan span. Untuk melakukannya, Anda harus menyediakan instrumentasi LogHook kustom ke konfigurasi NodeSDK yang disediakan oleh 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;
      },
    }),
  ]);

Contoh ini mengaktifkan semua instrumentasi otomatis untuk NodeSDK OpenTelemetry, lalu memberikan WinstonInstrumentation kustom yang menulis ID rekaman aktivitas dan span ke kolom kustom pada pesan log.

Framework Genkit akan menjamin bahwa TelemetryConfig plugin Anda akan diinisialisasi sebelum LoggerConfig plugin, tetapi Anda harus memastikan bahwa logger yang mendasarinya tidak diimpor hingga LoggerConfig diinisialisasi. Misalnya, loggingConfig di atas dapat diubah sebagai berikut:

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

Contoh Lengkap

Berikut adalah contoh lengkap plugin telemetri yang dibuat di atas. Untuk contoh nyata, lihat plugin @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;

Pemecahan masalah

Jika Anda mengalami masalah dalam menampilkan data di tempat yang diharapkan, OpenTelemetry menyediakan Alat diagnostik yang berguna untuk membantu menemukan sumber masalah.