跳到主内容

构建日志数据源插件

Grafana 数据源插件支持指标、日志和其他数据类型。构建日志数据源插件的步骤与构建指标数据源插件的步骤大体相同,但有一些差异,我们将在此指南中进行解释。

开始之前

本指南假定您已熟悉如何构建针对指标的数据源插件。建议您在继续之前查阅此材料。

向数据源添加日志支持

要向现有数据源添加日志支持,您需要:

  1. 启用日志支持
  2. 构建日志数据帧

完成这些步骤后,您可以通过一项或多项可选功能来改善用户体验。

步骤 1:启用日志支持

通过在 plugin.json 文件中添加 "logs": true,告诉 Grafana 您的数据源插件可以返回日志数据。

src/plugin.json
{
"logs": true
}

步骤 2:构建日志数据帧

日志数据帧格式

日志数据帧应包含以下字段

字段名称字段类型必需字段描述
timestamptime必需时间戳,不可为空。
bodystring必需日志行的内容,不可为空。
severitystring可选日志行的严重性/级别。如果未找到 severity 字段,则消费者/客户端将决定日志级别。有关日志级别的更多信息,请参阅 日志集成
idstring可选日志行的唯一标识符。
labelsjson raw message (Go) 或 other (TypeScript)可选日志行的附加标签。其他系统可能使用不同的名称(如“attributes”)来指代此字段。在 JavaScript 中,将其值表示为 Record<string,any> 类型。

日志数据帧的 type 在数据帧的 meta 中需要设置为 type: DataFrameType.LogLines

在 Go 中构建日志数据帧的示例

frame := data.NewFrame(
"logs",
data.NewField("timestamp", nil, []time.Time{time.UnixMilli(1645030244810), time.UnixMilli(1645030247027), time.UnixMilli(1645030247027)}),
data.NewField("body", nil, []string{"message one", "message two", "message three"}),
data.NewField("severity", nil, []string{"critical", "error", "warning"}),
data.NewField("id", nil, []string{"xxx-001", "xyz-002", "111-003"}),
data.NewField("labels", nil, []json.RawMessage{[]byte(`{}`), []byte(`{"hello":"world"}`), []byte(`{"hello":"world", "foo": 123.45, "bar" :["yellow","red"], "baz" : { "name": "alice" }}`)}),
)

frame.SetMeta(&data.FrameMeta{
Type: data.FrameTypeLogLines,
})

在 TypeScript 中构建日志数据帧的示例

import { createDataFrame, DataFrameType, FieldType } from '@grafana/data';

const result = createDataFrame({
fields: [
{ name: 'timestamp', type: FieldType.time, values: [1645030244810, 1645030247027, 1645030247027] },
{ name: 'body', type: FieldType.string, values: ['message one', 'message two', 'message three'] },
{ name: 'severity', type: FieldType.string, values: ['critical', 'error', 'warning'] },
{ name: 'id', type: FieldType.string, values: ['xxx-001', 'xyz-002', '111-003'] },
{
name: 'labels',
type: FieldType.other,
values: [{}, { hello: 'world' }, { hello: 'world', foo: 123.45, bar: ['yellow', 'red'], baz: { name: 'alice' } }],
},
],
meta: {
type: DataFrameType.LogLines,
},
});

使用可选功能增强日志数据源插件

您可以使用以下可选功能来增强您在 Explore 和日志面板中的日志数据源插件。

Explore 提供了一个有用的界面,用于调查事件和故障排除日志。如果数据源生成日志结果,我们强烈建议实现以下 API,以使用户能够充分利用 Explore 中的日志 UI 及其功能。

以下步骤展示了通过无缝集成向数据源插件添加 Explore 功能支持的过程。实现这些 API 可以增强用户体验,并利用 Explore 强大的日志调查功能。

在 Explore 的日志视图中显示日志结果

