Pisanie wtyczki Genkit Telemetry

OpenTelemetry obsługuje zbieranie logów czasu, wskaźników i logów. Firebase Genkit można rozszerzyć do eksportowania wszystkich danych telemetrycznych do dowolnego systemu obsługującego OpenTelemetry. W tym celu należy napisać wtyczkę telemetryczną, która konfiguruje Node.js SDK.

Konfiguracja

Aby kontrolować eksportowanie danych telemetrycznych, PluginOptions wtyczki musi udostępniać Obiekt telemetry zgodny z blokiem telemetry w konfiguracji Genkit.

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

Ten obiekt może udostępniać 2 oddzielne konfiguracje:

  • instrumentation: udostępnia konfigurację OpenTelemetry dla interfejsów Traces i Metrics
  • logger: udostępnia rejestrator używany przez Genkit do zapisu. uporządkowane dane dziennika, w tym dane wejściowe i wyjściowe przepływów Genkit.

Ten rozdział jest obecnie potrzebny, ponieważ funkcja logowania w Node.js Pakiet OpenTelemetry SDK jest nadal rozwijany. Logowanie jest dostępne oddzielnie, więc wtyczka może kontrolować, gdzie znajdują się dane napisane wprost.

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;

Dzięki powyższemu blokowi kodu wtyczka będzie teraz udostępniać Genkit dane telemetryczne , z której mogą korzystać programiści.

Instrumentacja

Aby kontrolować eksportowanie logów czasu i wskaźników, wtyczka musi udostępniać właściwość instrumentation w obiekcie telemetry zgodną z Interfejs TelemetryConfig:

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

Zapewnia to obiekt Partial<NodeSDKConfiguration>, który będzie używany przez Genkit do uruchamiania NodeSDK. Daje to wtyczce pełną kontrolę nad sposobem korzystania z integracji OpenTelemetry od Genkit.

Na przykład ta konfiguracja telemetrii zapewnia prosty eksporter śledzenia i wskaźników w pamięci:

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

Rejestrator

Aby kontrolować rejestrator używany przez platformę Genkit do zapisywania uporządkowanych danych logów: wtyczka musi zawierać w obiekcie telemetry właściwość logger zgodną z Interfejs LoggerConfig:

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

Zgodne są z tym większość popularnych platform logowania. Jednym z takich rozwiązań jest winston, która umożliwia skonfigurowanie Transportery, które mogą bezpośrednio przenosić dane dziennika do wybranej lokalizacji.

Aby na przykład udostępnić rejestrator Winston, który zapisuje dane dziennika w konsoli, możesz zaktualizować rejestrator wtyczek, aby używał:

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

Łączenie logów i logów czasu

Często dobrze jest, aby instrukcje dziennika były powiązane z Logi czasu OpenTelemetry wyeksportowane przez Twoją wtyczkę. Ponieważ wyciągi dziennika nie są eksportowanych bezpośrednio przez platformę OpenTelemetry. Nie zdarza się to bezpośrednio w . Na szczęście OpenTelemetry obsługuje instrumenty, które skopiują log czasu. i dodawania identyfikatorów span do instrukcji logowania w przypadku popularnych platform logowania, takich jak winston. i pino. Korzystając z pakietu @opentelemetry/auto-instrumentations-node, możesz skonfigurować te (i inne) instrumenty automatycznie, ale w niektórych przypadkach trzeba kontrolować nazwy i wartości pól w śladach oraz spany. Aby to zrobić, musisz udostępnić niestandardową instrumentację LogHook do konfiguracja NodeSDK udostępniona przez 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;
      },
    }),
  ]);

Ten przykład włącza wszystkie instrumenty automatyczne na potrzeby OpenTelemetry NodeSDK, a potem udostępnia niestandardowy element WinstonInstrumentation, który zapisuje ślad i identyfikatory spanów do pól niestandardowych w komunikacie logu.

Platforma Genkit gwarantuje, że TelemetryConfig wtyczki zostanie została zainicjowana przed żądaniem LoggerConfig wtyczki, ale musisz sprawdzić, czy upewnij się, że bazowy rejestrator nie zostanie zaimportowany, dopóki konfiguracja LoggerConfig nie zostanie zainicjowano. Na przykład powyższą konfigurację LoggingConfig można zmodyfikować w ten sposób:

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

Pełny przykład

Poniżej znajdziesz pełny przykład utworzonej powyżej wtyczki telemetrycznej. Dla: z jakiegoś przykładu, przyjrzyj się wtyczce @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;

Rozwiązywanie problemów

Jeśli masz problemy z wyświetleniem danych w oczekiwanych miejscach, OpenTelemetry Narzędzie diagnostyczne co pomaga w znalezieniu źródła problemu.