|
|
|
|
@ -1,22 +1,16 @@ |
|
|
|
|
// Libraries
|
|
|
|
|
import { isEmpty, map as lodashMap } from 'lodash'; |
|
|
|
|
import { Observable, from, merge, of, iif, defer } from 'rxjs'; |
|
|
|
|
import { map, filter, catchError, switchMap, mergeMap } from 'rxjs/operators'; |
|
|
|
|
import { Observable, from, merge, of } from 'rxjs'; |
|
|
|
|
import { map, filter, catchError, switchMap } from 'rxjs/operators'; |
|
|
|
|
|
|
|
|
|
// Services & Utils
|
|
|
|
|
import { DataFrame, dateMath, FieldCache } from '@grafana/data'; |
|
|
|
|
import { getBackendSrv } from '@grafana/runtime'; |
|
|
|
|
import { addLabelToSelector, keepSelectorFilters } from 'app/plugins/datasource/prometheus/add_label_to_query'; |
|
|
|
|
import { DatasourceRequestOptions } from 'app/core/services/backend_srv'; |
|
|
|
|
import { getBackendSrv } from '@grafana/runtime'; |
|
|
|
|
import { TemplateSrv } from 'app/features/templating/template_srv'; |
|
|
|
|
import { safeStringifyValue, convertToWebSocketUrl } from 'app/core/utils/explore'; |
|
|
|
|
import { |
|
|
|
|
lokiResultsToTableModel, |
|
|
|
|
processRangeQueryResponse, |
|
|
|
|
legacyLogStreamToDataFrame, |
|
|
|
|
lokiStreamResultToDataFrame, |
|
|
|
|
lokiLegacyStreamsToDataframes, |
|
|
|
|
} from './result_transformer'; |
|
|
|
|
import { lokiResultsToTableModel, processRangeQueryResponse, lokiStreamResultToDataFrame } from './result_transformer'; |
|
|
|
|
import { formatQuery, parseQuery, getHighlighterExpressionsFromQuery } from './query_utils'; |
|
|
|
|
|
|
|
|
|
// Types
|
|
|
|
|
@ -26,7 +20,6 @@ import { |
|
|
|
|
LoadingState, |
|
|
|
|
AnnotationEvent, |
|
|
|
|
DataFrameView, |
|
|
|
|
TimeRange, |
|
|
|
|
TimeSeries, |
|
|
|
|
PluginMeta, |
|
|
|
|
DataSourceApi, |
|
|
|
|
@ -42,30 +35,25 @@ import { |
|
|
|
|
import { |
|
|
|
|
LokiQuery, |
|
|
|
|
LokiOptions, |
|
|
|
|
LokiLegacyQueryRequest, |
|
|
|
|
LokiLegacyStreamResponse, |
|
|
|
|
LokiResponse, |
|
|
|
|
LokiResultType, |
|
|
|
|
LokiRangeQueryRequest, |
|
|
|
|
LokiStreamResponse, |
|
|
|
|
} from './types'; |
|
|
|
|
import { LegacyTarget, LiveStreams } from './live_streams'; |
|
|
|
|
import { LiveStreams, LokiLiveTarget } from './live_streams'; |
|
|
|
|
import LanguageProvider from './language_provider'; |
|
|
|
|
import { serializeParams } from '../../../core/utils/fetch'; |
|
|
|
|
|
|
|
|
|
export type RangeQueryOptions = Pick<DataQueryRequest<LokiQuery>, 'range' | 'intervalMs' | 'maxDataPoints' | 'reverse'>; |
|
|
|
|
export const DEFAULT_MAX_LINES = 1000; |
|
|
|
|
export const LEGACY_LOKI_ENDPOINT = '/api/prom'; |
|
|
|
|
export const LOKI_ENDPOINT = '/loki/api/v1'; |
|
|
|
|
|
|
|
|
|
const LEGACY_QUERY_ENDPOINT = `${LEGACY_LOKI_ENDPOINT}/query`; |
|
|
|
|
const RANGE_QUERY_ENDPOINT = `${LOKI_ENDPOINT}/query_range`; |
|
|
|
|
const INSTANT_QUERY_ENDPOINT = `${LOKI_ENDPOINT}/query`; |
|
|
|
|
|
|
|
|
|
const DEFAULT_QUERY_PARAMS: Partial<LokiLegacyQueryRequest> = { |
|
|
|
|
const DEFAULT_QUERY_PARAMS: Partial<LokiRangeQueryRequest> = { |
|
|
|
|
direction: 'BACKWARD', |
|
|
|
|
limit: DEFAULT_MAX_LINES, |
|
|
|
|
regexp: '', |
|
|
|
|
query: '', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
@ -76,7 +64,6 @@ interface LokiContextQueryOptions { |
|
|
|
|
|
|
|
|
|
export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
private streams = new LiveStreams(); |
|
|
|
|
private version: string; |
|
|
|
|
languageProvider: LanguageProvider; |
|
|
|
|
maxLines: number; |
|
|
|
|
|
|
|
|
|
@ -89,23 +76,6 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getVersion() { |
|
|
|
|
if (this.version) { |
|
|
|
|
return Promise.resolve(this.version); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return this._request(RANGE_QUERY_ENDPOINT) |
|
|
|
|
.toPromise() |
|
|
|
|
.then(() => { |
|
|
|
|
this.version = 'v1'; |
|
|
|
|
return this.version; |
|
|
|
|
}) |
|
|
|
|
.catch((err: any) => { |
|
|
|
|
this.version = err.status !== 404 ? 'v1' : 'v0'; |
|
|
|
|
return this.version; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_request(apiUrl: string, data?: any, options?: DatasourceRequestOptions): Observable<Record<string, any>> { |
|
|
|
|
const baseUrl = this.instanceSettings.url; |
|
|
|
|
const params = data ? serializeParams(data) : ''; |
|
|
|
|
@ -131,13 +101,13 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
filteredTargets.forEach(target => |
|
|
|
|
subQueries.push( |
|
|
|
|
this.runInstantQuery(target, options, filteredTargets.length), |
|
|
|
|
this.runRangeQueryWithFallback(target, options, filteredTargets.length) |
|
|
|
|
this.runRangeQuery(target, options, filteredTargets.length) |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
filteredTargets.forEach(target => |
|
|
|
|
subQueries.push( |
|
|
|
|
this.runRangeQueryWithFallback(target, options, filteredTargets.length).pipe( |
|
|
|
|
this.runRangeQuery(target, options, filteredTargets.length).pipe( |
|
|
|
|
map(dataQueryResponse => { |
|
|
|
|
if (options.exploreMode === ExploreMode.Logs && dataQueryResponse.data.find(d => isTimeSeries(d))) { |
|
|
|
|
throw new Error( |
|
|
|
|
@ -163,41 +133,6 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
return merge(...subQueries); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
runLegacyQuery = ( |
|
|
|
|
target: LokiQuery, |
|
|
|
|
options: { range?: TimeRange; maxDataPoints?: number; reverse?: boolean } |
|
|
|
|
): Observable<DataQueryResponse> => { |
|
|
|
|
if (target.liveStreaming) { |
|
|
|
|
return this.runLiveQuery(target, options); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const range = options.range |
|
|
|
|
? { start: this.getTime(options.range.from, false), end: this.getTime(options.range.to, true) } |
|
|
|
|
: {}; |
|
|
|
|
const query: LokiLegacyQueryRequest = { |
|
|
|
|
...DEFAULT_QUERY_PARAMS, |
|
|
|
|
...parseQuery(target.expr), |
|
|
|
|
...range, |
|
|
|
|
limit: Math.min(options.maxDataPoints || Infinity, this.maxLines), |
|
|
|
|
refId: target.refId, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return this._request(LEGACY_QUERY_ENDPOINT, query).pipe( |
|
|
|
|
catchError((err: any) => this.throwUnless(err, err.cancelled, target)), |
|
|
|
|
filter((response: any) => !response.cancelled), |
|
|
|
|
map((response: { data: LokiLegacyStreamResponse }) => ({ |
|
|
|
|
data: lokiLegacyStreamsToDataframes( |
|
|
|
|
response.data, |
|
|
|
|
query, |
|
|
|
|
this.maxLines, |
|
|
|
|
this.instanceSettings.jsonData, |
|
|
|
|
options.reverse |
|
|
|
|
), |
|
|
|
|
key: `${target.refId}_log`, |
|
|
|
|
})) |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
runInstantQuery = ( |
|
|
|
|
target: LokiQuery, |
|
|
|
|
options: DataQueryRequest<LokiQuery>, |
|
|
|
|
@ -255,9 +190,9 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Attempts to send a query to /loki/api/v1/query_range but falls back to the legacy endpoint if necessary. |
|
|
|
|
* Attempts to send a query to /loki/api/v1/query_range |
|
|
|
|
*/ |
|
|
|
|
runRangeQueryWithFallback = ( |
|
|
|
|
runRangeQuery = ( |
|
|
|
|
target: LokiQuery, |
|
|
|
|
options: RangeQueryOptions, |
|
|
|
|
responseListLength = 1 |
|
|
|
|
@ -291,10 +226,6 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
catchError((err: any) => this.throwUnless(err, err.cancelled || err.status === 404, target)), |
|
|
|
|
filter((response: any) => (response.cancelled ? false : true)), |
|
|
|
|
switchMap((response: { data: LokiResponse; status: number }) => |
|
|
|
|
iif<DataQueryResponse, DataQueryResponse>( |
|
|
|
|
() => response.status === 404, |
|
|
|
|
defer(() => this.runLegacyQuery(target, queryOptions)), |
|
|
|
|
defer(() => |
|
|
|
|
processRangeQueryResponse( |
|
|
|
|
response.data, |
|
|
|
|
target, |
|
|
|
|
@ -305,33 +236,16 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
options.reverse |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
createLegacyLiveTarget(target: LokiQuery, options: { maxDataPoints?: number }): LegacyTarget { |
|
|
|
|
const { query, regexp } = parseQuery(target.expr); |
|
|
|
|
const baseUrl = this.instanceSettings.url; |
|
|
|
|
const params = serializeParams({ query }); |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
query, |
|
|
|
|
regexp, |
|
|
|
|
url: convertToWebSocketUrl(`${baseUrl}/api/prom/tail?${params}`), |
|
|
|
|
refId: target.refId, |
|
|
|
|
size: Math.min(options.maxDataPoints || Infinity, this.maxLines), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
createLiveTarget(target: LokiQuery, options: { maxDataPoints?: number }): LegacyTarget { |
|
|
|
|
const { query, regexp } = parseQuery(target.expr); |
|
|
|
|
createLiveTarget(target: LokiQuery, options: { maxDataPoints?: number }): LokiLiveTarget { |
|
|
|
|
const { query } = parseQuery(target.expr); |
|
|
|
|
const baseUrl = this.instanceSettings.url; |
|
|
|
|
const params = serializeParams({ query }); |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
query, |
|
|
|
|
regexp, |
|
|
|
|
url: convertToWebSocketUrl(`${baseUrl}/loki/api/v1/tail?${params}`), |
|
|
|
|
refId: target.refId, |
|
|
|
|
size: Math.min(options.maxDataPoints || Infinity, this.maxLines), |
|
|
|
|
@ -347,17 +261,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
runLiveQuery = (target: LokiQuery, options: { maxDataPoints?: number }): Observable<DataQueryResponse> => { |
|
|
|
|
const liveTarget = this.createLiveTarget(target, options); |
|
|
|
|
|
|
|
|
|
return from(this.getVersion()).pipe( |
|
|
|
|
mergeMap(version => |
|
|
|
|
iif( |
|
|
|
|
() => version === 'v1', |
|
|
|
|
defer(() => this.streams.getStream(liveTarget)), |
|
|
|
|
defer(() => { |
|
|
|
|
const legacyTarget = this.createLegacyLiveTarget(target, options); |
|
|
|
|
return this.streams.getLegacyStream(legacyTarget); |
|
|
|
|
}) |
|
|
|
|
) |
|
|
|
|
), |
|
|
|
|
return this.streams.getStream(liveTarget).pipe( |
|
|
|
|
map(data => ({ |
|
|
|
|
data, |
|
|
|
|
key: `loki-${liveTarget.refId}`, |
|
|
|
|
@ -418,16 +322,13 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async labelNamesQuery() { |
|
|
|
|
const url = (await this.getVersion()) === 'v0' ? `${LEGACY_LOKI_ENDPOINT}/label` : `${LOKI_ENDPOINT}/label`; |
|
|
|
|
const url = `${LOKI_ENDPOINT}/label`; |
|
|
|
|
const result = await this.metadataRequest(url); |
|
|
|
|
return result.map((value: string) => ({ text: value })); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async labelValuesQuery(label: string) { |
|
|
|
|
const url = |
|
|
|
|
(await this.getVersion()) === 'v0' |
|
|
|
|
? `${LEGACY_LOKI_ENDPOINT}/label/${label}/values` |
|
|
|
|
: `${LOKI_ENDPOINT}/label/${label}/values`; |
|
|
|
|
const url = `${LOKI_ENDPOINT}/label/${label}/values`; |
|
|
|
|
const result = await this.metadataRequest(url); |
|
|
|
|
return result.map((value: string) => ({ text: value })); |
|
|
|
|
} |
|
|
|
|
@ -506,31 +407,11 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
throw error; |
|
|
|
|
}), |
|
|
|
|
switchMap((res: { data: LokiStreamResponse; status: number }) => |
|
|
|
|
iif( |
|
|
|
|
() => res.status === 404, |
|
|
|
|
defer(() => |
|
|
|
|
this._request(LEGACY_QUERY_ENDPOINT, target).pipe( |
|
|
|
|
catchError((err: any) => { |
|
|
|
|
const error: DataQueryError = { |
|
|
|
|
message: 'Error during context query. Please check JS console logs.', |
|
|
|
|
status: err.status, |
|
|
|
|
statusText: err.statusText, |
|
|
|
|
}; |
|
|
|
|
throw error; |
|
|
|
|
}), |
|
|
|
|
map((res: { data: LokiLegacyStreamResponse }) => ({ |
|
|
|
|
data: res.data ? res.data.streams.map(stream => legacyLogStreamToDataFrame(stream, reverse)) : [], |
|
|
|
|
})) |
|
|
|
|
) |
|
|
|
|
), |
|
|
|
|
defer(() => |
|
|
|
|
of({ |
|
|
|
|
data: res.data ? res.data.data.result.map(stream => lokiStreamResultToDataFrame(stream, reverse)) : [], |
|
|
|
|
}) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
.toPromise(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
@ -576,22 +457,8 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
// Consider only last 10 minutes otherwise request takes too long
|
|
|
|
|
const startMs = Date.now() - 10 * 60 * 1000; |
|
|
|
|
const start = `${startMs}000000`; // API expects nanoseconds
|
|
|
|
|
return this._request('/loki/api/v1/label', { start }) |
|
|
|
|
return this._request(`${LOKI_ENDPOINT}/label`, { start }) |
|
|
|
|
.pipe( |
|
|
|
|
catchError((err: any) => { |
|
|
|
|
if (err.status === 404) { |
|
|
|
|
return of(err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
throw err; |
|
|
|
|
}), |
|
|
|
|
switchMap((response: { data: { values: string[] }; status: number }) => |
|
|
|
|
iif<DataQueryResponse, any>( |
|
|
|
|
() => response.status === 404, |
|
|
|
|
defer(() => this._request('/api/prom/label', { start })), |
|
|
|
|
defer(() => of(response)) |
|
|
|
|
) |
|
|
|
|
), |
|
|
|
|
map(res => { |
|
|
|
|
const values: any[] = res?.data?.data || res?.data?.values || []; |
|
|
|
|
const testResult = |
|
|
|
|
@ -634,7 +501,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { |
|
|
|
|
|
|
|
|
|
const interpolatedExpr = this.templateSrv.replace(options.annotation.expr, {}, this.interpolateQueryExpr); |
|
|
|
|
const query = { refId: `annotation-${options.annotation.name}`, expr: interpolatedExpr }; |
|
|
|
|
const { data } = await this.runRangeQueryWithFallback(query, options).toPromise(); |
|
|
|
|
const { data } = await this.runRangeQuery(query, options).toPromise(); |
|
|
|
|
const annotations: AnnotationEvent[] = []; |
|
|
|
|
|
|
|
|
|
for (const frame of data) { |
|
|
|
|
|