为确保您的日志结果显示在交互式日志视图中,您必须在日志结果数据帧的 preferredVisualisationType 中添加一个 meta 属性。

Go 示例

frame.Meta = &data.FrameMeta{
PreferredVisualization: "logs",
}

TypeScript 示例

import { createDataFrame } from '@grafana/data';

const result = createDataFrame({
fields: [...],
meta: {
preferredVisualisationType: 'logs',
},
});

高亮搜索词

注意

此功能必须作为 meta 属性在数据帧中实现。

日志可视化可以高亮日志条目中的特定词或字符串。此功能通常用于高亮搜索词,使用户更容易在日志中找到并关注相关信息。要使高亮功能起作用,您必须在数据帧的 meta 信息中包含搜索词。

Go 示例

frame.Meta = &data.FrameMeta{
Custom: map[string]interface{}{
"searchWords": []string{"foo", "bar", "baz"} ,
}
}

TypeScript 示例

import { createDataFrame } from '@grafana/data';

const result = createDataFrame({
fields: [...],
meta: {
custom: {
searchWords: ["foo", "bar", "baz"],
}
},
});

日志结果 meta 信息

注意

此功能必须作为 meta 属性或作为字段在数据帧中实现。

日志结果元信息可用于向用户传达有关日志结果的信息。以下信息可以与用户共享:

  • 接收到的日志数量与限制 - 显示接收到的日志数量与指定限制的对比。数据帧应使用 meta 属性设置请求的日志行数量限制。
  • 错误:显示日志结果中可能出现的错误。数据帧应在 meta 属性中包含一个 error
  • 通用标签:显示 labels 数据帧字段中存在且所有显示的日志行都相同的标签。此功能支持生成包含 labels 字段的日志数据帧的数据源。有关更多信息,请参阅日志数据帧格式

Go 示例

frame.Meta = &data.FrameMeta{
Custom: map[string]interface{}{
"limit": 1000,
"error": "Error information",
}
}

TypeScript 示例

import { createDataFrame } from '@grafana/data';

const result = createDataFrame({
fields: [...],
meta: {
custom: {
limit: 1000,
error: "Error information"
}
},
});

如果您的日志数据包含追踪 ID,您可以通过添加一个包含*追踪 ID 值*和*URL 数据链接*的字段来增强您的日志数据帧。这些链接应使用追踪 ID 值准确链接到相应的追踪。此增强功能使用户能够从日志行无缝跳转到相关的追踪。

TypeScript 示例

import { createDataFrame, FieldType } from '@grafana/data';

const result = createDataFrame({
fields: [
...,
{ name: 'traceID',
type: FieldType.string,
values: ['a006649127e371903a2de979', 'e206649127z371903c3be12q' 'k777549127c371903a2lw34'],
config: {
links: [
{
// Be sure to adjust this example based on your data source logic.
title: 'Trace view',
url: `http://linkToTraceID/${__value.raw}` // ${__value.raw} is a variable that will be replaced with actual traceID value.
}
]
}
}
],
...,
});

颜色编码的日志级别

注意

此功能必须作为字段在数据帧中实现。

颜色编码的日志级别显示在每行日志的开头。它们允许用户快速评估日志条目的严重性,并促进日志分析和故障排除。日志级别由数据帧的 severity 字段确定。如果 severity 字段不存在,Grafana 会尝试根据日志行的内容评估级别。如果无法从内容中推断日志级别,则日志级别将设置为 unknown

有关更多信息,请参阅日志数据帧格式

注意

此功能必须作为字段在数据帧中实现。

复制日志行链接是一项功能,允许您生成指向特定日志行的链接,以便轻松共享和引用。Grafana 在生成包含 id 字段的日志数据源中支持此功能。

如果底层数据库不返回 id 字段,您可以在数据源内部实现一个。例如,在 Loki 数据源中,纳秒时间戳、标签和日志行内容的组合用于创建唯一的 id。另一方面,Elasticsearch 返回一个对于指定索引唯一的 _id 字段。在这种情况下,为了确保唯一性,会使用 index name_id 来创建唯一的 id

