-const createInitialLogPanelState = (logResources: LogResource[]) => {
- const allLogs = logsToLines(logResources);
- const groupedLogResources = groupBy(logResources, log => log.eventType);
- const groupedLogs = Object
- .keys(groupedLogResources)
- .reduce((grouped, key) => ({
- ...grouped,
- [key]: logsToLines(groupedLogResources[key])
- }), {});
- const filters = [COMBINED_FILTER_TYPE, ...Object.keys(groupedLogs)];
- const logs = { [COMBINED_FILTER_TYPE]: allLogs, ...groupedLogs };
+/**
+ * Loads the contents of each file from each file's lastByte simultaneously
+ * while respecting the maxLogFetchSize by requesting the start and end
+ * of the desired block and inserting a snipline.
+ * @param logFilesWithProgress CollectionFiles with the last byte previously loaded
+ * @param logService
+ * @param process
+ * @returns LogFragment[] containing a single LogFragment corresponding to each input file
+ */
+const loadContainerLogFileContents = async (logFilesWithProgress: FileWithProgress[], logService: LogService, process: Process) => (
+ (await Promise.allSettled(logFilesWithProgress.filter(({ file }) => file.size > 0).map(({ file, lastByte }) => {
+ const requestSize = file.size - lastByte;
+ if (requestSize > maxLogFetchSize) {
+ const chunkSize = Math.floor(maxLogFetchSize / 2);
+ const firstChunkEnd = lastByte + chunkSize - 1;
+ return Promise.all([
+ logService.getLogFileContents(process.containerRequest, file, lastByte, firstChunkEnd),
+ logService.getLogFileContents(process.containerRequest, file, file.size - chunkSize, file.size - 1)
+ ] as Promise<(LogFragment)>[]);
+ } else {
+ return Promise.all([logService.getLogFileContents(process.containerRequest, file, lastByte, file.size - 1)]);
+ }
+ })).then((res) => {
+ if (res.length && res.every(promiseResult => (promiseResult.status === 'rejected'))) {
+ // Since allSettled does not pass promise rejection we throw an
+ // error if every request failed
+ const error = res.find(
+ (promiseResult): promiseResult is PromiseRejectedResult => promiseResult.status === 'rejected'
+ )?.reason;
+ return Promise.reject(error);
+ }
+ return res.filter((promiseResult): promiseResult is PromiseFulfilledResult<LogFragment[]> => (
+ // Filter out log files with rejected promises
+ // (Promise.all rejects on any failure)
+ promiseResult.status === 'fulfilled' &&
+ // Filter out files where any fragment is empty
+ // (prevent incorrect snipline generation or an un-resumable situation)
+ !!promiseResult.value.every(logFragment => logFragment.contents.length)
+ )).map(one => one.value)
+ })).map((logResponseSet) => {
+ // For any multi fragment response set, modify the last line of non-final chunks to include a line break and snip line
+ // Don't add snip line as a separate line so that sorting won't reorder it
+ for (let i = 1; i < logResponseSet.length; i++) {
+ const fragment = logResponseSet[i - 1];
+ const lastLineIndex = fragment.contents.length - 1;
+ const lastLineContents = fragment.contents[lastLineIndex];
+ const newLastLine = `${lastLineContents}\n${SNIPLINE}`;
+
+ logResponseSet[i - 1].contents[lastLineIndex] = newLastLine;
+ }
+
+ // Merge LogFragment Array (representing multiple log line arrays) into single LogLine[] / LogFragment
+ return logResponseSet.reduce((acc, curr: LogFragment) => ({
+ logType: curr.logType,
+ contents: [...(acc.contents || []), ...curr.contents]
+ }), {} as LogFragment);
+ })
+);
+
+const createInitialLogPanelState = (logFiles: CollectionFile[], logFragments: LogFragment[]): { filters: string[], logs: ProcessLogs } => {
+ const logs = groupLogs(logFiles, logFragments);
+ const filters = Object.keys(logs);