Genkit Telemetry Eklentisi yazma

OpenTelemetry; izlerin, metriklerin ve günlüklerin toplanmasını destekler. Firebase Genkit, Node.js SDK'sını yapılandıran bir telemetri eklentisi yazılarak tüm telemetri verilerini herhangi bir OpenTelemetry özellikli sisteme aktaracak şekilde genişletilebilir.

Yapılandırma

Telemetri dışa aktarma işlemini kontrol etmek için eklentinizin PluginOptions nesnesi, Genkit'in yapılandırmasındaki telemetry bloğuna uygun bir telemetry nesnesi sağlamalıdır.

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

Bu nesne iki ayrı yapılandırma sağlayabilir:

  • instrumentation: Traces ve Metrics için OpenTelemetry yapılandırması sağlar.
  • logger: Genkit tarafından sağlanan, Genkit akışlarının girişleri ve çıkışları dahil olmak üzere yapılandırılmış günlük verilerini yazmak için kullanılan temel logger'ı sağlar.

Node.js OpenTelemetry SDK'sının günlük kaydı işlevi hâlâ geliştirme aşamasında olduğundan bu ayırma işlemi şu anda gereklidir. Günlük kaydı ayrı olarak sağlanır. Böylece eklenti, verilerin nereye yazıldığını açık bir şekilde kontrol edebilir.

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;

Yukarıdaki kod bloğuyla, eklentiniz artık Genkit'e geliştiriciler tarafından kullanılabilecek bir telemetri yapılandırması sağlayacaktır.

Enstrümantasyon

İzlerin ve metriklerin dışa aktarımını kontrol etmek için eklentinizin, telemetry nesnesinde TelemetryConfig arayüzüne uygun bir instrumentation özelliği sağlaması gerekir:

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

Bu işlem, NodeSDK'i başlatmak için Genkit çerçevesi tarafından kullanılacak bir Partial<NodeSDKConfiguration> sağlar. Bu sayede eklenti, OpenTelemetry entegrasyonunun Genkit tarafından nasıl kullanılacağını tam olarak kontrol edebilir.

Örneğin, aşağıdaki telemetri yapılandırması basit bir bellek içi izleme ve metrik dışa aktarıcısı sağlar:

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

Logger

Eklenti, yapılandırılmış günlük verileri yazmak amacıyla Genkit çerçevesi tarafından kullanılan günlük kaydediciyi kontrol etmek için telemetry nesnesinde LoggerConfig arayüzüne uygun bir logger özelliği sağlamalıdır:

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

En popüler günlük kaydı çerçeveleri de buna uygundur. Bu çerçevelerden biri de winston'dır. Bu çerçeve, günlük verilerini doğrudan seçtiğiniz bir konuma aktarabilen taşıyıcıların yapılandırılmasına olanak tanır.

Örneğin, konsola günlük verileri yazan bir Windows logger sağlamak için eklenti günlük kaydedicinizi aşağıdakileri kullanacak şekilde güncelleyebilirsiniz:

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

Günlükleri ve İzleri bağlama

Genellikle günlük ifadelerinizin, eklentiniz tarafından dışa aktarılan OpenTelemetry izleriyle ilişkilendirilmesi istenir. Günlük ifadeleri doğrudan OpenTelemetry çerçevesi tarafından dışa aktarılmadığından, bu durum kullanıma hazır olarak gerçekleşmiyor. Neyse ki OpenTelemetry, winston ve pino gibi popüler günlük kaydı çerçevelerinin iz ve aralık kimliklerini günlük ifadelerine kopyalayan araçları destekler. @opentelemetry/auto-instrumentations-node paketini kullanarak bu (ve diğer) araçların otomatik olarak yapılandırılmasını sağlayabilirsiniz ancak bazı durumlarda, izler ve aralıklar için alan adlarını ve değerlerini kontrol etmeniz gerekebilir. Bunu yapmak için TelemetryConfig tarafından sağlanan NodeSDK yapılandırmasına özel bir LogHook araçları sağlamanız gerekir:

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

Örnek, OpenTelemetry NodeSDK için tüm otomatik araçları etkinleştirir ve daha sonra iz ve aralık kimliklerini günlük mesajındaki özel alanlara yazan özel bir WinstonInstrumentation sağlar.

Genkit çerçevesi, eklentinizin TelemetryConfig öğesinin eklentinizin LoggerConfig öğesinden önce başlatılmasını garanti eder ancak temel günlükleyicinin LoggerConfig başlatılıncaya kadar içe aktarılmadığından emin olmanız gerekir. Örneğin, yukarıdaki LoggingConfig şu şekilde değiştirilebilir:

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

Tam Örnek

Aşağıda, yukarıda oluşturulan telemetri eklentisinin tam örneği verilmiştir. Gerçek bir örnek için @genkit-ai/google-cloud eklentisine göz atın.

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;

Sorun giderme

Verilerin beklediğiniz yerde gösterilmesini sağlama konusunda sorun yaşıyorsanız OpenTelemetry, sorunun kaynağını bulmanıza yardımcı olacak faydalı bir Teşhis aracı sağlar.