Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release-5.5] Add debug info #86

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions cypress/fixtures/query-range-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,7 @@ export const queryRangeMatrixInvalidResponse = () => {
export const queryRangeStreamsInvalidResponse = () => {
return {};
};

export const queryRangeStreamsErrorResponse = () => {
return 'max entries limit';
};
18 changes: 18 additions & 0 deletions cypress/integration/logs-page.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TestIds } from '../../src/test-ids';
import {
queryRangeMatrixInvalidResponse,
queryRangeMatrixValidResponse,
queryRangeStreamsErrorResponse,
queryRangeStreamsInvalidResponse,
queryRangeStreamsValidResponse,
} from '../fixtures/query-range-fixtures';
Expand Down Expand Up @@ -417,4 +418,21 @@ describe('Logs Page', () => {
cy.get('@queryRangeStreams.all').should('have.length.at.least', 1);
cy.get('@queryRangeMatrix.all').should('have.length.at.least', 1);
});

it('displays a suggestion to fix an error', () => {
cy.intercept(QUERY_RANGE_STREAMS_URL_MATCH, {
statusCode: 400,
body: queryRangeStreamsErrorResponse(),
}).as('queryRangeStreams');

cy.visit(LOGS_PAGE_URL);

cy.getByTestId(TestIds.LogsTable)
.should('exist')
.within(() => {
cy.contains('Select a smaller time range to reduce the number of results');
});

cy.get('@queryRangeStreams.all').should('have.length.at.least', 1);
});
});
16 changes: 15 additions & 1 deletion src/cancellable-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ class TimeoutError extends Error {
}
}

class FetchError extends Error {
status: number;
name: string;

constructor(message: string, status: number) {
super(message);
this.name = 'Fetch Error';
this.status = status;
}
}

export const isFetchError = (error: unknown): error is FetchError =>
!!(error as FetchError).name && (error as FetchError).name === 'Fetch Error';

export const cancellableFetch = <T>(
url: string,
init?: RequestInitWithTimeout,
Expand All @@ -25,7 +39,7 @@ export const cancellableFetch = <T>(
}).then(async (response) => {
if (!response.ok) {
const text = await response.text();
throw new Error(text);
throw new FetchError(text, response.status);
}
return response.json();
});
Expand Down
5 changes: 5 additions & 0 deletions src/components/error-message.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.co-logs-error_message {
margin-bottom: var(--pf-global--spacer--md);
margin-top: var(--pf-global--spacer--md);
background-color: transparent;
}
107 changes: 107 additions & 0 deletions src/components/error-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Alert, Text, TextContent, TextVariants } from '@patternfly/react-core';
import React from 'react';
import { isFetchError } from '../cancellable-fetch';
import { notUndefined } from '../value-utils';
import './error-message.css';

interface ErrorMessageProps {
error: unknown | Error;
}

const Suggestion: React.FC = ({ children }) => (
<Text component={TextVariants.small}>{children}</Text>
);

const messages: Record<string, React.ReactElement> = {
'max entries limit': (
<>
<Suggestion>Select a smaller time range to reduce the number of results</Suggestion>
<Suggestion>
Select a namespace, pod, or container filter to improve the query performance
</Suggestion>
<Suggestion>
Increase Loki &quot;max_entries_limit_per_query&quot; entry in configuration file
</Suggestion>
</>
),
'deadline exceeded,maximum of series': (
<>
<Suggestion>Select a smaller time range to reduce the number of results</Suggestion>
<Suggestion>
Select a namespace, pod, or container filter to improve the query performance
</Suggestion>
</>
),
'too many outstanding requests': (
<>
<Suggestion>Select a smaller time range to reduce the number of results</Suggestion>
<Suggestion>
Select a namespace, pod, or container filter to improve the query performance
</Suggestion>
<Suggestion>
Ensure Loki config contains &quot;parallelise_shardable_queries: true&quot; and
&quot;max_outstanding_requests_per_tenant: 2048&quot;
</Suggestion>
</>
),
'time range exceeds,maximum resolution': (
<>
<Suggestion>Reduce the time range to decrease the number of results</Suggestion>
<Suggestion>
Increase Loki &quot;max_query_length&quot; entry in configuration file
</Suggestion>
</>
),
'cannot connect to LokiStack': (
<>
<Suggestion>Make sure you have an instance of LokiStack runnning</Suggestion>
</>
),
};

export const ErrorMessage: React.FC<ErrorMessageProps> = ({ error }) => {
let errorMessage = (error as Error).message || String(error);
let title = 'You may consider the following query changes to avoid this error';
const status = isFetchError(error) ? error.status : undefined;

if (status !== undefined) {
switch (status) {
case 502:
title = 'This plugin requires Loki Operator and LokiStack to be running in the cluster';
errorMessage = 'cannot connect to LokiStack';
break;
}
}

const suggestions = React.useMemo(
() =>
Object.keys(messages)
.map((messageKey) => {
const errorKeys = messageKey.split(',');
const hasErrorKey = errorKeys.some((key) => errorMessage.includes(key));
return hasErrorKey ? messages[messageKey] : undefined;
})
.filter(notUndefined),
[errorMessage],
);

return (
<>
<Alert
className="co-logs-error_message"
variant="danger"
isInline
isPlain
title={errorMessage}
/>

{suggestions && suggestions.length > 0 ? (
<TextContent>
<Text component={TextVariants.p}>{title}</Text>

{suggestions}
</TextContent>
) : null}
</>
);
};
3 changes: 2 additions & 1 deletion src/components/logs-table.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@

.co-logs-table__row-error,
.co-logs-table__row-streaming {
display: flex;
display: grid;
justify-content: center;
justify-items: center;
}
8 changes: 2 additions & 6 deletions src/components/logs-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { TestIds } from '../test-ids';
import { CenteredContainer } from './centered-container';
import { LogDetail } from './log-detail';
import './logs-table.css';
import { ErrorMessage } from './error-message';

interface LogsTableProps {
logsData?: QueryRangeResponse;
Expand Down Expand Up @@ -320,12 +321,7 @@ export const LogsTable: React.FC<LogsTableProps> = ({
<Tr className="co-logs-table__row-info">
<Td colSpan={columns.length + 2} key="error-row">
<div className="co-logs-table__row-error">
<Alert
variant="danger"
isInline
isPlain
title={(error as Error).message || String(error)}
/>
<ErrorMessage error={error} />
</div>
</Td>
</Tr>
Expand Down