有关更多信息,请参阅日志数据帧格式

使用日志详情过滤字段

注意

通过数据源方法实现此功能。

每行日志都有一个可展开的部分,称为“日志详情”,您可以通过单击该行打开。在日志详情中,Grafana 显示与该日志条目相关的字段。如果数据源实现了 modifyQuery?(query: TQuery, action: QueryFixAction): TQuery; API,则每个字段都可使用过滤功能。对于日志,目前有两种过滤选项可用:

  • ADD_FILTER - 用于过滤包含选定字段的日志行。
  • ADD_FILTER_OUT - 用于过滤不包含选定字段的日志行。
export class ExampleDatasource extends DataSourceApi<ExampleQuery, ExampleOptions> {
modifyQuery(query: ExampleQuery, action: QueryFixAction): ExampleQuery {
let queryText = query.query ?? '';
switch (action.type) {
case 'ADD_FILTER':
if (action.options?.key && action.options?.value) {
// Be sure to adjust this example code based on your data source logic.
queryText = addLabelToQuery(queryText, action.options.key, '=', action.options.value);
}
break;
case 'ADD_FILTER_OUT':
{
if (action.options?.key && action.options?.value) {
// Be sure to adjust this example code based on your data source logic.
queryText = addLabelToQuery(queryText, action.options.key, '!=', action.options.value);
}
}
break;
}
return { ...query, query: queryText };
}
}

实时追踪日志

注意

在数据源方法中实现此功能,并在 plugin.json 中启用。

实时追踪日志是一项功能,可在 Explore 中实现日志结果的实时流式传输。要为您的数据源启用实时追踪日志,请按照以下步骤操作:

  1. plugin.json 中启用流式传输:在您的数据源插件的 plugin.json 文件中,将 streaming 属性设置为 true。这使得 Explore 能够识别并为您的数据源启用实时追踪日志控件。
{
"type": "datasource",
"name": "Example",
"id": "example",
"logs": true,
"streaming": true
}
  1. 确保您的数据源的 query 方法可以处理将 liveStreaming 设置为 true 的查询。
export class ExampleDatasource extends DataSourceApi<ExampleQuery, ExampleOptions> {
query(request: DataQueryRequest<ExampleQuery>): Observable<DataQueryResponse> {
// This is a mocked implementation. Be sure to adjust this based on your data source logic.
if (request.liveStreaming) {
return this.runLiveStreamQuery(request);
}
return this.runRegularQuery(request);
}
}

日志上下文

注意

通过 DataSourceWithLogsContextSupport 接口实现此功能。

日志上下文是 Explore 中的一项功能,可显示与特定搜索查询匹配的日志条目周围的其他上下文行。此功能允许用户通过查看日志条目在其相关上下文中的内容来更深入地了解日志数据。由于 Grafana 会显示周围的日志行,用户可以更好地理解事件序列以及日志条目发生的上下文,从而改进日志分析和故障排除。

import {
DataQueryRequest,
DataQueryResponse,
DataSourceWithLogsContextSupport,
LogRowContextOptions,
LogRowContextQueryDirection,
LogRowModel,
} from '@grafana/data';
import { catchError, lastValueFrom, of, switchMap, Observable } from 'rxjs';

