การเขียนปลั๊กอิน Genkit Telemetry

OpenTelemetry รองรับการรวบรวมการติดตาม เมตริก และบันทึก คุณสามารถขยาย Firebase Genkit เพื่อส่งออกข้อมูลการวัดและส่งข้อมูลทางไกลทั้งหมดไปยังระบบที่ใช้ OpenTelemetry ได้ โดยเขียนปลั๊กอินการวัดและส่งข้อมูลทางไกลที่กำหนดค่า SDK ของ Node.js

การกำหนดค่า

หากต้องการควบคุมการส่งออกการวัดและส่งข้อมูลทางไกล PluginOptions ของปลั๊กอินต้องมีออบเจ็กต์ telemetry ที่สอดคล้องกับบล็อก telemetry ในการกำหนดค่าของ Genkit

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

ออบเจ็กต์นี้มีการกำหนดค่าแยกกันได้ 2 รายการ ดังนี้

  • 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 Logger ที่เขียนข้อมูลบันทึกไปยังคอนโซล คุณสามารถอัปเดตตัวบันทึกปลั๊กอินให้ใช้สิ่งต่อไปนี้ได้

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 รองรับการใช้เครื่องมือที่จะคัดลอกรหัสการติดตามและ Span ไปยังคำสั่งบันทึกสำหรับเฟรมเวิร์กการบันทึกยอดนิยม เช่น 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 ที่กำหนดเองซึ่งเขียนรหัสการติดตามและ Span ไปยังช่องที่กำหนดเองในข้อความบันทึก

เฟรมเวิร์ก 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 มีเครื่องมือวินิจฉัยที่มีประโยชน์ซึ่งจะช่วยค้นหาแหล่งที่มาของปัญหา