كتابة مكوّن إضافي Genkit Telemetry

تتيح أداة OpenTelmetry جمع بيانات الآثار والمقاييس والسجلات. يمكن توسيع حزمة Firebase Genkit لتصدير جميع بيانات القياس عن بُعد إلى أي نظام متوافق مع OpenTELmetry (أدوات قياس الأداء عن بُعد) من خلال كتابة مكوّن إضافي للقياس عن بُعد يعمل على إعداد حزمة Node.js.

الإعداد

للتحكّم في تصدير بيانات القياس عن بُعد، يجب أن يوفّر PluginOptions في المكوّن الإضافي عنصر telemetry يتوافق مع كتلة telemetry في إعدادات Genkit.

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

يمكن أن يوفّر هذا الكائن عملية إعداد منفصلة:

  • instrumentation: لتوفير إعدادات OpenTELmetry لكل من Traces وMetrics.
  • logger: يوفّر المسجِّل الأساسي الذي يستخدمه Genkit لكتابة بيانات السجلّ المنظَّمة، بما في ذلك مدخلات ومخرجات تدفقات Genkit.

هذا الفصل ضروري حاليًا لأن وظيفة التسجيل لحزمة Node.js OpenTELmetry 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. يمنح ذلك المكون الإضافي تحكمًا كاملاً في كيفية استخدام تكامل OpenTELmetry بواسطة 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 التي تكتب بيانات السجل في وحدة التحكم، يمكنك تحديث مسجّل المكوّن الإضافي لاستخدام ما يلي:

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

ربط السجلّات والآثار

غالبًا ما يكون من المستحسن أن تكون بيانات السجل مرتبطة بتتبعات OpenTELmetry التي تم تصديرها بواسطة المكوّن الإضافي. نظرًا لأنه لا يتم تصدير بيانات السجل بواسطة إطار عمل OpenTELmetry مباشرةً، فلا يحدث هذا الأمر بشكل مفاجئ. لحسن الحظ، يدعم OpenTELmetry الأدوات التي ستنسخ التتبع وspan ID إلى بيانات السجل لإطارات عمل التسجيل الشائعة مثل 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;
      },
    }),
  ]);

يفعّل المثال جميع الأدوات التلقائية لـ NodeSDK OpenTELmetry NodeSDK، ثم يوفّر حقل WinstonInstrumentation مخصّصًا يكتب أرقام تعريف التتبُّع والامتداد إلى الحقول المخصّصة في رسالة السجلّ.

سيضمن إطار عمل Genkit أن تتم تهيئة TelemetryConfig للمكون الإضافي قبل LoggerConfig في المكون الإضافي، ولكن يجب أن تحرص على أن تتأكد من عدم استيراد مسجّل الدخول الأساسي إلى أن تتم تهيئة LoggerConfig. على سبيل المثال، يمكن تعديل logConfig أعلاه على النحو التالي:

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;

تحديد المشاكل وحلّها

إذا كنت تواجه مشكلة في ظهور البيانات على النحو المتوقّع، يوفّر لك OpenTELmetry أداة تشخيص مفيدة تساعد في تحديد مصدر المشكلة.