كتابة المكوّن الإضافي للقياس عن بُعد Genkit

يتيح OpenTelemetry جمع بيانات التتبّع والمقاييس والسجلّات. من الممكن توسيع نطاق Firebase Genkit من أجل تصدير كل بيانات القياس عن بُعد إلى أي نظام متوافق مع OpenTelemetry، وذلك من خلال كتابة مكوّن إضافي للقياس عن بُعد يضبط إعدادات Node.js SDK.

الإعداد

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

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

يمكن أن يوفر هذا الكائن ضبطين منفصلين:

  • 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;
}

تتوافق أكثر أطر التسجيل شيوعًا مع هذا. أحد هذه الأطر وينستون، التي تسمح بتهيئة التي يمكنها دفع بيانات السجل مباشرةً إلى الموقع الذي تختاره.

على سبيل المثال، لتوفير مسجِّل وينستون يكتب بيانات السجل إلى وحدة التحكم، يمكنك تحديث مسجّل المكوّن الإضافي لاستخدام ما يلي:

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 وبينو. باستخدام حزمة @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 التهيئة. على سبيل المثال، يمكن تعديل التسجيل أعلاه على النحو التالي:

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 توفر لك أداة التشخيص التي تساعد في تحديد مصدر المشكلة.