mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Merge pull request #817 from mattias-symphony/SDA-945_Unified_Logs_2
feat: Implemented unified log extraction
This commit is contained in:
@@ -8,6 +8,7 @@ import { analytics } from './analytics-handler';
|
||||
import { config } from './config-handler';
|
||||
import { memoryMonitor } from './memory-monitor';
|
||||
import { protocolHandler } from './protocol-handler';
|
||||
import { finalizeLogExports, registerLogRetriever } from './reports-handler';
|
||||
import { screenSnippet } from './screen-snippet-handler';
|
||||
import { activate, handleKeyPress } from './window-actions';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
@@ -53,6 +54,12 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.IpcMainEvent, arg: IApiArgs) =>
|
||||
// we make use of it and update the pod version info on SDA
|
||||
windowHandler.updateVersionInfo();
|
||||
break;
|
||||
case apiCmds.registerLogRetriever:
|
||||
registerLogRetriever(event.sender, arg.logName);
|
||||
break;
|
||||
case apiCmds.sendLogs:
|
||||
finalizeLogExports(arg.logs);
|
||||
break;
|
||||
case apiCmds.badgeDataUrl:
|
||||
if (typeof arg.dataUrl === 'string' && typeof arg.count === 'number') {
|
||||
setDataUrl(arg.dataUrl, arg.count);
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as electron from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ILogs } from '../common/api-interface';
|
||||
import { isLinux, isMac } from '../common/env';
|
||||
import { i18n } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
@@ -17,19 +18,30 @@ import { logger } from '../common/logger';
|
||||
* @param fileExtensions {Array} array of file ext
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
const generateArchiveForDirectory = (source: string, destination: string, fileExtensions: string[]): Promise<void> => {
|
||||
const generateArchiveForDirectory = (source: string, destination: string, fileExtensions: string[], retrievedLogs: ILogs[]): Promise<void> => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info(`reports-handler: generating archive for directory ${source}`);
|
||||
const output = fs.createWriteStream(destination);
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
const filesForCleanup: string[] = [];
|
||||
|
||||
output.on('close', () => {
|
||||
for (const file of filesForCleanup) {
|
||||
if (fs.existsSync(file)) {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
}
|
||||
logger.info(`reports-handler: generated archive for directory ${source}`);
|
||||
return resolve();
|
||||
});
|
||||
|
||||
archive.on('error', (err: Error) => {
|
||||
for (const file of filesForCleanup) {
|
||||
if (fs.existsSync(file)) {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
}
|
||||
logger.error(`reports-handler: error archiving directory for ${source} with error ${err}`);
|
||||
return reject(err);
|
||||
});
|
||||
@@ -53,17 +65,40 @@ const generateArchiveForDirectory = (source: string, destination: string, fileEx
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
archive.finalize();
|
||||
});
|
||||
};
|
||||
|
||||
let logWebContents: Electron.WebContents;
|
||||
const logTypes: string[] = [];
|
||||
const receivedLogs: ILogs[] = [];
|
||||
|
||||
export const registerLogRetriever = (sender: Electron.WebContents, logName: string): void => {
|
||||
logWebContents = sender;
|
||||
logTypes.push( logName );
|
||||
};
|
||||
|
||||
export const collectLogs = (): void => {
|
||||
receivedLogs.length = 0;
|
||||
logWebContents.send('collect-logs' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Compress and export logs stored under system log directory
|
||||
*
|
||||
* MacOS - /Library/Logs/Symphony/
|
||||
* Windows - AppData\Roaming\Symphony\logs
|
||||
*/
|
||||
export const exportLogs = (): void => {
|
||||
export const packageLogs = (retrievedLogs: ILogs[]): void => {
|
||||
const FILE_EXTENSIONS = [ '.log' ];
|
||||
const MAC_LOGS_PATH = '/Library/Logs/Symphony/';
|
||||
const LINUX_LOGS_PATH = '/.config/Symphony/';
|
||||
@@ -86,7 +121,7 @@ export const exportLogs = (): void => {
|
||||
const timestamp = new Date().getTime();
|
||||
const destination = app.getPath('downloads') + destPath + timestamp + '.zip';
|
||||
|
||||
generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
|
||||
generateArchiveForDirectory(source, destination, FILE_EXTENSIONS, retrievedLogs)
|
||||
.then(() => {
|
||||
shell.showItemInFolder(destination);
|
||||
})
|
||||
@@ -102,6 +137,31 @@ export const exportLogs = (): void => {
|
||||
});
|
||||
};
|
||||
|
||||
export const finalizeLogExports = (logs: ILogs) => {
|
||||
receivedLogs.push(logs);
|
||||
|
||||
let allReceived = true;
|
||||
for (const logType of logTypes) {
|
||||
const found = receivedLogs.some((log) => log.logName === logType);
|
||||
if (!found) {
|
||||
allReceived = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allReceived) {
|
||||
packageLogs(receivedLogs);
|
||||
receivedLogs.length = 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const exportLogs = (): void => {
|
||||
if (logTypes.length > 0) {
|
||||
collectLogs();
|
||||
} else {
|
||||
packageLogs([]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compress and export crash dump stored under system crashes directory
|
||||
*/
|
||||
@@ -125,7 +185,7 @@ export const exportCrashDumps = (): void => {
|
||||
|
||||
const destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
|
||||
|
||||
generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
|
||||
generateArchiveForDirectory(source, destination, FILE_EXTENSIONS, [])
|
||||
.then(() => {
|
||||
electron.shell.showItemInFolder(destination);
|
||||
})
|
||||
|
||||
@@ -7,6 +7,8 @@ export enum apiCmds {
|
||||
activate = 'activate',
|
||||
registerBoundsChange = 'register-bounds-change',
|
||||
registerProtocolHandler = 'register-protocol-handler',
|
||||
registerLogRetriever = 'register-log-retriever',
|
||||
sendLogs = 'send-logs',
|
||||
registerAnalyticsHandler = 'register-analytics-handler',
|
||||
registerActivityDetection = 'register-activity-detection',
|
||||
showNotificationSettings = 'show-notification-settings',
|
||||
@@ -61,6 +63,8 @@ export interface IApiArgs {
|
||||
displayId: string;
|
||||
path: string;
|
||||
type: string;
|
||||
logName: string;
|
||||
logs: ILogs;
|
||||
}
|
||||
|
||||
export type WindowTypes = 'screen-picker' | 'screen-sharing-indicator' | 'notification-settings';
|
||||
@@ -144,3 +148,13 @@ export interface ILogMsg {
|
||||
}
|
||||
|
||||
export type LogLevel = 'error' | 'warn' | 'info' | 'verbose' | 'debug' | 'silly';
|
||||
|
||||
export interface ILogFile {
|
||||
filename: string;
|
||||
contents: string;
|
||||
}
|
||||
|
||||
export interface ILogs {
|
||||
logName: string;
|
||||
logFiles: ILogFile[];
|
||||
}
|
||||
|
||||
@@ -125,6 +125,16 @@
|
||||
<p>snippet output:</p>
|
||||
<image id='snippet-img'/>
|
||||
|
||||
<hr>
|
||||
<p>Logs:</p>
|
||||
Filename <input type='text' id='log-filename' value='test_log.log'/>
|
||||
<p>Contents</p>
|
||||
<textarea id="log-contents" rows="4">Test log contents</textarea>
|
||||
<p/>
|
||||
Filename <input type='text' id='log-filename2' value=''/>
|
||||
<p>Contents</p>
|
||||
<textarea id="log-contents2" rows="4"></textarea>
|
||||
|
||||
<hr>
|
||||
<p>Window activate:</p>
|
||||
<button id='open-win'>open window</button>
|
||||
@@ -175,6 +185,8 @@
|
||||
activate: 'activate',
|
||||
registerBoundsChange: 'register-bounds-change',
|
||||
registerProtocolHandler: 'register-protocol-handler',
|
||||
registerLogRetriever: 'register-log-retriever',
|
||||
sendLogs: 'send-logs',
|
||||
registerAnalyticHandler: 'register-analytic-handler',
|
||||
registerActivityDetection: 'register-activity-detection',
|
||||
showNotificationSettings: 'show-notification-settings',
|
||||
@@ -428,9 +440,33 @@
|
||||
window.postMessage({ method, data }, '*');
|
||||
};
|
||||
|
||||
const getTestLogs = () => {
|
||||
if (document.getElementById('log-filename2').value == '') {
|
||||
const filename = document.getElementById('log-filename').value;
|
||||
const contents = document.getElementById('log-contents').value;
|
||||
return [ { filename, contents }, ];
|
||||
} else {
|
||||
const filename1 = document.getElementById('log-filename').value;
|
||||
const contents1 = document.getElementById('log-contents').value;
|
||||
const filename2 = document.getElementById('log-filename2').value;
|
||||
const contents2 = document.getElementById('log-contents2').value;
|
||||
return [ { filename: filename1, contents: contents1 },
|
||||
{ filename: filename2, contents: contents2 }, ];
|
||||
}
|
||||
}
|
||||
|
||||
const logTypeTestLogs = 'TestLogs';
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const { data, method } = event.data;
|
||||
switch (method) {
|
||||
case 'collect-logs':
|
||||
if (window.ssf) {
|
||||
window.ssf.sendLogs(logTypeTestLogs, getTestLogs());
|
||||
} else {
|
||||
postMessage( apiCmds.sendLogs, { logName: logTypeTestLogs, logFiles: getTestLogs() } );
|
||||
}
|
||||
break;
|
||||
case 'activity-callback':
|
||||
activityCallback(data);
|
||||
break;
|
||||
@@ -457,6 +493,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Log retriever
|
||||
*/
|
||||
if (window.ssf) {
|
||||
window.ssf.registerLogRetriever(logTypeTestLogs);
|
||||
} else {
|
||||
postMessage(apiCmds.registerLogRetriever, logTypeTestLogs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analytic events
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,7 @@ export class AppBridge {
|
||||
onRegisterLoggerCallback: (msg: ILogMsg, logLevel: LogLevel, showInConsole: boolean) =>
|
||||
this.registerLoggerCallback(msg, logLevel, showInConsole),
|
||||
onRegisterProtocolHandlerCallback: (uri: string) => this.protocolHandlerCallback(uri),
|
||||
onCollectLogsCallback: () => this.collectLogsCallback(),
|
||||
onScreenSharingIndicatorCallback: (arg: IScreenSharingIndicator) => this.screenSharingIndicatorCallback(arg),
|
||||
onMediaSourceCallback: (
|
||||
error: IScreenSourceError | null,
|
||||
@@ -123,6 +124,12 @@ export class AppBridge {
|
||||
case apiCmds.registerProtocolHandler:
|
||||
ssf.registerProtocolHandler(this.callbackHandlers.onRegisterProtocolHandlerCallback);
|
||||
break;
|
||||
case apiCmds.registerLogRetriever:
|
||||
ssf.registerLogRetriever(this.callbackHandlers.onCollectLogsCallback, data);
|
||||
break;
|
||||
case apiCmds.sendLogs:
|
||||
ssf.sendLogs(data.logName, data.logFiles);
|
||||
break;
|
||||
case apiCmds.openScreenSharingIndicator:
|
||||
ssf.openScreenSharingIndicator(data as IScreenSharingIndicatorOptions, this.callbackHandlers.onScreenSharingIndicatorCallback);
|
||||
break;
|
||||
@@ -191,6 +198,8 @@ export class AppBridge {
|
||||
*/
|
||||
private protocolHandlerCallback = (uri: string): void => this.broadcastMessage('protocol-callback', uri);
|
||||
|
||||
private collectLogsCallback = (): void => this.broadcastMessage('collect-logs', undefined);
|
||||
|
||||
/**
|
||||
* Broadcast event that stops screen sharing
|
||||
* @param arg {IScreenSharingIndicator}
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface ILocalObject {
|
||||
boundsChangeCallback?: (arg: IBoundsChange) => void;
|
||||
screenSharingIndicatorCallback?: (arg: IScreenSharingIndicator) => void;
|
||||
protocolActionCallback?: (arg: string) => void;
|
||||
collectLogsCallback?: Array<( () => void )>;
|
||||
analyticsEventHandler?: (arg: any) => void;
|
||||
}
|
||||
|
||||
@@ -263,6 +264,35 @@ export class SSFApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to register a log retriever that can be used by the
|
||||
* electron main process to retrieve current logs.
|
||||
*/
|
||||
public registerLogRetriever(collectLogs: () => void, logName: string): void {
|
||||
if (typeof collectLogs === 'function') {
|
||||
if (!local.collectLogsCallback) {
|
||||
local.collectLogsCallback = new Array<( () => void )>();
|
||||
}
|
||||
local.collectLogsCallback.push(collectLogs);
|
||||
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.registerLogRetriever,
|
||||
logName,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send log files to main process when requested.
|
||||
*/
|
||||
public sendLogs(logName: string, logFiles): void {
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.sendLogs,
|
||||
logs: { logName, logFiles },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to register analytics event handler
|
||||
* to pass analytics event data
|
||||
@@ -490,6 +520,14 @@ local.ipcRenderer.on('screen-snippet-data', (_event: Event, arg: IScreenSnippet)
|
||||
}
|
||||
});
|
||||
|
||||
local.ipcRenderer.on('collect-logs', ( _event: Event ) => {
|
||||
if (local.collectLogsCallback) {
|
||||
for (const callback of local.collectLogsCallback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* An event triggered by the main process
|
||||
* for ever few minutes if the user is active
|
||||
|
||||
Reference in New Issue
Block a user