OpenTelemetry 支持收集跟踪记录、指标和日志。扩展 Firebase Genkit 后,可以编写一个遥测插件来配置 Node.js SDK。
配置
如需控制遥测数据导出,插件的 PluginOptions
必须提供
符合 Genkit 配置中的 telemetry
代码块的 telemetry
对象。
export interface InitializedPlugin {
...
telemetry?: {
instrumentation?: Provider<TelemetryConfig>;
logger?: Provider<LoggerConfig>;
};
}
此对象可以提供两种单独的配置:
instrumentation
:为Traces
和Metrics
。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;
添加上述代码块后,您的插件现在将为 Genkit 提供遥测数据 供开发者使用的配置。
插桩
如需控制跟踪记录和指标的导出,您的插件必须提供
telemetry
对象的 instrumentation
属性,该对象符合
TelemetryConfig
接口:
interface TelemetryConfig {
getConfig(): Partial<NodeSDKConfiguration>;
}
这提供了一个 Partial<NodeSDKConfiguration>
,供
Genkit 框架
NodeSDK
。
这使插件能够完全控制 OpenTelemetry 集成的使用方式
Genkit
例如,以下遥测配置提供了一个简单的内存中跟踪记录和指标导出器:
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
如需控制 Genkit 框架用于写入结构化日志数据的日志记录器,
插件必须在 telemetry
对象上提供 logger
属性,该属性符合
LoggerConfig
接口:
interface LoggerConfig {
getLogger(env: string): any;
}
{
debug(...args: any);
info(...args: any);
warn(...args: any);
error(...args: any);
level: string;
}
大多数流行的日志记录框架都符合此要求。其中一种框架是 winston,可让您配置 。
例如,要提供将日志数据写入控制台的 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 支持会复制跟踪记录的插桩
并将 ID 跨越 winston 等常用日志记录框架的日志语句
和 pino。通过使用 @opentelemetry/auto-instrumentations-node
软件包,
您可以自动配置这些(和其他)插桩
在某些情况下,您可能需要控制跟踪记录的字段名称和值,
span。为此,您需要提供一个自定义 LogHook 插桩
TelemetryConfig
提供的 NodeSDK 配置:
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
,用于写入跟踪记录和
span ID 添加到日志消息中的自定义字段。
Genkit 框架可以保证您插件的 TelemetryConfig
在插件的 LoggerConfig
之前进行初始化,但您必须注意
确保在 LoggerConfig 创建完成之前,不会导入底层日志记录器
初始化。例如,可以按如下方式修改上面的 loggingConfig:
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 提供了 诊断工具 以便找出问题根源