构建日志数据源插件
Grafana 数据源插件支持指标、日志和其他数据类型。构建日志数据源插件的步骤与构建指标数据源插件的步骤基本相同,但有一些区别,我们将在本指南中进行说明。
开始之前
本指南假定您已经熟悉如何 构建指标数据源插件。我们建议您在继续之前复习这些材料。
为您的数据源添加日志支持
要为现有数据源添加日志支持,您需要
- 启用日志支持
- 构建日志数据帧
完成这些步骤后,您可以通过一个或多个 可选功能 来改进用户体验。
步骤 1:启用日志支持
通过在 plugin.json 文件中添加 "logs": true
,告诉 Grafana 您的数据源插件可以返回日志数据。
{
"logs": true
}
步骤 2:构建日志数据帧
日志数据帧格式
日志数据帧应包含以下字段
字段名称 | 字段类型 | 必需字段 | 描述 |
---|---|---|---|
timestamp | 时间 | 必需 | 时间戳,不可为空。 |
body | 字符串 | 必需 | 日志行的内容,不可为空。 |
severity | 字符串 | 可选 | 日志行的严重程度/级别。如果未找到严重程度字段,消费者/客户端将决定日志级别。有关日志级别的更多信息,请参阅 日志集成。 |
id | 字符串 | 可选 | 日志行的唯一标识符。 |
labels | json raw message (Go) 或 other (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,以允许您的用户充分利用 Explore 中的日志 UI 及其功能。
以下步骤显示了通过无缝集成在数据源插件中添加对 Explore 功能支持的过程。实现这些 API 可以增强用户体验,并利用 Explore 的强大日志调查功能。
在 Explore 的日志视图中显示日志结果
要确保您的日志结果显示在交互式日志视图中,您必须在日志结果数据帧中添加一个 meta
属性到 preferredVisualisationType
。
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
字段,该字段对于指定的索引是唯一的。在这种情况下,为了确保唯一性,索引名称
和 _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 启用实时日志结果流。要为您的数据源启用实时跟踪,请按照以下步骤操作
- 在
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
存储库中的数据源。它们不支持外部插件开发人员。
显示全范围日志量
此功能目前不支持 Grafana 存储库之外的外部插件。它通过实现 DataSourceWithXXXSupport
接口在数据源中实现。
使用 全范围日志量,Explore 显示一个图表,显示所有输入的日志查询的日志分布。要将全范围日志量支持添加到数据源插件,请使用 DataSourceWithSupplementaryQueriesSupport
API。
如何在数据源中实现 DataSourceWithSupplementaryQueriesSupport
API
此 API 必须在数据源的 typescript 代码中实现。
import { queryLogsVolume } from '../features/logs/logsModel'; // This is currently not available for use outside of the Grafana repo
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;
}
}
// Returns an observable that will be used to fetch supplementary data based on the provided
// supplementary query type and original request.
getDataProvider(
type: SupplementaryQueryType,
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(type)) {
return undefined;
}
switch (type) {
case SupplementaryQueryType.LogsVolume:
return this.getLogsVolumeDataProvider(request);
default:
return undefined;
}
}
// Be sure to adjust this example based your data source logic.
private getLogsVolumeDataProvider(
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
const logsVolumeRequest = cloneDeep(request);
const targets = logsVolumeRequest.targets
.map((query) => this.getSupplementaryQuery({ type: SupplementaryQueryType.LogsVolume }, query))
.filter((query): query is ExampleQuery => !!query);
if (!targets.length) {
return undefined;
}
// Use imported queryLogsVolume.
return queryLogsVolume(
this,
{ ...logsVolumeRequest, targets },
{
// Implement extract level to produce color-coded graph.
extractLevel: (dataFrame: DataFrame) => LogLevel.unknown,
range: request.range,
targets: request.targets,
}
);
}
}
日志示例
此功能目前不支持 Grafana 存储库之外的外部插件。通过实现 DataSourceWithXXXSupport
接口在数据源中实现此 API。
当您的数据源同时支持日志和指标时,日志示例 功能是一个宝贵的补充。它使用户能够查看有助于可视化指标的日志行的示例,从而提供对数据的更深入了解。
要在您的数据源插件中实现日志示例支持,您可以使用 DataSourceWithSupplementaryQueriesSupport
API。
import { queryLogsSample } from '../features/logs/logsModel'; // This is currently not possible to use outside of Grafana repo
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;
}
}
// Returns an observable that will be used to fetch supplementary data based on the provided supplementary query type and original request.
getDataProvider(
type: SupplementaryQueryType,
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(type)) {
return undefined;
}
switch (type) {
case SupplementaryQueryType.LogsSample:
return this.getLogsSampleDataProvider(request);
default:
return undefined;
}
}
private getLogsSampleDataProvider(
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
const logsSampleRequest = cloneDeep(request);
const targets = logsVolumeRequest.targets
.map((query) => this.getSupplementaryQuery({ type: SupplementaryQueryType.LogsVolume }, query))
.filter((query): query is ExampleQuery => !!query);
if (!targets.length) {
return undefined;
}
// Use imported queryLogsSample
return queryLogsSample(this, { ...logsVolumeRequest, 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。