mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-25 10:20:16 -06:00
SDA-4077 (Add support for persisting client logs after reload) (#2025)
* SDA-4077 - Add support for persisting client logs after reload * SDA-4077 - Improve write logic and format * SDA-4077 - Remove duplicate log entries * SDA-4077 - sanitize file name * SDA-4077 - Add /\ to the regex to prevent path traversal * SDA-4077 - Disable nosemgrep * SDA-4077 - Add new api add-logs
This commit is contained in:
parent
f3ca2cfdd9
commit
71e70df12f
@ -32,7 +32,11 @@ import { mainEvents } from './main-event-handler';
|
||||
import { memoryMonitor } from './memory-monitor';
|
||||
import notificationHelper from './notifications/notification-helper';
|
||||
import { protocolHandler } from './protocol-handler';
|
||||
import { finalizeLogExports, registerLogRetriever } from './reports-handler';
|
||||
import {
|
||||
addLogs,
|
||||
finalizeLogExports,
|
||||
registerLogRetriever,
|
||||
} from './reports-handler';
|
||||
import { screenSnippet } from './screen-snippet-handler';
|
||||
import { activate, handleKeyPress } from './window-actions';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
@ -156,6 +160,9 @@ ipcMain.on(
|
||||
case apiCmds.sendLogs:
|
||||
finalizeLogExports(arg.logs);
|
||||
break;
|
||||
case apiCmds.addLogs:
|
||||
addLogs(arg.logs);
|
||||
break;
|
||||
case apiCmds.badgeDataUrl:
|
||||
if (typeof arg.dataUrl === 'string') {
|
||||
if (typeof arg.count === 'number' && arg.count > 0) {
|
||||
@ -674,6 +681,20 @@ const logApiCallParams = (arg: any) => {
|
||||
)}`,
|
||||
);
|
||||
break;
|
||||
case apiCmds.addLogs:
|
||||
const lf = 'hidden';
|
||||
const ld = {
|
||||
...arg.logs,
|
||||
logFiles: lf,
|
||||
};
|
||||
logger.info(
|
||||
`main-api-handler: - ${apiCmd} - Properties: ${JSON.stringify(
|
||||
ld,
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
break;
|
||||
case apiCmds.writeCloud9Pipe:
|
||||
const compressedData = {
|
||||
...arg,
|
||||
|
@ -74,7 +74,6 @@ const generateArchiveForDirectory = (
|
||||
for (const logs of retrievedLogs) {
|
||||
for (const logFile of logs.logFiles) {
|
||||
const file = path.join(source, logFile.filename);
|
||||
fs.writeFileSync(file, logFile.contents);
|
||||
archive.file(file, { name: 'logs/' + logFile.filename });
|
||||
filesForCleanup.push(file);
|
||||
}
|
||||
@ -88,6 +87,24 @@ let logWebContents: WebContents;
|
||||
const logTypes: string[] = [];
|
||||
const receivedLogs: ILogs[] = [];
|
||||
|
||||
const validateFilename = (filename: string): string => {
|
||||
return filename?.replace(/[^a-zA-Z0-9/_\.-]/g, '_');
|
||||
};
|
||||
|
||||
const writeClientLogs = async (retrievedLogs: ILogs[]) => {
|
||||
for await (const logs of retrievedLogs) {
|
||||
for (const logFile of logs.logFiles) {
|
||||
const sanitizedFilename = validateFilename(logFile.filename);
|
||||
if (!sanitizedFilename) {
|
||||
continue;
|
||||
}
|
||||
// nosemgrep
|
||||
const file = path.join(app.getPath('logs'), sanitizedFilename);
|
||||
await writeDataToFile(file, logFile.contents);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const registerLogRetriever = (
|
||||
sender: WebContents,
|
||||
logName: string,
|
||||
@ -97,7 +114,6 @@ export const registerLogRetriever = (
|
||||
};
|
||||
|
||||
export const collectLogs = (): void => {
|
||||
receivedLogs.length = 0;
|
||||
logWebContents.send('collect-logs');
|
||||
};
|
||||
|
||||
@ -107,7 +123,7 @@ export const collectLogs = (): void => {
|
||||
* MacOS - /Library/Logs/Symphony/
|
||||
* Windows - AppData\Roaming\Symphony\logs
|
||||
*/
|
||||
export const packageLogs = (retrievedLogs: ILogs[]): void => {
|
||||
export const packageLogs = async (retrievedLogs: ILogs[]): Promise<void> => {
|
||||
const FILE_EXTENSIONS = ['.log'];
|
||||
const logsPath = app.getPath('logs');
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
@ -129,6 +145,7 @@ export const packageLogs = (retrievedLogs: ILogs[]): void => {
|
||||
const timestamp = new Date().getTime();
|
||||
const destination = app.getPath('downloads') + destPath + timestamp + '.zip';
|
||||
|
||||
await writeClientLogs(retrievedLogs);
|
||||
generateArchiveForDirectory(
|
||||
logsPath,
|
||||
destination,
|
||||
@ -150,6 +167,31 @@ export const packageLogs = (retrievedLogs: ILogs[]): void => {
|
||||
});
|
||||
};
|
||||
|
||||
export const addLogs = (logs: ILogs) => {
|
||||
const existingLogIndex = receivedLogs.findIndex(
|
||||
(receivedLog) => receivedLog.logName === logs.logName,
|
||||
);
|
||||
if (existingLogIndex === -1) {
|
||||
receivedLogs.push(logs);
|
||||
} else {
|
||||
const existingLog = receivedLogs[existingLogIndex];
|
||||
const existingLogFileIndex = existingLog.logFiles.findIndex(
|
||||
(logFile) => logFile.filename === logs.logFiles[0].filename,
|
||||
);
|
||||
|
||||
if (existingLogFileIndex === -1) {
|
||||
existingLog.logFiles.push(logs.logFiles[0]);
|
||||
} else {
|
||||
const logContent = `${existingLog.logFiles[existingLogFileIndex].contents} \n ${logs.logFiles[0].contents}`;
|
||||
const logEntries = logContent.split('\n');
|
||||
const uniqueLogEntries = new Set(logEntries);
|
||||
const filteredLogEntries = [...uniqueLogEntries];
|
||||
existingLog.logFiles[existingLogFileIndex].contents =
|
||||
filteredLogEntries.join('\n');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const finalizeLogExports = (logs: ILogs) => {
|
||||
receivedLogs.push(logs);
|
||||
|
||||
@ -229,3 +271,32 @@ const getCrashesDirectory = (): string => {
|
||||
}
|
||||
return source;
|
||||
};
|
||||
|
||||
/**
|
||||
* Write data in chunk
|
||||
* @param chunk
|
||||
* @param stream
|
||||
*/
|
||||
const writeChunk = (chunk: string[], stream: fs.WriteStream): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dataString = chunk.join('\n');
|
||||
stream.write(dataString + '\n', (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const writeDataToFile = async (filePath: string, data: string) => {
|
||||
const writeStream = fs.createWriteStream(filePath, { encoding: 'utf8' });
|
||||
|
||||
for (let chunkIndex = 0; chunkIndex < data.length; chunkIndex += 1000) {
|
||||
const chunk = data.slice(chunkIndex, chunkIndex + 1000);
|
||||
await writeChunk([chunk], writeStream);
|
||||
}
|
||||
|
||||
writeStream.end();
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ export enum apiCmds {
|
||||
registerProtocolHandler = 'register-protocol-handler',
|
||||
registerLogRetriever = 'register-log-retriever',
|
||||
sendLogs = 'send-logs',
|
||||
addLogs = 'add-logs',
|
||||
registerAnalyticsHandler = 'register-analytics-handler',
|
||||
registerActivityDetection = 'register-activity-detection',
|
||||
showNotificationSettings = 'show-notification-settings',
|
||||
@ -375,6 +376,7 @@ export interface ILogFile {
|
||||
export interface ILogs {
|
||||
logName: string;
|
||||
logFiles: ILogFile[];
|
||||
shouldExportLogs?: boolean;
|
||||
}
|
||||
|
||||
export interface IRestartFloaterData {
|
||||
|
@ -66,6 +66,7 @@ if (ssfWindow.ssf) {
|
||||
registerProtocolHandler: ssfWindow.ssf.registerProtocolHandler,
|
||||
registerLogRetriever: ssfWindow.ssf.registerLogRetriever,
|
||||
sendLogs: ssfWindow.ssf.sendLogs,
|
||||
addLogs: ssfWindow.ssf.addLogs,
|
||||
registerAnalyticsEvent: ssfWindow.ssf.registerAnalyticsEvent,
|
||||
ScreenSnippet: ssfWindow.ssf.ScreenSnippet,
|
||||
openScreenSnippet: ssfWindow.ssf.openScreenSnippet,
|
||||
|
@ -352,6 +352,16 @@ export class SSFApi {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds log into respective data.
|
||||
*/
|
||||
public addLogs(logName: string, logFiles): void {
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.addLogs,
|
||||
logs: { logName, logFiles },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to register analytics event handler
|
||||
* to pass analytics event data
|
||||
|
Loading…
Reference in New Issue
Block a user