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

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

การกำหนดค่า

ในการควบคุมการส่งออกการวัดและส่งข้อมูลทางไกล 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 Congiguration ที่นักพัฒนาซอฟต์แวร์สามารถใช้ได้

การวัดคุม

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

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