Genkit Telemetry प्लगिन लिखना

OpenTelemetry का इस्तेमाल करके, ट्रेस, मेट्रिक, और लॉग इकट्ठा किए जा सकते हैं. Firebase Genkit को ऐसा कोई टेलीमेट्री प्लगिन लिखकर, OpenTelemetry की सुविधा वाले किसी भी सिस्टम पर सभी टेलीमेट्री डेटा को एक्सपोर्ट करने के लिए बढ़ाया जा सकता है जो Node.js SDK टूल को कॉन्फ़िगर करता हो.

कॉन्फ़िगरेशन

टेलीमेट्री एक्सपोर्ट को कंट्रोल करने के लिए, यह ज़रूरी है कि आपके प्लगिन के PluginOptions में telemetry ऑब्जेक्ट दिया हो, जो Genkit के कॉन्फ़िगरेशन में telemetry ब्लॉक के मुताबिक हो.

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

यह ऑब्जेक्ट दो अलग-अलग कॉन्फ़िगरेशन दे सकता है:

  • instrumentation: यह Traces और Metrics के लिए OpenTelemetry कॉन्फ़िगरेशन देता है.
  • 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;

ऊपर दिए गए कोड ब्लॉक के साथ, अब आपका प्लग इन जेनकिट के साथ टेलीमेट्री कॉन्फ़िगरेशन देगा. डेवलपर इसका इस्तेमाल कर सकेंगे.

इंस्ट्रुमेंटेशन

ट्रेस और मेट्रिक के एक्सपोर्ट को कंट्रोल करने के लिए, आपके प्लग इन को telemetry ऑब्जेक्ट पर ऐसी instrumentation प्रॉपर्टी देनी होगी जो TelemetryConfig इंटरफ़ेस के मुताबिक हो:

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

इससे एक Partial<NodeSDKConfiguration> मिलता है, जिसका इस्तेमाल जेनकिट फ़्रेमवर्क में, NodeSDK को शुरू करने के लिए किया जाएगा. इससे प्लगिन को इस बात का पूरा कंट्रोल मिलता है कि Genkit में OpenTelemetry इंटिग्रेशन का इस्तेमाल कैसे किया जाएगा.

उदाहरण के लिए, यहां दिया गया टेलीमेट्री कॉन्फ़िगरेशन, मेमोरी में ट्रेस और मेट्रिक एक्सपोर्ट करने का आसान तरीका उपलब्ध कराता है:

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

लॉगर

स्ट्रक्चर्ड लॉग डेटा लिखने के लिए जेनकिट फ़्रेमवर्क में इस्तेमाल किए जाने वाले लॉगर को कंट्रोल करने के लिए, प्लगिन को telemetry ऑब्जेक्ट पर logger प्रॉपर्टी देनी होगी, जो LoggerConfig इंटरफ़ेस के मुताबिक हो:

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

सबसे लोकप्रिय लॉगिंग फ़्रेमवर्क इसका पालन करते हैं. ऐसा ही एक फ़्रेमवर्क है 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}`;
      }),
    });
  }
};

लॉग और ट्रेस लिंक करना

अक्सर अपने लॉग स्टेटमेंट को प्लगिन से एक्सपोर्ट किए गए OpenTelemetry ट्रेस के साथ लिंक किया जाना चाहिए. लॉग स्टेटमेंट को OpenTelemetry फ़्रेमवर्क से सीधे एक्सपोर्ट नहीं किया जाता है, इसलिए ऐसा करना मुश्किल होता है. अच्छी बात यह है कि OpenTelemetry ऐसे इंस्ट्रुमेंटेशन का इस्तेमाल करता है जो winston और pino जैसे लोकप्रिय लॉगिंग फ़्रेमवर्क के लिए लॉग स्टेटमेंट पर ट्रेस और स्पैन आईडी को कॉपी करते हैं. @opentelemetry/auto-instrumentations-node पैकेज का इस्तेमाल करके, इन (और अन्य) इंस्ट्रुमेंटेशन को अपने-आप कॉन्फ़िगर किया जा सकता है. हालांकि, कुछ मामलों में आपको ट्रेस और स्पैन के लिए, फ़ील्ड के नाम और वैल्यू को कंट्रोल करने की ज़रूरत पड़ सकती है. ऐसा करने के लिए, आपको अपने TelemetryConfig से मिले NodeSDK कॉन्फ़िगरेशन के लिए, पसंद के मुताबिक LogHook इंस्ट्रुमेंटेशन की सुविधा देनी होगी:

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 फ़्रेमवर्क इस बात की गारंटी देगा कि प्लगिन के LoggerConfig से पहले आपके प्लगिन का TelemetryConfig शुरू हो जाएगा. हालांकि, आपको इस बात का ध्यान रखना होगा कि जब तक LoggerConfig शुरू नहीं होता, तब तक लॉगर इंपोर्ट न किया जाए. उदाहरण के लिए, ऊपर दिए गए engagementConfig में इस तरह से बदलाव किया जा सकता है:

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 एक उपयोगी डाइग्नोस्टिक टूल उपलब्ध कराता है. इससे, समस्या की सोर्स का पता लगाने में मदद मिलती है.