O OpenTelemetry oferece suporte à coleta de traces, métricas e registros. O Firebase Genkit pode ser estendido para exportar todos os dados de telemetria para qualquer sistema compatível com o OpenTelemetry. Basta gravar um plug-in de telemetria que configure o SDK do Node.js.
Configuração
Para controlar a exportação de telemetria, o PluginOptions
do plug-in precisa fornecer um objeto telemetry
que esteja em conformidade com o bloco telemetry
na configuração do Genkit.
export interface InitializedPlugin {
...
telemetry?: {
instrumentation?: Provider<TelemetryConfig>;
logger?: Provider<LoggerConfig>;
};
}
Esse objeto pode fornecer duas configurações separadas:
instrumentation
: fornece a configuração do OpenTelemetry paraTraces
eMetrics
.logger
: fornece o logger subjacente usado pelo Genkit para gravar dados de registro estruturados, incluindo entradas e saídas de fluxos do Genkit.
Atualmente, essa separação é necessária porque a funcionalidade de geração de registros para o SDK do OpenTelemetry do Node.js ainda está em desenvolvimento. O Logging é fornecido separadamente para que um plug-in possa controlar onde os dados são gravados explicitamente.
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;
Com o bloco de código acima, seu plug-in agora fornecerá ao Genkit uma configuração de telemetria que pode ser usada pelos desenvolvedores.
Instrumentação
Para controlar a exportação de traces e métricas, seu plug-in precisa fornecer uma propriedade instrumentation
no objeto telemetry
que esteja em conformidade com a interface TelemetryConfig
:
interface TelemetryConfig {
getConfig(): Partial<NodeSDKConfiguration>;
}
Isso fornece um Partial<NodeSDKConfiguration>
que será usado pelo
framework do Genkit para iniciar o
NodeSDK
.
Isso dá ao plug-in controle total de como a integração do OpenTelemetry é usada
pelo Genkit.
Por exemplo, a configuração de telemetria a seguir oferece um exportador de métricas e traces simples na memória:
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
Para controlar o logger usado pelo framework do Genkit para gravar dados de registro estruturados, o plug-in precisa fornecer uma propriedade logger
no objeto telemetry
que esteja em conformidade com a interface LoggerConfig
:
interface LoggerConfig {
getLogger(env: string): any;
}
{
debug(...args: any);
info(...args: any);
warn(...args: any);
error(...args: any);
level: string;
}
Os frameworks de geração de registros mais conhecidos seguem esse modelo. Um desses frameworks é o winston, que permite configurar transportadores que podem enviar diretamente os dados de registro para um local de sua escolha.
Por exemplo, para fornecer um logger do winston que grava dados de registro no console, é possível atualizar o logger do plug-in para usar o seguinte:
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}`;
}),
});
}
};
Como vincular registros e traces
Muitas vezes, é desejável ter os log statements correlacionados com os
rastros do OpenTelemetry exportados pelo plug-in. Como os log statements não são exportados diretamente pelo framework do OpenTelemetry, isso não acontece imediatamente. O OpenTelemetry oferece suporte a instrumentações que copiam IDs de trace
e de período em log statements para frameworks de geração de registro conhecidos, como winston
e pino (links em inglês). Ao usar o pacote @opentelemetry/auto-instrumentations-node
,
essas e outras instrumentações podem ser configuradas automaticamente. No entanto, em
alguns casos, pode ser necessário controlar os nomes e valores de campos para traces e
períodos. Para isso, é preciso fornecer uma instrumentação personalizada do LogHook à
configuração do NodeSDK fornecida pelo 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;
},
}),
]);
O exemplo ativa todas as instrumentações automáticas para o NodeSDK
do OpenTelemetry e,
em seguida, fornece um WinstonInstrumentation
personalizado que grava os IDs de trace e
de período em campos personalizados na mensagem de registro.
O framework do Genkit garante que o TelemetryConfig
do plug-in será
inicializado antes do LoggerConfig
do plug-in, mas é preciso garantir
que o logger subjacente não seja importado até que o LoggerConfig seja
inicializado. Por exemplo, o loggingConfig acima pode ser modificado da seguinte maneira:
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}`;
}),
});
},
};
Exemplo completo
Confira a seguir um exemplo completo do plug-in de telemetria criado acima. Para
ver um exemplo real, dê uma olhada no plug-in @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;
Solução de problemas
Se você estiver com dificuldade para que os dados apareçam onde esperado, o OpenTelemetry fornece uma ferramenta de diagnóstico útil que ajuda a localizar a origem do problema.