1- import { inject , injectable } from '@theia/core/shared/inversify' ;
1+ import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props' ;
2+ import { environment } from '@theia/application-package/lib/environment' ;
23import {
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' ;
10+ import {
11+ Disposable ,
12+ DisposableCollection ,
13+ } from '@theia/core/lib/common/disposable' ;
14+ import { Emitter , Event } from '@theia/core/lib/common/event' ;
15+ import { isOSX } from '@theia/core/lib/common/os' ;
16+ import { Deferred } from '@theia/core/lib/common/promise-util' ;
1317import type { MaybePromise , Mutable } from '@theia/core/lib/common/types' ;
1418import { 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' ;
1719import {
1820 ElectronMainApplication as TheiaElectronMainApplication ,
1921 ElectronMainExecutionParams ,
2022} 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' ;
2423import { 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' ;
2824import { FileUri } from '@theia/core/lib/node/file-uri' ;
29- import {
30- Disposable ,
31- DisposableCollection ,
32- } from '@theia/core/lib/common/disposable' ;
25+ import { inject , injectable } from '@theia/core/shared/inversify' ;
26+ import { URI } from '@theia/core/shared/vscode-uri' ;
27+ import type { ChildProcess } from 'node:child_process' ;
28+ import { fork } from 'node:child_process' ;
29+ import { promises as fs , rm , rmSync } from 'node:fs' ;
30+ import type { AddressInfo } from 'node:net' ;
31+ import { isAbsolute , join , resolve } from 'node:path' ;
32+ import { format as formatLog } from 'node:util' ;
3333import { Sketch } from '../../common/protocol' ;
3434import {
3535 AppInfo ,
@@ -39,6 +39,54 @@ import {
3939 CHANNEL_SHOW_PLOTTER_WINDOW ,
4040 isShowPlotterWindowParams ,
4141} from '../../electron-common/electron-arduino' ;
42+ import { IsTempSketch } from '../../node/is-temp-sketch' ;
43+ import { isAccessibleSketchPath } from '../../node/sketches-service-impl' ;
44+ import { ErrnoException } from '../../node/utils/errors' ;
45+
46+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47+ type ConsoleLogFunction = ( message ?: any , ...optionalParams : any [ ] ) => void ;
48+ const consoleLogFunctionNames = [
49+ 'log' ,
50+ 'trace' ,
51+ 'debug' ,
52+ 'info' ,
53+ 'warn' ,
54+ 'error' ,
55+ ] as const ;
56+ type LogSeverity = ( typeof consoleLogFunctionNames ) [ number ] ;
57+ interface LogMessage {
58+ readonly severity : LogSeverity ;
59+ readonly message : string ;
60+ }
61+
62+ function forwardLog ( onDidFork : Event < ChildProcess > ) : void {
63+ if ( environment . electron . isDevMode ( ) ) {
64+ return ;
65+ }
66+ const buffer : LogMessage [ ] = [ ] ;
67+ let backendProcess : ChildProcess | undefined = undefined ;
68+ for ( const name of consoleLogFunctionNames ) {
69+ const original = < ConsoleLogFunction > console [ name as keyof Console ] ;
70+ console [ name ] = function ( ) {
71+ // eslint-disable-next-line prefer-rest-params
72+ const messages = Object . values ( arguments ) ;
73+ const message = formatLog ( ...messages ) ;
74+ const logMessage : LogMessage = { severity : name , message } ;
75+ if ( backendProcess ) {
76+ backendProcess . send ( logMessage ) ;
77+ } else {
78+ buffer . push ( logMessage ) ;
79+ }
80+ original ( message ) ;
81+ } ;
82+ }
83+ onDidFork ( ( cp ) => {
84+ backendProcess = cp ;
85+ buffer . forEach ( ( logMessage ) => cp . send ( logMessage ) ) ;
86+ } ) ;
87+ }
88+ const onDidForkEmitter = new Emitter < ChildProcess > ( ) ;
89+ forwardLog ( onDidForkEmitter . event ) ;
4290
4391app . commandLine . appendSwitch ( 'disable-http-cache' ) ;
4492
@@ -185,7 +233,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
185233
186234 private attachFileAssociations ( cwd : string ) : void {
187235 // OSX: register open-file event
188- if ( os . isOSX ) {
236+ if ( isOSX ) {
189237 app . on ( 'open-file' , async ( event , path ) => {
190238 event . preventDefault ( ) ;
191239 const resolvedPath = await this . resolvePath ( path , cwd ) ;
@@ -493,6 +541,9 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
493541 args ,
494542 await this . getForkOptions ( )
495543 ) ;
544+ // In production, the electron main's `console` must be rebind, and delegate all log messages to the Theia backend via IPC.
545+ // Otherwise, any error that happens in this process won't be in the log files.
546+ onDidForkEmitter . fire ( backendProcess ) ;
496547 console . log ( `Starting backend process. PID: ${ backendProcess . pid } ` ) ;
497548 return new Promise ( ( resolve , reject ) => {
498549 // The backend server main file is also supposed to send the resolved http(s) server port via IPC.
0 commit comments