export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithLogsContextSupport<ExampleQuery>
{
// Retrieve context for a given log row
async getLogRowContext(
row: LogRowModel,
options?: LogRowContextOptions,
query?: ExampleQuery
): Promise<DataQueryResponse> {
// Be sure to adjust this example implementation of createRequestFromQuery based on your data source logic.
// Remember to replace variables with `getTemplateSrv` and the passed `options.scopedVars` before returning your `request` object.
const request = createRequestFromQuery(row, query, options);
return lastValueFrom(
// Be sure to adjust this example of this.query based on your data source logic.
this.query(request).pipe(
catchError((err) => {
const error: DataQueryError = {
message: 'Error during context query. Please check JS console logs.',
status: err.status,
statusText: err.statusText,
};
throw error;
}),
// Be sure to adjust this example of processResultsToDataQueryResponse based on your data source logic.
switchMap((res) => of(processResultsToDataQueryResponse(res)))
)
);
}

// Retrieve the context query object for a given log row. This is currently used to open LogContext queries in a split view.
getLogRowContextQuery(
row: LogRowModel,
options?: LogRowContextOptions,
query?: ExampleQuery
): Promise<ExampleQuery | null> {
// Data source internal implementation that creates context query based on row, options and original query
}
}

正在开发的 API

这些 API 可用于 grafana/grafana 仓库中的数据源。它们不支持外部插件开发者。

显示全范围日志量

注意

通过实现 DataSourceWithXXXSupport 接口在数据源中实现。

借助全范围日志量功能,Explore 会显示一个图表,展示所有输入日志查询的日志分布。要向数据源插件添加全范围日志量支持,请使用 DataSourceWithSupplementaryQueriesSupport API。

如何在数据源中实现 DataSourceWithSupplementaryQueriesSupport API

注意

此 API 必须在数据源的 TypeScript 代码中实现。

import {
DataSourceWithSupplementaryQueriesSupport,
LogLevel,
SupplementaryQueryOptions,
SupplementaryQueryType,
} from '@grafana/data';

export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithSupplementaryQueriesSupport<ExampleQuery>
{
// Returns supplementary query types that data source supports.
getSupportedSupplementaryQueryTypes(): SupplementaryQueryType[] {
return [SupplementaryQueryType.LogsVolume];
}

// Returns a supplementary query to be used to fetch supplementary data based on the provided type and original query.
// If provided query is not suitable for provided supplementary query type, undefined should be returned.
getSupplementaryQuery(options: SupplementaryQueryOptions, query: ExampleQuery): ExampleQuery | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(options.type)) {
return undefined;
}

switch (options.type) {
case SupplementaryQueryType.LogsVolume:
// This is a mocked implementation. Be sure to adjust this based on your data source logic.
return { ...query, refId: `logs-volume-${query.refId}`, queryType: 'count' };
default:
return undefined;
}
}

// It generates a DataQueryRequest for a specific supplementary query type.
// @returns A DataQueryRequest for the supplementary queries or undefined if not supported.
getSupplementaryRequest(
type: SupplementaryQueryType,
request: DataQueryRequest<ExampleQuery>,
options?: SupplementaryQueryOptions
): DataQueryRequest<ExampleQuery> | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(type)) {
return undefined;
}

switch (type) {
case SupplementaryQueryType.LogsVolume:
const logsVolumeOption: LogsVolumeOption =
options?.type === SupplementaryQueryType.LogsVolume ? options : { type };
return this.getLogsVolumeDataProvider(request, logsVolumeOption);
default:
return undefined;
}
}

// Be sure to adjust this example based your data source logic.
private getLogsVolumeDataProvider(
request: DataQueryRequest<ExampleQuery>,
options: LogsVolumeOption
): DataQueryRequest<ExampleQuery> | undefined {
const logsVolumeRequest = cloneDeep(request);
const targets = logsVolumeRequest.targets
.map((query) => this.getSupplementaryQuery(options, query))
.filter((query): query is ExampleQuery => !!query);

if (!targets.length) {
return undefined;
}

return { ...logsVolumeRequest, targets };
}

日志样本

注意

通过实现 DataSourceWithXXXSupport 接口在数据源中实现此 API。

当您的数据源同时支持日志和指标时,日志样本功能是一项有价值的补充。它使用户能够查看有助于可视化指标的日志行样本,从而更深入地了解数据。

要在数据源插件中实现日志样本支持,可以使用 DataSourceWithSupplementaryQueriesSupport API。

