logs-volume: remove custom timeout (#45041)

* logs-volume: remove custom timeout

* make error message a little better
This commit is contained in:
Gábor Farkas
2022-02-08 15:39:09 +01:00
committed by GitHub
parent c5f0701707
commit 2c0030b1b4
4 changed files with 78 additions and 60 deletions

View File

@@ -39,7 +39,7 @@ import {
} from '@grafana/data';
import { getThemeColor } from 'app/core/utils/colors';
import { SIPrefix } from '@grafana/data/src/valueFormats/symbolFormatters';
import { Observable, throwError, timeout } from 'rxjs';
import { Observable } from 'rxjs';
export const LIMIT_LABEL = 'Line limit';
export const COMMON_LABELS = 'Common labels';
@@ -617,10 +617,7 @@ function aggregateFields(dataFrames: DataFrame[], config: FieldConfig): DataFram
return aggregatedDataFrame;
}
const LOGS_VOLUME_QUERY_DEFAULT_TIMEOUT = 60000;
type LogsVolumeQueryOptions<T extends DataQuery> = {
timeout?: number;
extractLevel: (dataFrame: DataFrame) => LogLevel;
targets: T[];
range: TimeRange;
@@ -651,45 +648,36 @@ export function queryLogsVolume<T extends DataQuery>(
data: [],
});
const subscription = (datasource.query(logsVolumeRequest) as Observable<DataQueryResponse>)
.pipe(
timeout({
each: options.timeout || LOGS_VOLUME_QUERY_DEFAULT_TIMEOUT,
with: () => throwError(new Error('Request timed-out. Please make your query more specific and try again.')),
})
)
.subscribe({
complete: () => {
const aggregatedLogsVolume = aggregateRawLogsVolume(rawLogsVolume, options.extractLevel);
if (aggregatedLogsVolume[0]) {
aggregatedLogsVolume[0].meta = {
custom: {
targets: options.targets,
absoluteRange: { from: options.range.from.valueOf(), to: options.range.to.valueOf() },
},
};
}
observer.next({
state: LoadingState.Done,
error: undefined,
data: aggregatedLogsVolume,
});
observer.complete();
},
next: (dataQueryResponse: DataQueryResponse) => {
rawLogsVolume = rawLogsVolume.concat(dataQueryResponse.data.map(toDataFrame));
},
error: (error) => {
const errorMessage = error.data?.message || error.statusText || error.message;
console.error('Log volume query failed with error: ', errorMessage);
observer.next({
state: LoadingState.Error,
error: error,
data: [],
});
observer.error(error);
},
});
const subscription = (datasource.query(logsVolumeRequest) as Observable<DataQueryResponse>).subscribe({
complete: () => {
const aggregatedLogsVolume = aggregateRawLogsVolume(rawLogsVolume, options.extractLevel);
if (aggregatedLogsVolume[0]) {
aggregatedLogsVolume[0].meta = {
custom: {
targets: options.targets,
absoluteRange: { from: options.range.from.valueOf(), to: options.range.to.valueOf() },
},
};
}
observer.next({
state: LoadingState.Done,
error: undefined,
data: aggregatedLogsVolume,
});
observer.complete();
},
next: (dataQueryResponse: DataQueryResponse) => {
rawLogsVolume = rawLogsVolume.concat(dataQueryResponse.data.map(toDataFrame));
},
error: (error) => {
observer.next({
state: LoadingState.Error,
error: error,
data: [],
});
observer.error(error);
},
});
return () => {
subscription?.unsubscribe();
};

View File

