构建日志数据源插件
Grafana 数据源插件支持指标、日志和其他数据类型。构建日志数据源插件与构建指标数据源插件的大致步骤相同,但也有一些差异,我们将在本指南中解释。
开始之前
本指南假设您已经熟悉如何为指标构建数据源插件。我们建议在继续之前查看这些材料。
为您的数据源添加日志支持
要为现有数据源添加日志支持,您需要
- 启用日志支持
- 构建日志数据帧
完成这些步骤后,您可以使用一个或多个可选功能来改进用户体验。
步骤 1:启用日志支持
通过将 "logs": true
添加到 plugin.json 文件来告诉 Grafana 您的数据源插件可以返回日志数据。
{
"logs": true
}
步骤 2:构建日志数据帧
日志数据帧格式
日志数据帧应包含以下字段
字段名称 | 字段类型 | 必需字段 | 描述 |
---|---|---|---|
timestamp | time | 必需 | 时间戳,非空。 |
正文 | 字符串 | 必需 | 日志行的内容,非空。 |
严重性 | 字符串 | 可选 | 日志行的严重性/级别。如果没有找到严重性字段,消费者/客户端将决定日志级别。有关日志级别的更多信息,请参阅日志集成。 |
id | 字符串 | 可选 | 日志行的唯一标识符。 |
标签 | json原始消息 (Go)或其他 (TypeScript) | 可选 | 日志行的其他标签。其他系统可能用不同的名称来引用此标签,例如“属性”。在JavaScript中以Record<string,any> 类型表示其值。 |
日志数据帧的type
需要在数据帧的元信息中设置为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,以允许您的用户充分利用日志UI和Explore中的功能。
以下步骤展示了通过无缝集成在数据源插件中添加对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
信息中包含搜索词。
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
属性中包含一个error
。 - 常见标签:显示在所有显示的日志行中相同的
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"
}
},
});
使用数据链接和URL进行跟踪的日志
如果您的日志数据包含跟踪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中的一个功能,该功能通过使用Explore启用实时日志结果流。要为您的数据源启用实时查看,请按照以下步骤操作
- 在
plugin.json
中启用流:在您的数据源插件plugin.json
文件中,将streaming
属性设置为true
。这允许Explore识别并启用您数据源的实时查看控件。
{
"type": "datasource",
"name": "Example",
"id": "example",
"logs": true,
"streaming": true
}
- 确保您的数据源的
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);
}
}
日志上下文
通过DataSourceWithXXXSupport
接口实现此功能。
日志上下文是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.
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存储库以外的外部插件。内部API @internal
目前处于开发中。
如果您正在开发一个同时处理日志和跟踪的数据源插件,并且您的日志数据包含跟踪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存储库以外的外部插件。API @alpha
目前处于开发中。
它允许插件开发者通过实现方法 getLogRowContextUi?(row: LogRowModel, runContextQuery?: () => void, origQuery?: TQuery): React.ReactNode;
在上下文视图中显示自定义UI。