Commit a9c94ec9 by Dominik Prokop Committed by GitHub

Explore: Update the way Loki retrieve log context (#17204)

* Move log's typings into grafana/ui
* Update the way context is retrieved for Loki

Major changes:

1. getLogRowContext expects row to be of LogRowModel type
2. getLogRowContext accepts generic options object, specific to a datasource of interest. limit option has been removed, and now it's a part of Loki's context query options (see below)
3. LogRowContextProvider performs two queries now. Before, it was Loki ds that performed queries in both directions when getLogRowContext.
4. Loki's getLogRowContext accepts options object of a type:

interface LokiContextQueryOptions {
    direction?: 'BACKWARD' | 'FORWARD';
    limit?: number;
}

This will enable querying in either direction independently and also slightly simplifies the way query errors are handled.

LogRowContextProvider maps the results to a Loki specific context types, basically string[][], as raw log lines are displayed in first version.
parent 87495749
......@@ -155,6 +155,7 @@
"storybook:build": "cd packages/grafana-ui && yarn storybook:build",
"themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts",
"prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"",
"prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write",
"gui:tslint": "tslint -c ./packages/grafana-ui/tslint.json --project ./packages/grafana-ui/tsconfig.json",
"gui:build": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:build",
"gui:releasePrepare": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release",
......
......@@ -3,6 +3,7 @@ import { TimeRange } from './time';
import { PluginMeta, GrafanaPlugin } from './plugin';
import { TableData, TimeSeries, SeriesData, LoadingState } from './data';
import { PanelData } from './panel';
import { LogRowModel } from './logs';
// NOTE: this seems more general than just DataSource
export interface DataSourcePluginOptionsEditorProps<TOptions> {
......@@ -187,7 +188,10 @@ export abstract class DataSourceApi<
/**
* Retrieve context for a given log row
*/
getLogRowContext?(row: any, limit?: number): Promise<DataQueryResponse>;
getLogRowContext?: <TContextQueryOptions extends {}>(
row: LogRowModel,
options?: TContextQueryOptions
) => Promise<DataQueryResponse>;
/**
* Set after constructor call, as the data source instance is the most common thing to pass around
......@@ -299,10 +303,6 @@ export interface DataQueryResponse {
data: DataQueryResponseData[];
}
export interface LogRowContextQueryResponse {
data: Array<Array<string | DataQueryError>>;
}
export interface DataQuery {
/**
* A - Z
......
import { Labels, TimeSeries } from './data';
/**
* Mapping of log level abbreviation to canonical log level.
* Supported levels are reduce to limit color variation.
......@@ -19,3 +21,85 @@ export enum LogLevel {
trace = 'trace',
unknown = 'unknown',
}
export enum LogsMetaKind {
Number,
String,
LabelsMap,
}
export interface LogsMetaItem {
label: string;
value: string | number | Labels;
kind: LogsMetaKind;
}
export interface LogRowModel {
duplicates?: number;
entry: string;
hasAnsi: boolean;
labels: Labels;
logLevel: LogLevel;
raw: string;
searchWords?: string[];
timestamp: string; // ISO with nanosec precision
timeFromNow: string;
timeEpochMs: number;
timeLocal: string;
uniqueLabels?: Labels;
}
export interface LogsModel {
hasUniqueLabels: boolean;
meta?: LogsMetaItem[];
rows: LogRowModel[];
series?: TimeSeries[];
}
export interface LogSearchMatch {
start: number;
length: number;
text: string;
}
export interface LogLabelStatsModel {
active?: boolean;
count: number;
proportion: number;
value: string;
}
export enum LogsDedupStrategy {
none = 'none',
exact = 'exact',
numbers = 'numbers',
signature = 'signature',
}
export interface LogsParser {
/**
* Value-agnostic matcher for a field label.
* Used to filter rows, and first capture group contains the value.
*/
buildMatcher: (label: string) => RegExp;
/**
* Returns all parsable substrings from a line, used for highlighting
*/
getFields: (line: string) => string[];
/**
* Gets the label name from a parsable substring of a line
*/
getLabelFromField: (field: string) => string;
/**
* Gets the label value from a parsable substring of a line
*/
getValueFromField: (field: string) => string;
/**
* Function to verify if this is a valid parser for the given line.
* The parser accepts the line unless it returns undefined.
*/
test: (line: string) => any;
}
......@@ -13,6 +13,13 @@ import {
toLegacyResponseData,
FieldCache,
FieldType,
LogRowModel,
LogsModel,
LogsMetaItem,
LogsMetaKind,
LogsParser,
LogLabelStatsModel,
LogsDedupStrategy,
} from '@grafana/ui';
import { getThemeColor } from 'app/core/utils/colors';
import { hasAnsiCodes } from 'app/core/utils/text';
......@@ -28,95 +35,12 @@ export const LogLevelColor = {
[LogLevel.unknown]: getThemeColor('#8e8e8e', '#dde4ed'),
};
export interface LogSearchMatch {
start: number;
length: number;
text: string;
}
export interface LogRowModel {
duplicates?: number;
entry: string;
hasAnsi: boolean;
labels: Labels;
logLevel: LogLevel;
raw: string;
searchWords?: string[];
timestamp: string; // ISO with nanosec precision
timeFromNow: string;
timeEpochMs: number;
timeLocal: string;
uniqueLabels?: Labels;
}
export interface LogLabelStatsModel {
active?: boolean;
count: number;
proportion: number;
value: string;
}
export enum LogsMetaKind {
Number,
String,
LabelsMap,
}
export interface LogsMetaItem {
label: string;
value: string | number | Labels;
kind: LogsMetaKind;
}
export interface LogsModel {
hasUniqueLabels: boolean;
meta?: LogsMetaItem[];
rows: LogRowModel[];
series?: TimeSeries[];
}
export enum LogsDedupDescription {
none = 'No de-duplication',
exact = 'De-duplication of successive lines that are identical, ignoring ISO datetimes.',
numbers = 'De-duplication of successive lines that are identical when ignoring numbers, e.g., IP addresses, latencies.',
signature = 'De-duplication of successive lines that have identical punctuation and whitespace.',
}
export enum LogsDedupStrategy {
none = 'none',
exact = 'exact',
numbers = 'numbers',
signature = 'signature',
}
export interface LogsParser {
/**
* Value-agnostic matcher for a field label.
* Used to filter rows, and first capture group contains the value.
*/
buildMatcher: (label: string) => RegExp;
/**
* Returns all parsable substrings from a line, used for highlighting
*/
getFields: (line: string) => string[];
/**
* Gets the label name from a parsable substring of a line
*/
getLabelFromField: (field: string) => string;
/**
* Gets the label value from a parsable substring of a line
*/
getValueFromField: (field: string) => string;
/**
* Function to verify if this is a valid parser for the given line.
* The parser accepts the line unless it returns undefined.
*/
test: (line: string) => any;
}
const LOGFMT_REGEXP = /(?:^|\s)(\w+)=("[^"]*"|\S+)/;
export const LogsParsers: { [name: string]: LogsParser } = {
......
import { SeriesData, FieldType, LogsModel, LogsMetaKind, LogsDedupStrategy } from '@grafana/ui';
import {
dedupLogRows,
calculateFieldStats,
calculateLogsLabelStats,
dedupLogRows,
getParser,
LogsDedupStrategy,
LogsModel,
LogsParsers,
seriesDataToLogsModel,
LogsMetaKind,
} from '../logs_model';
import { SeriesData, FieldType } from '@grafana/ui';
describe('dedupLogRows()', () => {
test('should return rows as is when dedup is set to none', () => {
......
......@@ -12,8 +12,7 @@ import {
} from './explore';
import { ExploreUrlState } from 'app/types/explore';
import store from 'app/core/store';
import { LogsDedupStrategy } from 'app/core/logs_model';
import { DataQueryError } from '@grafana/ui';
import { DataQueryError, LogsDedupStrategy } from '@grafana/ui';
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
datasource: null,
......
......@@ -22,6 +22,9 @@ import {
guessFieldTypes,
TimeFragment,
DataQueryError,
LogRowModel,
LogsModel,
LogsDedupStrategy,
} from '@grafana/ui';
import TimeSeries from 'app/core/time_series2';
import {
......@@ -33,7 +36,7 @@ import {
QueryOptions,
ResultGetter,
} from 'app/types/explore';
import { LogsDedupStrategy, seriesDataToLogsModel, LogsModel, LogRowModel } from 'app/core/logs_model';
import { seriesDataToLogsModel } from 'app/core/logs_model';
import { toUtc } from '@grafana/ui/src/utils/moment_wrapper';
import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
......
import React, { PureComponent } from 'react';
import { css, cx } from 'emotion';
import { Themeable, withTheme, GrafanaTheme, selectThemeVariant, LinkButton } from '@grafana/ui';
import {
Themeable,
withTheme,
GrafanaTheme,
selectThemeVariant,
LinkButton,
LogsModel,
LogRowModel,
} from '@grafana/ui';
import { LogsModel, LogRowModel } from 'app/core/logs_model';
import ElapsedTime from './ElapsedTime';
import { ButtonSize, ButtonVariant } from '@grafana/ui/src/components/Button/AbstractButton';
......
import React, { PureComponent } from 'react';
import { calculateLogsLabelStats, LogLabelStatsModel, LogRowModel } from 'app/core/logs_model';
import { LogLabelStats } from './LogLabelStats';
import { LogRowModel, LogLabelStatsModel } from '@grafana/ui';
import { calculateLogsLabelStats } from 'app/core/logs_model';
interface Props {
getRows?: () => LogRowModel[];
......
import React, { PureComponent } from 'react';
import classnames from 'classnames';
import { LogLabelStatsModel } from 'app/core/logs_model';
import { LogLabelStatsModel } from '@grafana/ui';
function LogLabelStatsRow(logLabelStatsModel: LogLabelStatsModel) {
const { active, count, proportion, value } = logLabelStatsModel;
......
import React, { PureComponent } from 'react';
import { LogRowModel } from 'app/core/logs_model';
import { LogLabel } from './LogLabel';
import { Labels } from '@grafana/ui';
import { Labels, LogRowModel } from '@grafana/ui';
interface Props {
getRows?: () => LogRowModel[];
......
......@@ -3,7 +3,7 @@ import _ from 'lodash';
import Highlighter from 'react-highlight-words';
import classnames from 'classnames';
import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
import { calculateFieldStats, getParser } from 'app/core/logs_model';
import { LogLabels } from './LogLabels';
import { findHighlightChunksInText } from 'app/core/utils/text';
import { LogLabelStats } from './LogLabelStats';
......@@ -15,7 +15,15 @@ import {
HasMoreContextRows,
LogRowContextQueryErrors,
} from './LogRowContextProvider';
import { ThemeContext, selectThemeVariant, GrafanaTheme, DataQueryResponse } from '@grafana/ui';
import {
ThemeContext,
selectThemeVariant,
GrafanaTheme,
DataQueryResponse,
LogRowModel,
LogLabelStatsModel,
LogsParser,
} from '@grafana/ui';
import { LogRowContext } from './LogRowContext';
import tinycolor from 'tinycolor2';
......@@ -29,7 +37,7 @@ interface Props {
getRows: () => LogRowModel[];
onClickLabel?: (label: string, value: string) => void;
onContextClick?: () => void;
getRowContext?: (row: LogRowModel, limit: number) => Promise<DataQueryResponse>;
getRowContext?: (row: LogRowModel, options?: any) => Promise<DataQueryResponse>;
className?: string;
}
......
......@@ -7,10 +7,10 @@ import {
ClickOutsideWrapper,
CustomScrollbar,
DataQueryError,
LogRowModel,
} from '@grafana/ui';
import { css, cx } from 'emotion';
import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider';
import { LogRowModel } from 'app/core/logs_model';
import { Alert } from './Error';
interface LogRowContextProps {
......
import { LogRowModel } from 'app/core/logs_model';
import { LogRowContextQueryResponse, SeriesData, DataQueryResponse, DataQueryError } from '@grafana/ui';
import { DataQueryResponse, DataQueryError, LogRowModel } from '@grafana/ui';
import { useState, useEffect } from 'react';
import flatten from 'lodash/flatten';
import useAsync from 'react-use/lib/useAsync';
export interface LogRowContextRows {
before?: Array<string | DataQueryError>;
after?: Array<string | DataQueryError>;
before?: string[];
after?: string[];
}
export interface LogRowContextQueryErrors {
before?: string;
......@@ -19,7 +19,7 @@ export interface HasMoreContextRows {
interface LogRowContextProviderProps {
row: LogRowModel;
getRowContext: (row: LogRowModel, limit: number) => Promise<DataQueryResponse>;
getRowContext: (row: LogRowModel, options?: any) => Promise<DataQueryResponse>;
children: (props: {
result: LogRowContextRows;
errors: LogRowContextQueryErrors;
......@@ -34,23 +34,44 @@ export const LogRowContextProvider: React.FunctionComponent<LogRowContextProvide
children,
}) => {
const [limit, setLimit] = useState(10);
const [result, setResult] = useState<LogRowContextQueryResponse>(null);
const [errors, setErrors] = useState<LogRowContextQueryErrors>(null);
const [result, setResult] = useState<{
data: string[][];
errors: string[];
}>(null);
const [hasMoreContextRows, setHasMoreContextRows] = useState({
before: true,
after: true,
});
const { value } = useAsync(async () => {
const context = await getRowContext(row, limit);
const promises = [
getRowContext(row, {
limit,
}),
getRowContext(row, {
limit,
direction: 'FORWARD',
}),
];
const results: Array<DataQueryResponse | DataQueryError> = await Promise.all(promises.map(p => p.catch(e => e)));
return {
data: context.data.map(series => {
if ((series as SeriesData).rows) {
return (series as SeriesData).rows.map(row => row[1]);
data: results.map(result => {
if ((result as DataQueryResponse).data) {
return (result as DataQueryResponse).data.map(series => {
return series.rows.map(row => row[1]);
});
} else {
return [];
}
}),
errors: results.map(result => {
if ((result as DataQueryError).message) {
return (result as DataQueryError).message;
} else {
return [series];
return null;
}
return [];
}),
};
}, [limit]);
......@@ -60,7 +81,6 @@ export const LogRowContextProvider: React.FunctionComponent<LogRowContextProvide
setResult(currentResult => {
let hasMoreLogsBefore = true,
hasMoreLogsAfter = true;
let beforeContextError, afterContextError;
if (currentResult && currentResult.data[0].length === value.data[0].length) {
hasMoreLogsBefore = false;
......@@ -70,23 +90,11 @@ export const LogRowContextProvider: React.FunctionComponent<LogRowContextProvide
hasMoreLogsAfter = false;
}
if (value.data[0] && value.data[0].length > 0 && value.data[0][0].message) {
beforeContextError = value.data[0][0].message;
}
if (value.data[1] && value.data[1].length > 0 && value.data[1][0].message) {
afterContextError = value.data[1][0].message;
}
setHasMoreContextRows({
before: hasMoreLogsBefore,
after: hasMoreLogsAfter,
});
setErrors({
before: beforeContextError,
after: afterContextError,
});
return value;
});
}
......@@ -94,10 +102,13 @@ export const LogRowContextProvider: React.FunctionComponent<LogRowContextProvide
return children({
result: {
before: result ? result.data[0] : [],
after: result ? result.data[1] : [],
before: result ? flatten(result.data[0]) : [],
after: result ? flatten(result.data[1]) : [],
},
errors: {
before: result ? result.errors[0] : null,
after: result ? result.errors[1] : null,
},
errors,
hasMoreContextRows,
updateLimit: () => setLimit(limit + 10),
});
......
......@@ -2,16 +2,26 @@ import _ from 'lodash';
import React, { PureComponent } from 'react';
import * as rangeUtil from '@grafana/ui/src/utils/rangeutil';
import { RawTimeRange, Switch, LogLevel, TimeZone, TimeRange, AbsoluteTimeRange } from '@grafana/ui';
import {
RawTimeRange,
Switch,
LogLevel,
TimeZone,
TimeRange,
AbsoluteTimeRange,
LogsMetaKind,
LogsModel,
LogsDedupStrategy,
LogRowModel,
} from '@grafana/ui';
import TimeSeries from 'app/core/time_series2';
import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind, LogRowModel } from 'app/core/logs_model';
import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
import Graph from './Graph';
import { LogLabels } from './LogLabels';
import { LogRow } from './LogRow';
import { LogsDedupDescription } from 'app/core/logs_model';
const PREVIEW_LIMIT = 100;
......@@ -60,7 +70,7 @@ interface Props {
onStopScanning?: () => void;
onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
onToggleLogLevel: (hiddenLogLevels: Set<LogLevel>) => void;
getRowContext?: (row: LogRowModel, limit: number) => Promise<any>;
getRowContext?: (row: LogRowModel, options?: any) => Promise<any>;
}
interface State {
......
......@@ -10,10 +10,12 @@ import {
toUtc,
dateTime,
DataSourceApi,
LogsModel,
LogRowModel,
LogsDedupStrategy,
} from '@grafana/ui';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { LogsModel, LogsDedupStrategy, LogRowModel } from 'app/core/logs_model';
import { StoreState } from 'app/types';
import { changeDedupStrategy, changeTime } from './state/actions';
......@@ -29,6 +31,7 @@ interface LogsContainerProps {
datasourceInstance: DataSourceApi | null;
exploreId: ExploreId;
loading: boolean;
logsHighlighterExpressions?: string[];
logsResult?: LogsModel;
dedupedResult?: LogsModel;
......@@ -77,11 +80,11 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
});
};
getLogRowContext = async (row: LogRowModel, limit: number) => {
getLogRowContext = async (row: LogRowModel, options?: any) => {
const { datasourceInstance } = this.props;
if (datasourceInstance) {
return datasourceInstance.getLogRowContext(row, limit);
return datasourceInstance.getLogRowContext(row, options);
}
return [];
......
import { refreshExplore, testDatasource, loadDatasource } from './actions';
import { ExploreId, ExploreUrlState, ExploreUpdateState } from 'app/types';
import { thunkTester } from 'test/core/thunk/thunkTester';
import { LogsDedupStrategy } from 'app/core/logs_model';
import {
initializeExploreAction,
InitializeExplorePayload,
......@@ -18,7 +17,7 @@ import { Emitter } from 'app/core/core';
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { makeInitialUpdateState } from './reducers';
import { DataQuery } from '@grafana/ui/src/types/datasource';
import { DefaultTimeZone, RawTimeRange } from '@grafana/ui';
import { DefaultTimeZone, RawTimeRange, LogsDedupStrategy } from '@grafana/ui';
import { toUtc } from '@grafana/ui/src/utils/moment_wrapper';
jest.mock('app/features/plugins/datasource_srv', () => ({
......
......@@ -35,7 +35,8 @@ import {
DataSourceSelectItem,
QueryFixAction,
TimeRange,
} from '@grafana/ui/src/types';
LogsDedupStrategy,
} from '@grafana/ui';
import {
ExploreId,
ExploreUrlState,
......@@ -87,7 +88,6 @@ import {
changeModeAction,
} from './actionTypes';
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
import { LogsDedupStrategy } from 'app/core/logs_model';
import { getTimeZone } from 'app/features/profile/state/selectors';
import { isDateTime } from '@grafana/ui/src/utils/moment_wrapper';
import { toDataQueryError } from 'app/features/dashboard/state/PanelQueryState';
......
......@@ -29,10 +29,9 @@ import {
import { Reducer } from 'redux';
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { updateLocation } from 'app/core/actions/location';
import { LogsDedupStrategy, LogsModel } from 'app/core/logs_model';
import { serializeStateToUrlParam } from 'app/core/utils/explore';
import TableModel from 'app/core/table_model';
import { DataSourceApi, DataQuery } from '@grafana/ui';
import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy } from '@grafana/ui';
describe('Explore item reducer', () => {
describe('scanning', () => {
......
......@@ -10,7 +10,7 @@ import {
sortLogsResult,
} from 'app/core/utils/explore';
import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore';
import { DataQuery } from '@grafana/ui/src/types';
import { DataQuery, LogsModel } from '@grafana/ui';
import {
HigherOrderAction,
ActionTypes,
......@@ -58,7 +58,7 @@ import { LocationUpdate } from 'app/types';
import TableModel from 'app/core/table_model';
import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
import { subscriptionDataReceivedAction, startSubscriptionAction } from './epics';
import { LogsModel, seriesDataToLogsModel } from 'app/core/logs_model';
import { seriesDataToLogsModel } from 'app/core/logs_model';
export const DEFAULT_RANGE = {
from: 'now-6h',
......
import { deduplicatedLogsSelector } from './selectors';
import { LogsDedupStrategy } from 'app/core/logs_model';
import { LogsDedupStrategy } from '@grafana/ui';
import { ExploreItemState } from 'app/types';
const state = {
......
......@@ -16,12 +16,12 @@ import {
DataSourceApi,
DataSourceInstanceSettings,
DataQueryError,
} from '@grafana/ui/src/types';
LogRowModel,
} from '@grafana/ui';
import { LokiQuery, LokiOptions } from './types';
import { BackendSrv } from 'app/core/services/backend_srv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { safeStringifyValue } from 'app/core/utils/explore';
import { LogRowModel } from 'app/core/logs_model';
export const DEFAULT_MAX_LINES = 1000;
......@@ -41,6 +41,11 @@ function serializeParams(data: any) {
.join('&');
}
interface LokiContextQueryOptions {
direction?: 'BACKWARD' | 'FORWARD';
limit?: number;
}
export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
languageProvider: LanguageProvider;
maxLines: number;
......@@ -224,7 +229,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
return Math.ceil(date.valueOf() * 1e6);
}
prepareLogRowContextQueryTargets = (row: LogRowModel, limit: number) => {
prepareLogRowContextQueryTarget = (row: LogRowModel, limit: number, direction: 'BACKWARD' | 'FORWARD') => {
const query = Object.keys(row.labels)
.map(label => {
return `${label}="${row.labels[label]}"`;
......@@ -236,69 +241,58 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
const commontTargetOptons = {
limit,
query: `{${query}}`,
direction,
};
return [
// Target for "before" context
{
if (direction === 'BACKWARD') {
return {
...commontTargetOptons,
start: timeEpochNs - contextTimeBuffer,
end: timeEpochNs,
direction: 'BACKWARD',
},
// Target for "after" context
{
direction,
};
} else {
return {
...commontTargetOptons,
start: timeEpochNs, // TODO: We should add 1ns here for the original row not no be included in the result
end: timeEpochNs + contextTimeBuffer,
direction: 'FORWARD',
},
];
};
}
};
getLogRowContext = (row: LogRowModel, limit?: number) => {
// Preparing two targets, for preceeding and following log queries
const targets = this.prepareLogRowContextQueryTargets(row, limit || 10);
return Promise.all(
targets.map(target => {
return this._request('/api/prom/query', target).catch(e => {
const error: DataQueryError = {
message: 'Error during context query. Please check JS console logs.',
status: e.status,
statusText: e.statusText,
};
return error;
});
})
).then((results: any[]) => {
const series: Array<Array<SeriesData | DataQueryError>> = [];
const emptySeries = {
fields: [],
rows: [],
} as SeriesData;
for (let i = 0; i < results.length; i++) {
const result = results[i];
series[i] = [];
if (result.data) {
for (const stream of result.data.streams || []) {
const seriesData = logStreamToSeriesData(stream);
series[i].push(seriesData);
}
} else {
series[i].push(result);
getLogRowContext = async (row: LogRowModel, options?: LokiContextQueryOptions) => {
const target = this.prepareLogRowContextQueryTarget(
row,
(options && options.limit) || 10,
(options && options.direction) || 'BACKWARD'
);
const series: SeriesData[] = [];
try {
const result = await this._request('/api/prom/query', target);
if (result.data) {
for (const stream of result.data.streams || []) {
const seriesData = logStreamToSeriesData(stream);
series.push(seriesData);
}
}
// Following context logs are requested in "forward" direction.
// This means, that we need to reverse those to make them sorted
// in descending order (by timestamp)
if (series[1][0] && (series[1][0] as SeriesData).rows) {
(series[1][0] as SeriesData).rows.reverse();
if (options && options.direction === 'FORWARD') {
if (series[0] && series[0].rows) {
series[0].rows.reverse();
}
}
return { data: [series[0][0] || emptySeries, series[1][0] || emptySeries] };
});
return {
data: series,
};
} catch (e) {
const error: DataQueryError = {
message: 'Error during context query. Please check JS console logs.',
status: e.status,
statusText: e.statusText,
};
throw error;
}
};
testDatasource() {
......
......@@ -11,10 +11,11 @@ import {
LogLevel,
TimeRange,
DataQueryError,
LogsModel,
LogsDedupStrategy,
} from '@grafana/ui';
import { Emitter, TimeSeries } from 'app/core/core';
import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
import TableModel from 'app/core/table_model';
export enum ExploreMode {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment