Genkit-Telemetrie-Plug-in schreiben

OpenTelemetry unterstützt das Erfassen von Traces, Messwerten und Logs. Firebase Genkit kann erweitert werden, um alle Telemetriedaten in jedes OpenTelemetry-fähige System zu exportieren. Dazu wird ein Telemetrie-Plug-in geschrieben, das die Node.js SDK.

Konfiguration

Zum Steuern des Telemetrieexports muss das PluginOptions Ihres Plug-ins ein telemetry-Objekt, das dem telemetry-Block in der Genkit-Konfiguration entspricht.

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

Dieses Objekt kann zwei separate Konfigurationen bereitstellen:

  • instrumentation: Bietet OpenTelemetry-Konfiguration für Traces und Metrics.
  • logger: stellt den zugrunde liegenden Logger bereit, der von Genkit zum Schreiben verwendet wird strukturierte Logdaten einschließlich Ein- und Ausgaben von Genkit-Abläufen.

Diese Trennung ist derzeit erforderlich, da die Logging-Funktionen für das Node.js OpenTelemetry SDK noch in der Entwicklung sind. Die Protokollierung wird separat bereitgestellt, damit ein Plug-in steuern kann, wo die Daten gespeichert werden. explizit geschrieben.

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;

Mit dem obigen Codeblock stellt das Plug-in nun Genkit eine Telemetrie zur Verfügung Konfiguration, die von Entwickelnden verwendet werden kann.

Instrumentierung

Um den Export von Traces und Messwerten zu steuern, muss Ihr Plug-in eine instrumentation-Eigenschaft für das telemetry-Objekt fest, das den TelemetryConfig-Schnittstelle:

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

Dadurch wird ein Partial<NodeSDKConfiguration> bereitgestellt, das vom Genkit-Framework zum Starten der NodeSDK verwendet wird. So hat das Plug-in die vollständige Kontrolle darüber, wie die OpenTelemetry-Integration von Genkit verwendet wird.

Die folgende Telemetriekonfiguration stellt beispielsweise einen einfachen In-Memory-Trace- und Messwertexporter bereit:

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

Um den Protokoller zu steuern, der vom Genkit-Framework zum Schreiben strukturierter Protokolldaten verwendet wird, muss das Plug-in eine logger-Property für das telemetry-Objekt bereitstellen, die der LoggerConfig-Schnittstelle entspricht:

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

Die meisten gängigen Logging-Frameworks berücksichtigen dies. Ein solches Framework ist winston, mit dem sich Transporter konfigurieren lassen, die die Protokolldaten direkt an einen beliebigen Ort pushen können.

Um beispielsweise einen Winston-Logger bereitzustellen, der Protokolldaten in die Konsole schreibt, können Sie Ihren Plug-in-Logger mit Folgendem aktualisieren:

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

Logs und Traces verknüpfen

Häufig ist es wünschenswert, dass Ihre Protokollanweisungen mit den von Ihrem Plug-in exportierten OpenTelemetry-Spuren korreliert sind. Da die Log-Anweisungen nicht direkt vom OpenTelemetry-Framework exportiert wird, geschieht nicht . Glücklicherweise unterstützt OpenTelemetry Instrumentierungen, die Traces und Span-IDs für Log-Anweisungen für gängige Logging-Frameworks wie winston und pino. Mit dem Paket @opentelemetry/auto-instrumentations-node können Sie diese (und andere) Instrumente automatisch konfigurieren lassen. In einigen Fällen müssen Sie die Feldnamen und -werte für Traces und Spans. Dazu müssen Sie eine benutzerdefinierte LogHook-Instrumentierung für die NodeSDK-Konfiguration bereitstellen, die von Ihrem TelemetryConfig bereitgestellt wird:

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

Im Beispiel werden alle automatischen Instrumentierungen für die OpenTelemetry-NodeSDK aktiviert und dann eine benutzerdefinierte WinstonInstrumentation bereitgestellt, die die Trace- und Span-IDs in benutzerdefinierte Felder der Protokollnachricht schreibt.

Das Genkit-Framework garantiert, dass das TelemetryConfig Ihres Plug-ins vor dem LoggerConfig des Plug-ins initialisiert wurde. Stellen Sie sicher, dass der zugrunde liegende Logger erst importiert wird, wenn die Protokollierungskonfiguration aktiviert ist. initialisiert. Das obige LoggingConfig-Objekt kann beispielsweise so geändert werden:

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

Vollständiges Beispiel

Im Folgenden finden Sie ein vollständiges Beispiel für das oben erstellte Telemetrie-Plug-in. Ein praktisches Beispiel ist das @genkit-ai/google-cloud-Plug-in.

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;

Fehlerbehebung

Wenn Sie Probleme haben, Daten an der erwarteten Stelle zu sehen, bietet OpenTelemetry ein nützliches Diagnosetool, mit dem Sie die Ursache des Problems ermitteln können.