Skip to content

Commit ec28623

Browse files
authored
fix: forward backend logging to electron (#2236)
Signed-off-by: Akos Kitta <[email protected]>
1 parent 73ddbef commit ec28623

File tree

5 files changed

+106
-59
lines changed

5 files changed

+106
-59
lines changed

Diff for: .vscode/tasks.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
{
55
"label": "Rebuild App",
66
"type": "shell",
7-
"command": "yarn rebuild:browser && yarn rebuild:electron",
7+
"command": "yarn rebuild",
88
"group": "build",
9+
"options": {
10+
"cwd": "${workspaceFolder}/electron-app"
11+
},
912
"presentation": {
1013
"reveal": "always",
1114
"panel": "new",

Diff for: arduino-ide-extension/src/electron-main/theia/electron-main-application.ts

+96-25
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
1-
import { inject, injectable } from '@theia/core/shared/inversify';
1+
import type { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
2+
import { environment } from '@theia/application-package/lib/environment';
23
import {
34
app,
45
BrowserWindow,
56
contentTracing,
6-
ipcMain,
77
Event as ElectronEvent,
8+
ipcMain,
89
} from '@theia/core/electron-shared/electron';
9-
import { fork } from 'node:child_process';
10-
import { AddressInfo } from 'node:net';
11-
import { join, isAbsolute, resolve } from 'node:path';
12-
import { promises as fs, rm, rmSync } from 'node:fs';
13-
import type { MaybePromise, Mutable } from '@theia/core/lib/common/types';
10+
import {
11+
Disposable,
12+
DisposableCollection,
13+
} from '@theia/core/lib/common/disposable';
14+
import { isOSX } from '@theia/core/lib/common/os';
15+
import { Deferred } from '@theia/core/lib/common/promise-util';
16+
import { isObject, MaybePromise, Mutable } from '@theia/core/lib/common/types';
1417
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
15-
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
16-
import { environment } from '@theia/application-package/lib/environment';
1718
import {
1819
ElectronMainApplication as TheiaElectronMainApplication,
1920
ElectronMainExecutionParams,
2021
} from '@theia/core/lib/electron-main/electron-main-application';
21-
import { URI } from '@theia/core/shared/vscode-uri';
22-
import { Deferred } from '@theia/core/lib/common/promise-util';
23-
import * as os from '@theia/core/lib/common/os';
24-
import { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
25-
import { IsTempSketch } from '../../node/is-temp-sketch';
26-
import { ErrnoException } from '../../node/utils/errors';
27-
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
22+
import type { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
2823
import { FileUri } from '@theia/core/lib/node/file-uri';
29-
import {
30-
Disposable,
31-
DisposableCollection,
32-
} from '@theia/core/lib/common/disposable';
24+
import { inject, injectable } from '@theia/core/shared/inversify';
25+
import { URI } from '@theia/core/shared/vscode-uri';
26+
import { log as logToFile, setup as setupFileLog } from 'node-log-rotate';
27+
import { fork } from 'node:child_process';
28+
import { promises as fs, rm, rmSync } from 'node:fs';
29+
import type { AddressInfo } from 'node:net';
30+
import { isAbsolute, join, resolve } from 'node:path';
3331
import { Sketch } from '../../common/protocol';
3432
import {
3533
AppInfo,
@@ -39,9 +37,71 @@ import {
3937
CHANNEL_SHOW_PLOTTER_WINDOW,
4038
isShowPlotterWindowParams,
4139
} from '../../electron-common/electron-arduino';
40+
import { IsTempSketch } from '../../node/is-temp-sketch';
41+
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
42+
import { ErrnoException } from '../../node/utils/errors';
4243

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

46+
const consoleLogFunctionNames = [
47+
'log',
48+
'trace',
49+
'debug',
50+
'info',
51+
'warn',
52+
'error',
53+
] as const;
54+
type ConsoleLogSeverity = (typeof consoleLogFunctionNames)[number];
55+
interface ConsoleLogParams {
56+
readonly severity: ConsoleLogSeverity;
57+
readonly message: string;
58+
}
59+
function isConsoleLogParams(arg: unknown): arg is ConsoleLogParams {
60+
return (
61+
isObject<ConsoleLogParams>(arg) &&
62+
typeof arg.message === 'string' &&
63+
typeof arg.severity === 'string' &&
64+
consoleLogFunctionNames.includes(arg.severity as ConsoleLogSeverity)
65+
);
66+
}
67+
68+
// Patch for on Linux when `XDG_CONFIG_HOME` is not available, `node-log-rotate` creates the folder with `undefined` name.
69+
// See https://github.com/lemon-sour/node-log-rotate/issues/23 and https://github.com/arduino/arduino-ide/issues/394.
70+
// If the IDE2 is running on Linux, and the `XDG_CONFIG_HOME` variable is not available, set it to avoid the `undefined` folder.
71+
// From the specs: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
72+
// "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used."
73+
function enableFileLogger() {
74+
const os = require('os');
75+
const util = require('util');
76+
if (os.platform() === 'linux' && !process.env['XDG_CONFIG_HOME']) {
77+
const { join } = require('path');
78+
const home = process.env['HOME'];
79+
const xdgConfigHome = home
80+
? join(home, '.config')
81+
: join(os.homedir(), '.config');
82+
process.env['XDG_CONFIG_HOME'] = xdgConfigHome;
83+
}
84+
setupFileLog({
85+
appName: 'Arduino IDE',
86+
maxSize: 10 * 1024 * 1024,
87+
});
88+
for (const name of consoleLogFunctionNames) {
89+
const original = console[name];
90+
console[name] = function () {
91+
// eslint-disable-next-line prefer-rest-params
92+
const messages = Object.values(arguments);
93+
const message = util.format(...messages);
94+
original(message);
95+
logToFile(message);
96+
};
97+
}
98+
}
99+
100+
const isProductionMode = !environment.electron.isDevMode();
101+
if (isProductionMode) {
102+
enableFileLogger();
103+
}
104+
45105
interface WorkspaceOptions {
46106
file: string;
47107
x: number;
@@ -185,7 +245,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
185245

186246
private attachFileAssociations(cwd: string): void {
187247
// OSX: register open-file event
188-
if (os.isOSX) {
248+
if (isOSX) {
189249
app.on('open-file', async (event, path) => {
190250
event.preventDefault();
191251
const resolvedPath = await this.resolvePath(path, cwd);
@@ -495,9 +555,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
495555
);
496556
console.log(`Starting backend process. PID: ${backendProcess.pid}`);
497557
return new Promise((resolve, reject) => {
498-
// The backend server main file is also supposed to send the resolved http(s) server port via IPC.
499-
backendProcess.on('message', (address: AddressInfo) => {
500-
resolve(address.port);
558+
// The forked backend process sends the resolved http(s) server port via IPC, and forwards the log messages.
559+
backendProcess.on('message', (arg: unknown) => {
560+
if (isConsoleLogParams(arg)) {
561+
const { message, severity } = arg;
562+
console[severity](message);
563+
} else if (isAddressInfo(arg)) {
564+
resolve(arg.port);
565+
}
501566
});
502567
backendProcess.on('error', (error) => {
503568
reject(error);
@@ -703,7 +768,7 @@ class InterruptWorkspaceRestoreError extends Error {
703768
async function updateFrontendApplicationConfigFromPackageJson(
704769
config: FrontendApplicationConfig
705770
): Promise<FrontendApplicationConfig> {
706-
if (environment.electron.isDevMode()) {
771+
if (!isProductionMode) {
707772
console.debug(
708773
'Skipping frontend application configuration customizations. Running in dev mode.'
709774
);
@@ -777,3 +842,9 @@ function updateAppInfo(
777842
});
778843
return toUpdate;
779844
}
845+
846+
function isAddressInfo(arg: unknown): arg is Pick<AddressInfo, 'port'> {
847+
// Cannot do the type-guard on all properties, but the port is sufficient as the address is always `localhost`.
848+
// For example, the `family` might be absent if the address is IPv6.
849+
return isObject<AddressInfo>(arg) && typeof arg.port === 'number';
850+
}

Diff for: electron-app/arduino-ide-backend-main.js

+5-30
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,17 @@
11
// @ts-check
22
'use strict';
33

4-
// Patch for on Linux when `XDG_CONFIG_HOME` is not available, `node-log-rotate` creates the folder with `undefined` name.
5-
// See https://github.com/lemon-sour/node-log-rotate/issues/23 and https://github.com/arduino/arduino-ide/issues/394.
6-
// If the IDE2 is running on Linux, and the `XDG_CONFIG_HOME` variable is not available, set it to avoid the `undefined` folder.
7-
// From the specs: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
8-
// "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used."
9-
function enableFileLogger() {
10-
const os = require('os');
4+
// `true` if the this (backend main) process has been forked.
5+
if (process.send) {
116
const util = require('util');
12-
if (os.platform() === 'linux' && !process.env['XDG_CONFIG_HOME']) {
13-
const { join } = require('path');
14-
const home = process.env['HOME'];
15-
const xdgConfigHome = home
16-
? join(home, '.config')
17-
: join(os.homedir(), '.config');
18-
process.env['XDG_CONFIG_HOME'] = xdgConfigHome;
19-
}
20-
const { setup, log } = require('node-log-rotate');
21-
22-
setup({
23-
appName: 'Arduino IDE',
24-
maxSize: 10 * 1024 * 1024,
25-
});
267
for (const name of ['log', 'trace', 'debug', 'info', 'warn', 'error']) {
27-
const original = console[name];
288
console[name] = function () {
299
// eslint-disable-next-line prefer-rest-params
30-
const messages = Object.values(arguments);
31-
const message = util.format(...messages);
32-
original(message);
33-
log(message);
10+
const args = Object.values(arguments);
11+
const message = util.format(...args);
12+
process.send?.({ severity: name, message }); // send the log message to the parent process (electron main)
3413
};
3514
}
3615
}
3716

38-
if (process.env.IDE2_FILE_LOGGER === 'true') {
39-
enableFileLogger();
40-
}
41-
4217
require('./src-gen/backend/main');

Diff for: electron-app/arduino-ide-electron-main.js

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ if (config.buildDate) {
1818
]
1919
.filter(Boolean)
2020
.join(',');
21-
// Enables the file logger in the backend process.
22-
process.env.IDE2_FILE_LOGGER = 'true';
2321
}
2422

2523
require('./lib/backend/electron-main');

Diff for: electron-app/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"prepackage": "rimraf dist",
5454
"package": "node ./scripts/package.js",
5555
"postpackage": "node ./scripts/post-package.js",
56-
"rebuild": "theia rebuild:browser && theia rebuild:electron"
56+
"rebuild": "theia rebuild:browser --cacheRoot ../.. && theia rebuild:electron --cacheRoot ../.."
5757
},
5858
"theia": {
5959
"target": "electron",

0 commit comments

Comments
 (0)