1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/dock/log.store.ts
Sebastian Malton 8b352f6c6b
fix regression from #2260 (#2891)
Signed-off-by: Sebastian Malton <sebastian@malton.name>
2021-05-27 10:46:15 -04:00

206 lines
5.7 KiB
TypeScript

/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autorun, computed, observable, makeObservable } from "mobx";
import { IPodLogsQuery, Pod, podsApi } from "../../api/endpoints";
import { autoBind, interval } from "../../utils";
import { dockStore, TabId, TabKind } from "./dock.store";
import { logTabStore } from "./log-tab.store";
type PodLogLine = string;
const logLinesToLoad = 500;
export class LogStore {
private refresher = interval(10, () => {
const id = dockStore.selectedTabId;
if (!this.podLogs.get(id)) return;
this.loadMore(id);
});
@observable podLogs = observable.map<TabId, PodLogLine[]>();
constructor() {
makeObservable(this);
autoBind(this);
autorun(() => {
const { selectedTab, isOpen } = dockStore;
if (selectedTab?.kind === TabKind.POD_LOGS && isOpen) {
this.refresher.start();
} else {
this.refresher.stop();
}
}, { delay: 500 });
}
handlerError(tabId: TabId, error: any): void {
if (error.error && !(error.message || error.reason || error.code)) {
error = error.error;
}
const message = [
`Failed to load logs: ${error.message}`,
`Reason: ${error.reason} (${error.code})`
];
this.refresher.stop();
this.podLogs.set(tabId, message);
}
/**
* Function prepares tailLines param for passing to API request
* Each time it increasing it's number, caused to fetch more logs.
* Also, it handles loading errors, rewriting whole logs with error
* messages
* @param tabId
*/
load = async (tabId: TabId) => {
try {
const logs = await this.loadLogs(tabId, {
tailLines: this.lines + logLinesToLoad
});
this.refresher.start();
this.podLogs.set(tabId, logs);
} catch (error) {
this.handlerError(tabId, error);
}
};
/**
* Function is used to refresher/stream-like requests.
* It changes 'sinceTime' param each time allowing to fetch logs
* starting from last line received.
* @param tabId
*/
loadMore = async (tabId: TabId) => {
if (!this.podLogs.get(tabId).length) {
return;
}
try {
const oldLogs = this.podLogs.get(tabId);
const logs = await this.loadLogs(tabId, {
sinceTime: this.getLastSinceTime(tabId)
});
// Add newly received logs to bottom
this.podLogs.set(tabId, [...oldLogs, ...logs]);
} catch (error) {
this.handlerError(tabId, error);
}
};
/**
* Main logs loading function adds necessary data to payload and makes
* an API request
* @param tabId
* @param params request parameters described in IPodLogsQuery interface
* @returns A fetch request promise
*/
async loadLogs(tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> {
const data = logTabStore.getData(tabId);
const { selectedContainer, previous } = data;
const pod = new Pod(data.selectedPod);
const namespace = pod.getNs();
const name = pod.getName();
const result = await podsApi.getLogs({ namespace, name }, {
...params,
timestamps: true, // Always setting timestamp to separate old logs from new ones
container: selectedContainer.name,
previous
});
return result.trimEnd().split("\n");
}
/**
* Converts logs into a string array
* @returns Length of log lines
*/
@computed
get lines(): number {
return this.logs.length;
}
/**
* Returns logs with timestamps for selected tab
*/
@computed
get logs() {
return this.podLogs.get(dockStore.selectedTabId) ?? [];
}
/**
* Removes timestamps from each log line and returns changed logs
* @returns Logs without timestamps
*/
@computed
get logsWithoutTimestamps() {
return this.logs.map(item => this.removeTimestamps(item));
}
/**
* It gets timestamps from all logs then returns last one + 1 second
* (this allows to avoid getting the last stamp in the selection)
* @param tabId
*/
getLastSinceTime(tabId: TabId) {
const logs = this.podLogs.get(tabId);
const timestamps = this.getTimestamps(logs[logs.length - 1]);
const stamp = new Date(timestamps ? timestamps[0] : null);
stamp.setSeconds(stamp.getSeconds() + 1); // avoid duplicates from last second
return stamp.toISOString();
}
splitOutTimestamp(logs: string): [string, string] {
const extraction = /^(\d+\S+)(.*)/m.exec(logs);
if (!extraction || extraction.length < 3) {
return ["", ""];
}
return [extraction[1], extraction[2]];
}
getTimestamps(logs: string) {
return logs.match(/^\d+\S+/gm);
}
removeTimestamps(logs: string) {
return logs.replace(/^\d+.*?\s/gm, "");
}
clearLogs(tabId: TabId) {
this.podLogs.delete(tabId);
}
}
export const logStore = new LogStore();