diff --git a/frontend/packages/console-shared/src/constants/common.ts b/frontend/packages/console-shared/src/constants/common.ts index c2559dcb5c4..6ce6c944ffa 100644 --- a/frontend/packages/console-shared/src/constants/common.ts +++ b/frontend/packages/console-shared/src/constants/common.ts @@ -47,6 +47,7 @@ export const COLUMN_MANAGEMENT_LOCAL_STORAGE_KEY = `${STORAGE_PREFIX}/table-colu export const LOG_WRAP_LINES_USERSETTINGS_KEY = `${USERSETTINGS_PREFIX}.log.wrapLines`; export const SHOW_YAML_EDITOR_TOOLTIPS_USER_SETTING_KEY = `${USERSETTINGS_PREFIX}.showYAMLEditorTooltips`; export const SHOW_YAML_EDITOR_TOOLTIPS_LOCAL_STORAGE_KEY = `${STORAGE_PREFIX}/showYAMLEditorTooltips`; +export const SHOW_FULL_LOG_USERSETTINGS_KEY = `${USERSETTINGS_PREFIX}.show.full.log`; // Bootstrap user for OpenShift 4.0 clusters (kube:admin) export const KUBE_ADMIN_USERNAMES = ['kube:admin']; diff --git a/frontend/packages/integration-tests-cypress/tests/app/resource-log.cy.ts b/frontend/packages/integration-tests-cypress/tests/app/resource-log.cy.ts new file mode 100644 index 00000000000..41ad47df115 --- /dev/null +++ b/frontend/packages/integration-tests-cypress/tests/app/resource-log.cy.ts @@ -0,0 +1,30 @@ +import { checkErrors } from '../../support'; +import { detailsPage } from '../../views/details-page'; +import { listPage, listPage } from '../../views/list-page'; + +describe('Pod log viewer tab', () => { + before(() => { + cy.login(); + }); + + afterEach(() => { + checkErrors(); + }); + + it('Open logs from pod details page tab and verify the log buffer sizes', () => { + cy.visit( + `/k8s/ns/openshift-kube-apiserver/core~v1~Pod?name=kube-apiserver-ip-&rowFilter-pod-status=Running&orderBy=desc&sortBy=Owner`, + ); + listPage.rows.clickFirstLinkInFirstRow(); + detailsPage.isLoaded(); + detailsPage.selectTab('Logs'); + detailsPage.isLoaded(); + // Verify the default log buffer size + cy.byTestID('no-log-lines').contains('1000 lines'); + // Verify the log exceeds the default log buffer size + cy.byTestID('show-full-log').check(); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000); + cy.byTestID('no-log-lines').should('not.contain', '1000 lines'); + }); +}); diff --git a/frontend/packages/pipelines-plugin/src/components/pipelineruns/logs/logs-utils.ts b/frontend/packages/pipelines-plugin/src/components/pipelineruns/logs/logs-utils.ts index 05716071adc..9f34576d95f 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelineruns/logs/logs-utils.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelineruns/logs/logs-utils.ts @@ -160,7 +160,7 @@ export const getDownloadAllLogsCallback = ( ); } } - const buffer = new LineBuffer(); + const buffer = new LineBuffer(null); buffer.ingest(allLogs); const blob = buffer.getBlob({ type: 'text/plain;charset=utf-8', diff --git a/frontend/public/components/utils/line-buffer.ts b/frontend/public/components/utils/line-buffer.ts index 4ad6dcf98df..22df4cb0641 100644 --- a/frontend/public/components/utils/line-buffer.ts +++ b/frontend/public/components/utils/line-buffer.ts @@ -6,11 +6,13 @@ export class LineBuffer { private _buffer: string[]; private _tail: string; private _hasTruncated: boolean; + private _maxSize?: number; - constructor() { + constructor(maxSize) { this._buffer = []; this._tail = ''; this._hasTruncated = false; + this._maxSize = maxSize; } ingest(text): number { @@ -22,6 +24,9 @@ export class LineBuffer { this._hasTruncated = true; } if (/\n$/.test(line)) { + if (this._buffer.length === this._maxSize) { + this._buffer.shift(); + } this._buffer.push(_.truncate(next, { length: TRUNCATE_LENGTH }).trimEnd()); lineCount++; this._tail = ''; diff --git a/frontend/public/components/utils/resource-log.tsx b/frontend/public/components/utils/resource-log.tsx index 1e19326ccb7..64886350d75 100644 --- a/frontend/public/components/utils/resource-log.tsx +++ b/frontend/public/components/utils/resource-log.tsx @@ -4,6 +4,8 @@ import * as React from 'react'; // @ts-ignore import { useSelector } from 'react-redux'; import { Base64 } from 'js-base64'; +import * as _ from 'lodash-es'; +import { Trans, useTranslation } from 'react-i18next'; import { Alert, AlertActionLink, Button, Checkbox, Divider, Tooltip } from '@patternfly/react-core'; import { Select as SelectDeprecated, @@ -11,21 +13,23 @@ import { SelectVariant as SelectVariantDeprecated, } from '@patternfly/react-core/deprecated'; import { LogViewer, LogViewerSearch } from '@patternfly/react-log-viewer'; - -import * as _ from 'lodash-es'; -import { Trans, useTranslation } from 'react-i18next'; -import { CompressIcon } from '@patternfly/react-icons/dist/esm/icons/compress-icon'; -import { ExpandIcon } from '@patternfly/react-icons/dist/esm/icons/expand-icon'; -import { DownloadIcon } from '@patternfly/react-icons/dist/esm/icons/download-icon'; -import { OutlinedWindowRestoreIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-window-restore-icon'; -import { OutlinedPlayCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-play-circle-icon'; +import { + CompressIcon, + ExpandIcon, + DownloadIcon, + OutlinedWindowRestoreIcon, + OutlinedPlayCircleIcon, +} from '@patternfly/react-icons'; import * as classNames from 'classnames'; -import { FLAGS, LOG_WRAP_LINES_USERSETTINGS_KEY } from '@console/shared/src/constants'; +import { + FLAGS, + LOG_WRAP_LINES_USERSETTINGS_KEY, + SHOW_FULL_LOG_USERSETTINGS_KEY, +} from '@console/shared/src/constants'; import { useUserSettings } from '@console/shared'; import { LoadingInline, TogglePlay, ExternalLink } from './'; import { modelFor, resourceURL } from '../../module/k8s'; import { WSFactory } from '../../module/ws-factory'; -import { LineBuffer } from './line-buffer'; import * as screenfull from 'screenfull'; import { RootState } from '@console/internal/redux'; import { k8sGet, k8sList, K8sResourceKind, PodKind } from '@console/internal/module/k8s'; @@ -36,6 +40,7 @@ import { Link } from 'react-router-dom'; import { resourcePath } from './resource-link'; import { isWindowsPod } from '../../module/k8s/pods'; import { getImpersonate } from '@console/dynamic-plugin-sdk'; +import useToggleLineBuffer from './useToggleLineBuffer'; export const STREAM_EOF = 'eof'; export const STREAM_LOADING = 'loading'; @@ -50,6 +55,8 @@ export const LOG_SOURCE_WAITING = 'waiting'; const LOG_TYPE_CURRENT = 'current'; const LOG_TYPE_PREVIOUS = 'previous'; +const DEFAULT_BUFFER_SIZE = 1000; + // Messages to display for corresponding log status const streamStatusMessages = { // t('public~Log stream ended.') @@ -161,6 +168,8 @@ export const LogControls: React.FC = ({ hasPreviousLog, logType, showLogTypeSelect, + isShowFullLog, + toggleShowFullLog, }) => { const { t } = useTranslation(); const [isLogTypeOpen, setLogTypeOpen] = React.useState(false); @@ -234,7 +243,9 @@ export const LogControls: React.FC = ({ ); }; + const label = t('public~Debug container'); + return (
@@ -314,6 +325,29 @@ export const LogControls: React.FC = ({ ); })} +
+ + { + toggleShowFullLog(checked); + }} + /> + +
+ = ({ // Resource agnostic log component export const ResourceLog: React.FC = ({ + bufferSize = DEFAULT_BUFFER_SIZE, containerName, dropdown, resource, resourceStatus, }) => { const { t } = useTranslation(); - const buffer = React.useRef(new LineBuffer()); // TODO Make this a hook + const [showFullLog, setShowFullLog] = useUserSettings( + SHOW_FULL_LOG_USERSETTINGS_KEY, + false, + true, + ); + const [showFullLogCheckbox, setShowFullLogCheckbox] = React.useState(showFullLog); + const buffer = useToggleLineBuffer(showFullLogCheckbox ? null : bufferSize); const ws = React.useRef(); // TODO Make this a hook const resourceLogRef = React.useRef(); const logViewerRef = React.useRef(null); @@ -415,14 +456,17 @@ export const ResourceLog: React.FC = ({ const [wrapLinesCheckbox, setWrapLinesCheckbox] = React.useState(wrapLines || hasWrapAnnotation); const firstRender = React.useRef(true); + const handleShowFullLogCheckbox = () => setShowFullLogCheckbox(!showFullLogCheckbox); React.useEffect(() => { if (firstRender.current) { firstRender.current = false; return; } + setWrapLines(wrapLinesCheckbox); - }, [wrapLinesCheckbox, setWrapLines]); + setShowFullLog(showFullLogCheckbox); + }, [wrapLinesCheckbox, showFullLogCheckbox, setWrapLines, setShowFullLog]); const timeoutIdRef = React.useRef(null); const countRef = React.useRef(0); @@ -532,7 +576,7 @@ export const ResourceLog: React.FC = ({ startWebSocket(); } return () => ws.current?.destroy(); - }, [error, resourceStatus, stale, startWebSocket]); + }, [error, resourceStatus, stale, startWebSocket, showFullLogCheckbox]); // Toggle currently displayed log content to/from fullscreen const toggleFullscreen = () => { @@ -613,10 +657,12 @@ export const ResourceLog: React.FC = ({ namespaceUID={namespaceUID} toggleWrapLines={setWrapLinesCheckbox} isWrapLines={wrapLinesCheckbox} + isShowFullLog={showFullLogCheckbox} hasPreviousLog={hasPreviousLogs} changeLogType={setLogType} logType={logType} showLogTypeSelect={resource.kind === 'Pod'} + toggleShowFullLog={handleShowFullLogCheckbox} /> ); @@ -676,7 +722,7 @@ export const ResourceLog: React.FC = ({
+
} @@ -721,6 +767,8 @@ type LogControlsProps = { hasPreviousLog?: boolean; logType: LogTypeStatus; showLogTypeSelect: boolean; + toggleShowFullLog: (showFullLogCheckbox: boolean) => void; + isShowFullLog: boolean; }; type ResourceLogProps = { @@ -728,6 +776,7 @@ type ResourceLogProps = { dropdown?: React.ReactNode; resource: any; resourceStatus: string; + bufferSize?: number; }; type LogTypeStatus = typeof LOG_TYPE_CURRENT | typeof LOG_TYPE_PREVIOUS; diff --git a/frontend/public/components/utils/useToggleLineBuffer.ts b/frontend/public/components/utils/useToggleLineBuffer.ts new file mode 100644 index 00000000000..d6420806af3 --- /dev/null +++ b/frontend/public/components/utils/useToggleLineBuffer.ts @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { LineBuffer } from './line-buffer'; + +const useToggleLineBuffer = (bufferSize: number | null) => { + const buffer = React.useRef(null); + React.useEffect(() => { + buffer.current = new LineBuffer(bufferSize); + }, [bufferSize]); + + return buffer; +}; + +export default useToggleLineBuffer; diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index af53e3c1334..33abfc038fb 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -1745,6 +1745,8 @@ "Previous log": "Previous log", "Only the current log is available for this container.": "Only the current log is available for this container.", "Debug in terminal is not currently available for windows containers.": "Debug in terminal is not currently available for windows containers.", + "Select to view the entire log. Default view is the last 1,000 lines.": "Select to view the entire log. Default view is the last 1,000 lines.", + "Show full log": "Show full log", "Wrap lines": "Wrap lines", "Raw": "Raw", "An error occurred while retrieving the requested logs.": "An error occurred while retrieving the requested logs.",