Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: forward backend logging to electron #2236

Merged
merged 2 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
kittaakos marked this conversation as resolved.
Show resolved Hide resolved
};
}
}

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
Loading