import {
DataSourceWithSupplementaryQueriesSupport,
SupplementaryQueryOptions,
SupplementaryQueryType,
} from '@grafana/data';

export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithSupplementaryQueriesSupport<ExampleQuery>
{
// Returns supplementary query types that data source supports.
getSupportedSupplementaryQueryTypes(): SupplementaryQueryType[] {
return [SupplementaryQueryType.LogsSample];
}

// Returns a supplementary query to be used to fetch supplementary data based on the provided type and original query.
// If provided query is not suitable for provided supplementary query type, undefined should be returned.
getSupplementaryQuery(options: SupplementaryQueryOptions, query: ExampleQuery): ExampleQuery | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(options.type)) {
return undefined;
}

switch (options.type) {
case SupplementaryQueryType.LogsSample:
// Be sure to adjust this example based on your data source logic.
return { ...query, refId: `logs-sample-${query.refId}`, queryType: 'logs' };
default:
return undefined;
}
}

// It generates a DataQueryRequest for a specific supplementary query type.
// @returns A DataQueryRequest for the supplementary queries or undefined if not supported.
getSupplementaryRequest(
type: SupplementaryQueryType,
request: DataQueryRequest<ExampleQuery>,
options?: SupplementaryQueryOptions
): DataQueryRequest<ExampleQuery> | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(type)) {
return undefined;
}

switch (type) {
case SupplementaryQueryType.LogsSample:
const logsSampleOption: LogsSampleOptions =
options?.type === SupplementaryQueryType.LogsSample ? options : { type };
return this.getLogsSampleDataProvider(request, logsSampleOption);
default:
return undefined;
}
}

private getLogsSampleDataProvider(
request: DataQueryRequest<ExampleQuery>,
options?: LogsSampleOptions
): DataQueryRequest<ExampleQuery> | undefined {
const logsSampleRequest = cloneDeep(request);
const targets = logsSampleRequest.targets
.map((query) => this.getSupplementaryQuery({ type: SupplementaryQueryType.LogsSample, limit: 100 }, query))
.filter((query): query is ExampleQuery => !!query);

if (!targets.length) {
return undefined;
}
return { ...logsSampleRequest, targets };
}
}

有关如何在 Elasticsearch 数据源中实现日志样本的示例,请参阅PR 70258

注意

此功能目前不支持 Grafana 仓库外部的外部插件。@internal API 正在开发中。

如果您正在开发一个同时处理日志和追踪的数据源插件,并且您的日志数据包含追踪 ID,您可以通过添加一个包含追踪 ID 值和内部数据链接的字段来增强您的日志数据帧。这些链接应使用追踪 ID 值准确创建一个生成相关追踪的追踪查询。此增强功能使用户能够从日志行无缝跳转到追踪。

TypeScript 示例

import { createDataFrame } from '@grafana/data';

const result = createDataFrame({
fields: [
...,
{ name: 'traceID',
type: FieldType.string,
values: ['a006649127e371903a2de979', 'e206649127z371903c3be12q' 'k777549127c371903a2lw34'],
config: {
links: [
{
title: 'Trace view',
url: '',
internal: {
// Be sure to adjust this example with datasourceUid, datasourceName and query based on your data source logic.
datasourceUid: instanceSettings.uid,
datasourceName: instanceSettings.name,
query: {
{ ...query, queryType: 'trace', traceId: '${__value.raw}'}, // ${__value.raw} is a variable that will be replaced with actual traceID value.
}
}

}
]
}

}
],
...,
});

日志上下文查询编辑器

注意

此功能目前不支持 Grafana 仓库外部的外部插件。@alpha API 正在开发中。

通过实现 getLogRowContextUi?(row: LogRowModel, runContextQuery?: () => void, origQuery?: TQuery): React.ReactNode; 方法,插件开发者可以在上下文视图中显示自定义 UI。