@@ -40,11 +40,23 @@ describe('LogsVolumePanel', () => {
expect(screen.getByText('ExploreGraph')).toBeInTheDocument();
});
it('shows warning message without details', () => {
it('shows short warning message', () => {
renderPanel({ state: LoadingState.Error, error: { data: { message: 'Test error message' } }, data: [] });
expect(screen.getByText('Failed to load log volume for this query')).toBeInTheDocument();
expect(screen.getByText('Please check console logs for more details.')).toBeInTheDocument();
expect(screen.queryByText('Test error message')).not.toBeInTheDocument();
expect(screen.getByText('Test error message')).toBeInTheDocument();
});
it('shows long warning message', () => {
// we make a long message
const messagePart = 'One two three four five six seven eight nine ten.';
const message = messagePart + ' ' + messagePart + ' ' + messagePart;
renderPanel({ state: LoadingState.Error, error: { data: { message } }, data: [] });
expect(screen.getByText('Failed to load log volume for this query')).toBeInTheDocument();
expect(screen.queryByText(message)).not.toBeInTheDocument();
const button = screen.getByText('Show details');
button.click();
expect(screen.getByText(message)).toBeInTheDocument();
});
it('does not show the panel when there is no volume data', () => {

View File

@@ -1,7 +1,7 @@
import { AbsoluteTimeRange, DataQueryResponse, LoadingState, SplitOpen, TimeZone } from '@grafana/data';
import { AbsoluteTimeRange, DataQueryError, DataQueryResponse, LoadingState, SplitOpen, TimeZone } from '@grafana/data';
import { Alert, Button, Collapse, InlineField, TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui';
import { ExploreGraph } from './ExploreGraph';
import React from 'react';
import React, { useState } from 'react';
import { css } from '@emotion/css';
type Props = {
@@ -14,6 +14,35 @@ type Props = {
onLoadLogsVolume: () => void;
};
const SHORT_ERROR_MESSAGE_LIMIT = 100;
function ErrorAlert(props: { error: DataQueryError }) {
const [isOpen, setIsOpen] = useState(false);
// generic get-error-message-logic, taken from
// /public/app/features/explore/ErrorContainer.tsx
const message = props.error.message || props.error.data?.message || '';
const showButton = !isOpen && message.length > SHORT_ERROR_MESSAGE_LIMIT;
return (
<Alert title="Failed to load log volume for this query" severity="warning">
{showButton ? (
<Button
variant="secondary"
size="xs"
onClick={() => {
setIsOpen(true);
}}
>
Show details
</Button>
) : (
message
)}
</Alert>
);
}
export function LogsVolumePanel(props: Props) {
const { width, logsVolumeData, absoluteRange, timeZone, splitOpen, onUpdateTimeRange, onLoadLogsVolume } = props;
const theme = useTheme2();
@@ -26,11 +55,7 @@ export function LogsVolumePanel(props: Props) {
if (!logsVolumeData) {
return null;
} else if (logsVolumeData?.error) {
return (
<Alert title="Failed to load log volume for this query" severity="warning">
Please check console logs for more details.
</Alert>
);
return <ErrorAlert error={logsVolumeData?.error} />;
} else if (logsVolumeData?.state === LoadingState.Loading) {
LogsVolumePanelContent = <span>Log volume is loading...</span>;
} else if (logsVolumeData?.data) {

View File

@@ -70,12 +70,6 @@ export const DEFAULT_MAX_LINES = 1000;
export const LOKI_ENDPOINT = '/loki/api/v1';
const NS_IN_MS = 1000000;
/**
* Loki's logs volume query may be expensive as it requires counting all logs in the selected range. If such query
* takes too much time it may need be made more specific to limit number of logs processed under the hood.
*/
const LOGS_VOLUME_TIMEOUT = 10000;
const RANGE_QUERY_ENDPOINT = `${LOKI_ENDPOINT}/query_range`;
const INSTANT_QUERY_ENDPOINT = `${LOKI_ENDPOINT}/query`;
@@ -150,7 +144,6 @@ export class LokiDatasource
});
return queryLogsVolume(this, logsVolumeRequest, {
timeout: LOGS_VOLUME_TIMEOUT,
extractLevel,
range: request.range,
targets: request.targets,