Skip to content

Commit

Permalink
fix: forward backend logging to electron (#2236)
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <[email protected]>
  • Loading branch information
kittaakos authored Sep 26, 2023
1 parent 73ddbef commit ec28623
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 59 deletions.
5 changes: 4 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
{
"label": "Rebuild App",
"type": "shell",
"command": "yarn rebuild:browser && yarn rebuild:electron",
"command": "yarn rebuild",
"group": "build",
"options": {
"cwd": "${workspaceFolder}/electron-app"
},
"presentation": {
"reveal": "always",
"panel": "new",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import { inject, injectable } from '@theia/core/shared/inversify';
import type { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
import { environment } from '@theia/application-package/lib/environment';
import {
app,
BrowserWindow,
contentTracing,
ipcMain,
Event as ElectronEvent,
ipcMain,
} from '@theia/core/electron-shared/electron';
import { fork } from 'node:child_process';
import { AddressInfo } from 'node:net';
import { join, isAbsolute, resolve } from 'node:path';
import { promises as fs, rm, rmSync } from 'node:fs';
import type { MaybePromise, Mutable } from '@theia/core/lib/common/types';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { isOSX } from '@theia/core/lib/common/os';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { isObject, MaybePromise, Mutable } from '@theia/core/lib/common/types';
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
import { environment } from '@theia/application-package/lib/environment';
import {
ElectronMainApplication as TheiaElectronMainApplication,
ElectronMainExecutionParams,
} from '@theia/core/lib/electron-main/electron-main-application';
import { URI } from '@theia/core/shared/vscode-uri';
import { Deferred } from '@theia/core/lib/common/promise-util';
import * as os from '@theia/core/lib/common/os';
import { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
import { IsTempSketch } from '../../node/is-temp-sketch';
import { ErrnoException } from '../../node/utils/errors';
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
import type { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
import { FileUri } from '@theia/core/lib/node/file-uri';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { inject, injectable } from '@theia/core/shared/inversify';
import { URI } from '@theia/core/shared/vscode-uri';
import { log as logToFile, setup as setupFileLog } from 'node-log-rotate';
import { fork } from 'node:child_process';
import { promises as fs, rm, rmSync } from 'node:fs';
import type { AddressInfo } from 'node:net';
import { isAbsolute, join, resolve } from 'node:path';
import { Sketch } from '../../common/protocol';
import {
AppInfo,
Expand All @@ -39,9 +37,71 @@ import {
CHANNEL_SHOW_PLOTTER_WINDOW,
isShowPlotterWindowParams,
} from '../../electron-common/electron-arduino';
import { IsTempSketch } from '../../node/is-temp-sketch';
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
import { ErrnoException } from '../../node/utils/errors';

app.commandLine.appendSwitch('disable-http-cache');

const consoleLogFunctionNames = [
'log',
'trace',
'debug',
'info',
'warn',
'error',
] as const;
type ConsoleLogSeverity = (typeof consoleLogFunctionNames)[number];
interface ConsoleLogParams {
readonly severity: ConsoleLogSeverity;
readonly message: string;
}
function isConsoleLogParams(arg: unknown): arg is ConsoleLogParams {
return (
isObject<ConsoleLogParams>(arg) &&
typeof arg.message === 'string' &&
typeof arg.severity === 'string' &&
consoleLogFunctionNames.includes(arg.severity as ConsoleLogSeverity)
);
}

// Patch for on Linux when `XDG_CONFIG_HOME` is not available, `node-log-rotate` creates the folder with `undefined` name.
// See https://github.com/lemon-sour/node-log-rotate/issues/23 and https://github.com/arduino/arduino-ide/issues/394.
// If the IDE2 is running on Linux, and the `XDG_CONFIG_HOME` variable is not available, set it to avoid the `undefined` folder.
// From the specs: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
// "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used."
function enableFileLogger() {
const os = require('os');
const util = require('util');
if (os.platform() === 'linux' && !process.env['XDG_CONFIG_HOME']) {
const { join } = require('path');
const home = process.env['HOME'];
const xdgConfigHome = home
? join(home, '.config')
: join(os.homedir(), '.config');
process.env['XDG_CONFIG_HOME'] = xdgConfigHome;
}
setupFileLog({
appName: 'Arduino IDE',
maxSize: 10 * 1024 * 1024,
});
for (const name of consoleLogFunctionNames) {
const original = console[name];
console[name] = function () {
// eslint-disable-next-line prefer-rest-params
const messages = Object.values(arguments);
const message = util.format(...messages);
original(message);
logToFile(message);
};
}
}

const isProductionMode = !environment.electron.isDevMode();
if (isProductionMode) {
enableFileLogger();
}

interface WorkspaceOptions {
file: string;
x: number;
Expand Down Expand Up @@ -185,7 +245,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {

private attachFileAssociations(cwd: string): void {
// OSX: register open-file event
if (os.isOSX) {
if (isOSX) {
app.on('open-file', async (event, path) => {
event.preventDefault();
const resolvedPath = await this.resolvePath(path, cwd);
Expand Down Expand Up @@ -495,9 +555,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
);
console.log(`Starting backend process. PID: ${backendProcess.pid}`);
return new Promise((resolve, reject) => {
// The backend server main file is also supposed to send the resolved http(s) server port via IPC.
backendProcess.on('message', (address: AddressInfo) => {
resolve(address.port);
// The forked backend process sends the resolved http(s) server port via IPC, and forwards the log messages.
backendProcess.on('message', (arg: unknown) => {
if (isConsoleLogParams(arg)) {
const { message, severity } = arg;
console[severity](message);
} else if (isAddressInfo(arg)) {
resolve(arg.port);
}
});
backendProcess.on('error', (error) => {
reject(error);
Expand Down Expand Up @@ -703,7 +768,7 @@ class InterruptWorkspaceRestoreError extends Error {
async function updateFrontendApplicationConfigFromPackageJson(
config: FrontendApplicationConfig
): Promise<FrontendApplicationConfig> {
if (environment.electron.isDevMode()) {
if (!isProductionMode) {
console.debug(
'Skipping frontend application configuration customizations. Running in dev mode.'
);
Expand Down Expand Up @@ -777,3 +842,9 @@ function updateAppInfo(
});
return toUpdate;
}

function isAddressInfo(arg: unknown): arg is Pick<AddressInfo, 'port'> {
// Cannot do the type-guard on all properties, but the port is sufficient as the address is always `localhost`.
// For example, the `family` might be absent if the address is IPv6.
return isObject<AddressInfo>(arg) && typeof arg.port === 'number';
}
35 changes: 5 additions & 30 deletions electron-app/arduino-ide-backend-main.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,17 @@
// @ts-check
'use strict';

// Patch for on Linux when `XDG_CONFIG_HOME` is not available, `node-log-rotate` creates the folder with `undefined` name.
// See https://github.com/lemon-sour/node-log-rotate/issues/23 and https://github.com/arduino/arduino-ide/issues/394.
// If the IDE2 is running on Linux, and the `XDG_CONFIG_HOME` variable is not available, set it to avoid the `undefined` folder.
// From the specs: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
// "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used."
function enableFileLogger() {
const os = require('os');
// `true` if the this (backend main) process has been forked.
if (process.send) {
const util = require('util');
if (os.platform() === 'linux' && !process.env['XDG_CONFIG_HOME']) {
const { join } = require('path');
const home = process.env['HOME'];
const xdgConfigHome = home
? join(home, '.config')
: join(os.homedir(), '.config');
process.env['XDG_CONFIG_HOME'] = xdgConfigHome;
}
const { setup, log } = require('node-log-rotate');

setup({
appName: 'Arduino IDE',
maxSize: 10 * 1024 * 1024,
});
for (const name of ['log', 'trace', 'debug', 'info', 'warn', 'error']) {
const original = console[name];
console[name] = function () {
// eslint-disable-next-line prefer-rest-params
const messages = Object.values(arguments);
const message = util.format(...messages);
original(message);
log(message);
const args = Object.values(arguments);
const message = util.format(...args);
process.send?.({ severity: name, message }); // send the log message to the parent process (electron main)
};
}
}

if (process.env.IDE2_FILE_LOGGER === 'true') {
enableFileLogger();
}

require('./src-gen/backend/main');
2 changes: 0 additions & 2 deletions electron-app/arduino-ide-electron-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ if (config.buildDate) {
]
.filter(Boolean)
.join(',');
// Enables the file logger in the backend process.
process.env.IDE2_FILE_LOGGER = 'true';
}

require('./lib/backend/electron-main');
2 changes: 1 addition & 1 deletion electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"prepackage": "rimraf dist",
"package": "node ./scripts/package.js",
"postpackage": "node ./scripts/post-package.js",
"rebuild": "theia rebuild:browser && theia rebuild:electron"
"rebuild": "theia rebuild:browser --cacheRoot ../.. && theia rebuild:electron --cacheRoot ../.."
},
"theia": {
"target": "electron",
Expand Down

0 comments on commit ec28623

Please sign in to comment.