2019-01-18 10:43:58 -06:00
// Libraries
2019-12-02 06:05:40 -06:00
import { isEmpty , map as lodashMap } from 'lodash' ;
2020-04-22 06:59:06 -05:00
import { Observable , from , merge , of } from 'rxjs' ;
import { map , filter , catchError , switchMap } from 'rxjs/operators' ;
2019-11-15 09:38:25 -06:00
2019-01-18 10:43:58 -06:00
// Services & Utils
2020-06-30 02:25:05 -05:00
import { DataFrame , dateMath , FieldCache , QueryResultMeta } from '@grafana/data' ;
2020-07-07 14:22:47 -05:00
import { getBackendSrv , BackendSrvRequest } from '@grafana/runtime' ;
2020-05-04 11:16:54 -05:00
import { addLabelToQuery } from 'app/plugins/datasource/prometheus/add_label_to_query' ;
2019-11-15 09:38:25 -06:00
import { TemplateSrv } from 'app/features/templating/template_srv' ;
import { safeStringifyValue , convertToWebSocketUrl } from 'app/core/utils/explore' ;
2020-04-22 06:59:06 -05:00
import { lokiResultsToTableModel , processRangeQueryResponse , lokiStreamResultToDataFrame } from './result_transformer' ;
2020-05-06 04:21:25 -05:00
import { getHighlighterExpressionsFromQuery } from './query_utils' ;
2019-11-15 09:38:25 -06:00
// Types
2019-09-10 04:04:44 -05:00
import {
LogRowModel ,
DateTime ,
2019-11-15 09:38:25 -06:00
LoadingState ,
2019-09-10 04:04:44 -05:00
AnnotationEvent ,
DataFrameView ,
2019-05-10 04:37:43 -05:00
PluginMeta ,
DataSourceApi ,
DataSourceInstanceSettings ,
2019-05-10 07:00:39 -05:00
DataQueryError ,
2019-07-06 01:05:53 -05:00
DataQueryRequest ,
2019-06-18 04:01:12 -05:00
DataQueryResponse ,
2019-09-10 04:04:44 -05:00
AnnotationQueryRequest ,
2020-01-24 02:50:09 -06:00
ScopedVars ,
2019-10-31 04:48:05 -05:00
} from '@grafana/data' ;
2018-07-20 10:07:17 -05:00
2019-11-15 09:38:25 -06:00
import {
LokiQuery ,
LokiOptions ,
LokiResponse ,
LokiResultType ,
LokiRangeQueryRequest ,
LokiStreamResponse ,
} from './types' ;
2020-04-22 06:59:06 -05:00
import { LiveStreams , LokiLiveTarget } from './live_streams' ;
2019-11-15 09:38:25 -06:00
import LanguageProvider from './language_provider' ;
2020-03-25 06:25:39 -05:00
import { serializeParams } from '../../../core/utils/fetch' ;
2020-04-25 15:48:20 -05:00
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider' ;
2019-11-15 09:38:25 -06:00
2019-11-26 07:43:24 -06:00
export type RangeQueryOptions = Pick < DataQueryRequest < LokiQuery > , 'range' | 'intervalMs' | 'maxDataPoints' | 'reverse' > ;
2018-12-31 08:10:45 -06:00
export const DEFAULT_MAX_LINES = 1000 ;
2019-12-03 02:40:22 -06:00
export const LOKI_ENDPOINT = '/loki/api/v1' ;
const RANGE_QUERY_ENDPOINT = ` ${ LOKI_ENDPOINT } /query_range ` ;
const INSTANT_QUERY_ENDPOINT = ` ${ LOKI_ENDPOINT } /query ` ;
2018-07-20 10:07:17 -05:00
2020-04-22 06:59:06 -05:00
const DEFAULT_QUERY_PARAMS : Partial < LokiRangeQueryRequest > = {
2018-07-20 10:07:17 -05:00
direction : 'BACKWARD' ,
2018-12-31 08:10:45 -06:00
limit : DEFAULT_MAX_LINES ,
2018-07-20 10:07:17 -05:00
query : '' ,
} ;
2019-05-10 04:37:43 -05:00
export class LokiDatasource extends DataSourceApi < LokiQuery , LokiOptions > {
2019-09-05 07:04:01 -05:00
private streams = new LiveStreams ( ) ;
2018-10-30 10:14:01 -05:00
languageProvider : LanguageProvider ;
2018-12-31 08:10:45 -06:00
maxLines : number ;
2018-10-30 10:14:01 -05:00
2018-07-20 10:07:17 -05:00
/** @ngInject */
2020-01-21 03:08:07 -06:00
constructor ( private instanceSettings : DataSourceInstanceSettings < LokiOptions > , private templateSrv : TemplateSrv ) {
2019-05-10 04:37:43 -05:00
super ( instanceSettings ) ;
2019-11-15 09:38:25 -06:00
2018-10-30 10:14:01 -05:00
this . languageProvider = new LanguageProvider ( this ) ;
2018-12-31 05:25:28 -06:00
const settingsData = instanceSettings . jsonData || { } ;
2020-05-07 06:53:05 -05:00
this . maxLines = parseInt ( settingsData . maxLines ? ? '0' , 10 ) || DEFAULT_MAX_LINES ;
2018-10-30 10:14:01 -05:00
}
2018-07-20 10:07:17 -05:00
2020-07-07 14:22:47 -05:00
_request ( apiUrl : string , data? : any , options? : Partial < BackendSrvRequest > ) : Observable < Record < string , any > > {
2018-07-20 10:07:17 -05:00
const baseUrl = this . instanceSettings . url ;
const params = data ? serializeParams ( data ) : '' ;
2019-11-15 09:38:25 -06:00
const url = ` ${ baseUrl } ${ apiUrl } ${ params . length ? ` ? ${ params } ` : '' } ` ;
2018-07-20 10:07:17 -05:00
const req = {
. . . options ,
url ,
} ;
2019-07-08 10:14:48 -05:00
2020-01-21 03:08:07 -06:00
return from ( getBackendSrv ( ) . datasourceRequest ( req ) ) ;
2018-07-20 10:07:17 -05:00
}
2019-11-15 09:38:25 -06:00
query ( options : DataQueryRequest < LokiQuery > ) : Observable < DataQueryResponse > {
const subQueries : Array < Observable < DataQueryResponse > > = [ ] ;
const filteredTargets = options . targets
. filter ( target = > target . expr && ! target . hide )
. map ( target = > ( {
. . . target ,
2020-01-24 02:50:09 -06:00
expr : this.templateSrv.replace ( target . expr , options . scopedVars , this . interpolateQueryExpr ) ,
2019-11-15 09:38:25 -06:00
} ) ) ;
2020-07-09 09:14:55 -05:00
filteredTargets . forEach ( target = > subQueries . push ( this . runRangeQuery ( target , options , filteredTargets . length ) ) ) ;
2019-09-05 07:04:01 -05:00
2019-11-15 09:38:25 -06:00
// No valid targets, return the empty result to save a round trip.
if ( isEmpty ( subQueries ) ) {
return of ( {
data : [ ] ,
state : LoadingState.Done ,
} ) ;
}
return merge ( . . . subQueries ) ;
2019-06-03 07:54:32 -05:00
}
2019-05-20 06:28:23 -05:00
2019-11-15 09:38:25 -06:00
runInstantQuery = (
target : LokiQuery ,
options : DataQueryRequest < LokiQuery > ,
responseListLength : number
) : Observable < DataQueryResponse > = > {
const timeNs = this . getTime ( options . range . to , true ) ;
const query = {
2020-05-06 04:21:25 -05:00
query : target.expr ,
2019-11-15 09:38:25 -06:00
time : ` ${ timeNs + ( 1 e9 - ( timeNs % 1 e9 ) ) } ` ,
limit : Math.min ( options . maxDataPoints || Infinity , this . maxLines ) ,
} ;
2020-06-30 02:25:05 -05:00
/** Show results of Loki instant queries only in table */
const meta : QueryResultMeta = {
preferredVisualisationType : 'table' ,
} ;
2019-11-15 09:38:25 -06:00
return this . _request ( INSTANT_QUERY_ENDPOINT , query ) . pipe (
catchError ( ( err : any ) = > this . throwUnless ( err , err . cancelled , target ) ) ,
filter ( ( response : any ) = > ( response . cancelled ? false : true ) ) ,
map ( ( response : { data : LokiResponse } ) = > {
if ( response . data . data . resultType === LokiResultType . Stream ) {
2020-07-09 09:14:55 -05:00
return {
data : [ ] ,
key : ` ${ target . refId } _instant ` ,
} ;
2019-11-15 09:38:25 -06:00
}
2019-09-12 10:28:46 -05:00
return {
2020-06-30 02:25:05 -05:00
data : [ lokiResultsToTableModel ( response . data . data . result , responseListLength , target . refId , meta , true ) ] ,
2019-11-15 09:38:25 -06:00
key : ` ${ target . refId } _instant ` ,
2019-09-12 10:28:46 -05:00
} ;
} )
) ;
2019-06-03 07:54:32 -05:00
} ;
2019-11-15 09:38:25 -06:00
createRangeQuery ( target : LokiQuery , options : RangeQueryOptions ) : LokiRangeQueryRequest {
2020-05-06 04:21:25 -05:00
const query = target . expr ;
2019-11-15 09:38:25 -06:00
let range : { start? : number ; end? : number ; step? : number } = { } ;
2019-12-04 06:43:22 -06:00
if ( options . range ) {
2019-11-15 09:38:25 -06:00
const startNs = this . getTime ( options . range . from , false ) ;
const endNs = this . getTime ( options . range . to , true ) ;
const rangeMs = Math . ceil ( ( endNs - startNs ) / 1 e6 ) ;
2019-12-04 06:43:22 -06:00
const step = Math . ceil ( this . adjustInterval ( options . intervalMs || 1000 , rangeMs ) / 1000 ) ;
2019-11-15 09:38:25 -06:00
const alignedTimes = {
start : startNs - ( startNs % 1 e9 ) ,
end : endNs + ( 1 e9 - ( endNs % 1 e9 ) ) ,
} ;
2019-05-10 07:00:39 -05:00
2019-11-15 09:38:25 -06:00
range = {
start : alignedTimes.start ,
end : alignedTimes.end ,
step ,
} ;
}
return {
. . . DEFAULT_QUERY_PARAMS ,
. . . range ,
query ,
limit : Math.min ( options . maxDataPoints || Infinity , this . maxLines ) ,
} ;
}
/ * *
2020-04-22 06:59:06 -05:00
* Attempts to send a query to / loki / api / v1 / query_range
2019-11-15 09:38:25 -06:00
* /
2020-04-22 06:59:06 -05:00
runRangeQuery = (
2019-11-15 09:38:25 -06:00
target : LokiQuery ,
options : RangeQueryOptions ,
responseListLength = 1
) : Observable < DataQueryResponse > = > {
2020-02-06 06:34:52 -06:00
// target.maxLines value already preprocessed
// available cases:
// 1) empty input -> mapped to NaN, falls back to dataSource.maxLines limit
// 2) input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative
// - mapped to 0, falls back to the limit of 0 lines
// 3) default case - correct input, mapped to the value from the input field
let linesLimit = 0 ;
if ( target . maxLines === undefined ) {
// no target.maxLines, using options.maxDataPoints
linesLimit = Math . min ( options . maxDataPoints || Infinity , this . maxLines ) ;
} else {
// using target.maxLines
if ( isNaN ( target . maxLines ) ) {
linesLimit = this . maxLines ;
} else {
linesLimit = target . maxLines ;
}
2019-11-15 09:38:25 -06:00
}
2020-02-06 06:34:52 -06:00
const queryOptions = { . . . options , maxDataPoints : linesLimit } ;
if ( target . liveStreaming ) {
return this . runLiveQuery ( target , queryOptions ) ;
}
const query = this . createRangeQuery ( target , queryOptions ) ;
2019-11-15 09:38:25 -06:00
return this . _request ( RANGE_QUERY_ENDPOINT , query ) . pipe (
catchError ( ( err : any ) = > this . throwUnless ( err , err . cancelled || err . status === 404 , target ) ) ,
2019-09-12 10:28:46 -05:00
filter ( ( response : any ) = > ( response . cancelled ? false : true ) ) ,
2019-11-15 09:38:25 -06:00
switchMap ( ( response : { data : LokiResponse ; status : number } ) = >
2020-04-22 06:59:06 -05:00
processRangeQueryResponse (
response . data ,
target ,
query ,
responseListLength ,
linesLimit ,
this . instanceSettings . jsonData ,
options . reverse
2019-11-15 09:38:25 -06:00
)
)
2019-05-10 07:00:39 -05:00
) ;
2019-06-03 07:54:32 -05:00
} ;
2020-04-22 06:59:06 -05:00
createLiveTarget ( target : LokiQuery , options : { maxDataPoints? : number } ) : LokiLiveTarget {
2020-05-06 04:21:25 -05:00
const query = target . expr ;
2019-11-15 09:38:25 -06:00
const baseUrl = this . instanceSettings . url ;
const params = serializeParams ( { query } ) ;
return {
query ,
url : convertToWebSocketUrl ( ` ${ baseUrl } /loki/api/v1/tail? ${ params } ` ) ,
refId : target.refId ,
size : Math.min ( options . maxDataPoints || Infinity , this . maxLines ) ,
} ;
2018-07-20 10:07:17 -05:00
}
2019-11-15 09:38:25 -06:00
/ * *
* Runs live queries which in this case means creating a websocket and listening on it for new logs .
* This returns a bit different dataFrame than runQueries as it returns single dataframe even if there are multiple
* Loki streams , sets only common labels on dataframe . labels and has additional dataframe . fields . labels for unique
* labels per row .
* /
runLiveQuery = ( target : LokiQuery , options : { maxDataPoints? : number } ) : Observable < DataQueryResponse > = > {
const liveTarget = this . createLiveTarget ( target , options ) ;
2020-04-22 06:59:06 -05:00
return this . streams . getStream ( liveTarget ) . pipe (
2019-11-15 09:38:25 -06:00
map ( data = > ( {
data ,
key : ` loki- ${ liveTarget . refId } ` ,
state : LoadingState.Streaming ,
} ) )
) ;
} ;
2020-01-24 02:50:09 -06:00
interpolateVariablesInQueries ( queries : LokiQuery [ ] , scopedVars : ScopedVars ) : LokiQuery [ ] {
2019-10-08 10:01:20 -05:00
let expandedQueries = queries ;
2019-11-15 09:38:25 -06:00
if ( queries && queries . length ) {
expandedQueries = queries . map ( query = > ( {
. . . query ,
datasource : this.name ,
2020-01-24 02:50:09 -06:00
expr : this.templateSrv.replace ( query . expr , scopedVars , this . interpolateQueryExpr ) ,
2019-11-15 09:38:25 -06:00
} ) ) ;
2019-10-08 10:01:20 -05:00
}
2019-11-15 09:38:25 -06:00
2019-10-08 10:01:20 -05:00
return expandedQueries ;
}
Explore: Rich History (#22570)
* Explore: Refactor active buttons css
* Explore: Add query history button
* WIP: Creating drawer
* WIP: Create custom drawer (for now)
* Revert changes to Drawer.tsx
* WIP: Layout finished
* Rich History: Set up boilerplate for Settings
* WIP: Query history cards
* Refactor, split components
* Add resizability, interactivity
* Save history to local storage
* Visualise queries from queryhistory local storage
* Set up query history settings
* Refactor
* Create link, un-refactored verison
* Copyable url
* WIP: Add slider
* Commenting feature
* Slider filtration
* Add headings
* Hide Rich history behind feature toggle
* Cleaning up, refactors
* Update tests
* Implement getQueryDisplayText
* Update lockfile for new dependencies
* Update heading based on sorting
* Fix typescript strinctNullCheck errors
* Fix Forms, new forms
* Fixes based on provided feedback
* Fixes, splitting component into two
* Add tooltips, add delete history button
* Clicking on card adds queries to query rows
* Delete history, width of drawers
* UI/UX changes, updates
* Add number of queries to headings, box shadows
* Fix slider, remove feature toggle
* Fix typo in the beta announcement
* Fix how the rich history state is rendered when initialization
* Move updateFilters when activeDatasourceFilter onlyto RichHistory, remove duplicated code
* Fix typescript strictnull errors, not used variables errors
2020-03-10 09:08:15 -05:00
getQueryDisplayText ( query : LokiQuery ) {
return query . expr ;
}
2019-01-18 11:14:27 -06:00
async importQueries ( queries : LokiQuery [ ] , originMeta : PluginMeta ) : Promise < LokiQuery [ ] > {
2018-11-13 09:35:20 -06:00
return this . languageProvider . importQueries ( queries , originMeta . id ) ;
}
2019-11-15 09:38:25 -06:00
async metadataRequest ( url : string , params? : Record < string , string > ) {
const res = await this . _request ( url , params , { silent : true } ) . toPromise ( ) ;
2020-03-04 10:17:02 -06:00
return res . data . data || res . data . values || [ ] ;
2018-07-20 10:07:17 -05:00
}
2019-12-03 02:40:22 -06:00
async metricFindQuery ( query : string ) {
if ( ! query ) {
return Promise . resolve ( [ ] ) ;
}
const interpolated = this . templateSrv . replace ( query , { } , this . interpolateQueryExpr ) ;
return await this . processMetricFindQuery ( interpolated ) ;
}
async processMetricFindQuery ( query : string ) {
const labelNamesRegex = /^label_names\(\)\s*$/ ;
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/ ;
const labelNames = query . match ( labelNamesRegex ) ;
if ( labelNames ) {
return await this . labelNamesQuery ( ) ;
}
const labelValues = query . match ( labelValuesRegex ) ;
if ( labelValues ) {
return await this . labelValuesQuery ( labelValues [ 2 ] ) ;
}
return Promise . resolve ( [ ] ) ;
}
async labelNamesQuery() {
2020-04-22 06:59:06 -05:00
const url = ` ${ LOKI_ENDPOINT } /label ` ;
2019-12-03 02:40:22 -06:00
const result = await this . metadataRequest ( url ) ;
2020-03-04 10:17:02 -06:00
return result . map ( ( value : string ) = > ( { text : value } ) ) ;
2019-12-03 02:40:22 -06:00
}
async labelValuesQuery ( label : string ) {
2020-04-22 06:59:06 -05:00
const url = ` ${ LOKI_ENDPOINT } /label/ ${ label } /values ` ;
2019-12-03 02:40:22 -06:00
const result = await this . metadataRequest ( url ) ;
2020-03-04 10:17:02 -06:00
return result . map ( ( value : string ) = > ( { text : value } ) ) ;
2019-12-03 02:40:22 -06:00
}
2019-11-06 10:29:44 -06:00
interpolateQueryExpr ( value : any , variable : any ) {
// if no multi or include all do not regexEscape
if ( ! variable . multi && ! variable . includeAll ) {
return lokiRegularEscape ( value ) ;
}
if ( typeof value === 'string' ) {
return lokiSpecialRegexEscape ( value ) ;
}
const escapedValues = lodashMap ( value , lokiSpecialRegexEscape ) ;
return escapedValues . join ( '|' ) ;
}
2019-01-18 11:14:27 -06:00
modifyQuery ( query : LokiQuery , action : any ) : LokiQuery {
2020-05-04 11:16:54 -05:00
let expression = query . expr ? ? '' ;
2018-11-28 03:46:35 -06:00
switch ( action . type ) {
case 'ADD_FILTER' : {
2020-05-25 05:23:35 -05:00
expression = addLabelToQuery ( expression , action . key , action . value , undefined , true ) ;
2018-11-28 03:46:35 -06:00
break ;
}
2019-11-01 04:01:00 -05:00
case 'ADD_FILTER_OUT' : {
2020-05-25 05:23:35 -05:00
expression = addLabelToQuery ( expression , action . key , action . value , '!=' , true ) ;
2019-11-01 04:01:00 -05:00
break ;
}
2018-11-28 03:46:35 -06:00
default :
break ;
}
return { . . . query , expr : expression } ;
}
2019-05-13 02:58:26 -05:00
getHighlighterExpression ( query : LokiQuery ) : string [ ] {
return getHighlighterExpressionsFromQuery ( query . expr ) ;
2018-12-01 08:26:51 -06:00
}
2019-06-18 04:01:12 -05:00
getTime ( date : string | DateTime , roundUp : boolean ) {
2019-11-15 09:38:25 -06:00
if ( typeof date === 'string' ) {
2020-07-06 14:16:27 -05:00
date = dateMath . parse ( date , roundUp ) ! ;
2018-07-20 10:07:17 -05:00
}
2019-11-15 09:38:25 -06:00
2018-07-20 10:07:17 -05:00
return Math . ceil ( date . valueOf ( ) * 1 e6 ) ;
}
2020-04-25 15:48:20 -05:00
getLogRowContext = ( row : LogRowModel , options? : RowContextOptions ) : Promise < { data : DataFrame [ ] } > = > {
2019-11-15 09:38:25 -06:00
const target = this . prepareLogRowContextQueryTarget (
row ,
( options && options . limit ) || 10 ,
( options && options . direction ) || 'BACKWARD'
) ;
const reverse = options && options . direction === 'FORWARD' ;
return this . _request ( RANGE_QUERY_ENDPOINT , target )
. pipe (
catchError ( ( err : any ) = > {
if ( err . status === 404 ) {
return of ( err ) ;
}
const error : DataQueryError = {
message : 'Error during context query. Please check JS console logs.' ,
status : err.status ,
statusText : err.statusText ,
} ;
throw error ;
} ) ,
switchMap ( ( res : { data : LokiStreamResponse ; status : number } ) = >
2020-04-22 06:59:06 -05:00
of ( {
data : res.data ? res . data . data . result . map ( stream = > lokiStreamResultToDataFrame ( stream , reverse ) ) : [ ] ,
} )
2019-11-15 09:38:25 -06:00
)
)
. toPromise ( ) ;
} ;
2019-05-22 16:10:05 -05:00
prepareLogRowContextQueryTarget = ( row : LogRowModel , limit : number , direction : 'BACKWARD' | 'FORWARD' ) = > {
2019-05-20 01:44:37 -05:00
const query = Object . keys ( row . labels )
2020-07-07 10:42:49 -05:00
. map ( label = > ` ${ label } =" ${ row . labels [ label ] . replace ( /\\/g , '\\\\' ) } " ` ) // escape backslashes in label as users can't escape them by themselves
2019-05-20 01:44:37 -05:00
. join ( ',' ) ;
2019-11-15 09:38:25 -06:00
2020-01-26 16:13:56 -06:00
const contextTimeBuffer = 2 * 60 * 60 * 1000 ; // 2h buffer
2019-11-15 09:38:25 -06:00
const commonTargetOptions = {
2019-05-20 01:44:37 -05:00
limit ,
query : ` { ${ query } } ` ,
2019-11-15 09:38:25 -06:00
expr : ` { ${ query } } ` ,
2019-05-22 16:10:05 -05:00
direction ,
2019-05-20 01:44:37 -05:00
} ;
2019-05-22 16:10:05 -05:00
2020-01-26 16:13:56 -06:00
const fieldCache = new FieldCache ( row . dataFrame ) ;
const nsField = fieldCache . getFieldByName ( 'tsNs' ) ! ;
const nsTimestamp = nsField . values . get ( row . rowIndex ) ;
2019-05-22 16:10:05 -05:00
if ( direction === 'BACKWARD' ) {
return {
2019-11-15 09:38:25 -06:00
. . . commonTargetOptions ,
2020-01-26 16:13:56 -06:00
// convert to ns, we loose some precision here but it is not that important at the far points of the context
start : row.timeEpochMs - contextTimeBuffer + '000000' ,
end : nsTimestamp ,
2019-05-22 16:10:05 -05:00
direction ,
} ;
} else {
return {
2019-11-15 09:38:25 -06:00
. . . commonTargetOptions ,
2020-01-26 16:13:56 -06:00
// start param in Loki API is inclusive so we'll have to filter out the row that this request is based from
// and any other that were logged in the same ns but before the row. Right now these rows will be lost
// because the are before but came it he response that should return only rows after.
start : nsTimestamp ,
// convert to ns, we loose some precision here but it is not that important at the far points of the context
end : row.timeEpochMs + contextTimeBuffer + '000000' ,
2019-05-22 16:10:05 -05:00
} ;
}
2019-05-20 01:44:37 -05:00
} ;
2018-07-20 10:07:17 -05:00
testDatasource() {
2019-08-03 09:58:08 -05:00
// Consider only last 10 minutes otherwise request takes too long
const startMs = Date . now ( ) - 10 * 60 * 1000 ;
const start = ` ${ startMs } 000000 ` ; // API expects nanoseconds
2020-04-22 06:59:06 -05:00
return this . _request ( ` ${ LOKI_ENDPOINT } /label ` , { start } )
2019-11-15 09:38:25 -06:00
. pipe (
2019-12-09 09:45:56 -06:00
map ( res = > {
const values : any [ ] = res ? . data ? . data || res ? . data ? . values || [ ] ;
const testResult =
values . length > 0
? { status : 'success' , message : 'Data source connected and labels found.' }
: {
status : 'error' ,
message :
'Data source connected, but no labels received. Verify that Loki and Promtail is configured properly.' ,
} ;
return testResult ;
} ) ,
2019-11-15 09:38:25 -06:00
catchError ( ( err : any ) = > {
let message = 'Loki: ' ;
if ( err . statusText ) {
message += err . statusText ;
} else {
message += 'Cannot connect to Loki' ;
}
2018-12-05 17:19:55 -06:00
2019-11-15 09:38:25 -06:00
if ( err . status ) {
message += ` . ${ err . status } ` ;
}
if ( err . data && err . data . message ) {
message += ` . ${ err . data . message } ` ;
} else if ( err . data ) {
message += ` . ${ err . data } ` ;
}
return of ( { status : 'error' , message : message } ) ;
} )
)
. toPromise ( ) ;
2018-07-20 10:07:17 -05:00
}
2019-09-10 04:04:44 -05:00
async annotationQuery ( options : AnnotationQueryRequest < LokiQuery > ) : Promise < AnnotationEvent [ ] > {
if ( ! options . annotation . expr ) {
return [ ] ;
}
2019-11-27 08:58:21 -06:00
const interpolatedExpr = this . templateSrv . replace ( options . annotation . expr , { } , this . interpolateQueryExpr ) ;
const query = { refId : ` annotation- ${ options . annotation . name } ` , expr : interpolatedExpr } ;
2020-07-09 08:16:35 -05:00
const { data } = await this . runRangeQuery ( query , options as any ) . toPromise ( ) ;
2019-09-10 04:04:44 -05:00
const annotations : AnnotationEvent [ ] = [ ] ;
2019-09-12 10:28:46 -05:00
2019-09-10 04:04:44 -05:00
for ( const frame of data ) {
2019-11-07 09:50:45 -06:00
const tags : string [ ] = [ ] ;
for ( const field of frame . fields ) {
if ( field . labels ) {
2020-05-06 12:05:00 -05:00
tags . push . apply ( tags , [ . . . new Set ( Object . values ( field . labels ) . map ( ( label : string ) = > label . trim ( ) ) ) ] ) ;
2019-11-07 09:50:45 -06:00
}
}
2019-09-10 04:04:44 -05:00
const view = new DataFrameView < { ts : string ; line : string } > ( frame ) ;
2019-09-12 10:28:46 -05:00
2020-04-11 09:08:22 -05:00
view . forEach ( row = > {
2019-09-10 04:04:44 -05:00
annotations . push ( {
time : new Date ( row . ts ) . valueOf ( ) ,
text : row.line ,
tags ,
} ) ;
} ) ;
}
return annotations ;
}
2019-11-06 09:15:08 -06:00
2020-07-06 14:16:27 -05:00
showContextToggle ( row? : LogRowModel ) : boolean {
return ( row && row . searchWords && row . searchWords . length > 0 ) === true ;
}
2020-04-25 15:48:20 -05:00
2019-11-15 09:38:25 -06:00
throwUnless = ( err : any , condition : boolean , target : LokiQuery ) = > {
if ( condition ) {
return of ( err ) ;
}
const error : DataQueryError = this . processError ( err , target ) ;
throw error ;
2019-09-10 04:04:44 -05:00
} ;
2019-11-15 09:38:25 -06:00
processError = ( err : any , target : LokiQuery ) : DataQueryError = > {
const error : DataQueryError = {
message : ( err && err . statusText ) || 'Unknown error during query transaction. Please check JS console logs.' ,
refId : target.refId ,
} ;
if ( err . data ) {
if ( typeof err . data === 'string' ) {
2020-07-09 03:13:41 -05:00
if ( err . data . includes ( 'escape' ) && target . expr . includes ( '\\' ) ) {
error . message = ` Error: ${ err . data } . Make sure that all special characters are escaped with \\ . For more information on escaping of special characters visit LogQL documentation at https://github.com/grafana/loki/blob/master/docs/logql.md. ` ;
} else {
error . message = err . data ;
}
2019-11-15 09:38:25 -06:00
} else if ( err . data . error ) {
error . message = safeStringifyValue ( err . data . error ) ;
}
} else if ( err . message ) {
error . message = err . message ;
} else if ( typeof err === 'string' ) {
error . message = err ;
}
error . status = err . status ;
error . statusText = err . statusText ;
return error ;
} ;
adjustInterval ( interval : number , range : number ) {
// Loki will drop queries that might return more than 11000 data points.
// Calibrate interval if it is too small.
if ( interval !== 0 && range / interval > 11000 ) {
interval = Math . ceil ( range / 11000 ) ;
}
return Math . max ( interval , 1000 ) ;
}
2018-07-20 10:07:17 -05:00
}
2019-01-23 10:44:22 -06:00
2019-11-06 10:29:44 -06:00
export function lokiRegularEscape ( value : any ) {
if ( typeof value === 'string' ) {
return value . replace ( /'/g , "\\\\'" ) ;
}
return value ;
}
export function lokiSpecialRegexEscape ( value : any ) {
if ( typeof value === 'string' ) {
return lokiRegularEscape ( value . replace ( /\\/g , '\\\\\\\\' ) . replace ( /[$^*{}\[\]+?.()|]/g , '\\\\$&' ) ) ;
}
return value ;
}
2019-01-23 10:44:22 -06:00
export default LokiDatasource ;