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' ;
2
3
import {
3
4
app ,
4
5
BrowserWindow ,
5
6
contentTracing ,
6
- ipcMain ,
7
7
Event as ElectronEvent ,
8
+ ipcMain ,
8
9
} 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' ;
13
17
import type { MaybePromise , Mutable } from '@theia/core/lib/common/types' ;
14
18
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' ;
17
19
import {
18
20
ElectronMainApplication as TheiaElectronMainApplication ,
19
21
ElectronMainExecutionParams ,
20
22
} 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' ;
23
+ import type { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window' ;
28
24
import { 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' ;
33
33
import { Sketch } from '../../common/protocol' ;
34
34
import {
35
35
AppInfo ,
@@ -39,6 +39,54 @@ import {
39
39
CHANNEL_SHOW_PLOTTER_WINDOW ,
40
40
isShowPlotterWindowParams ,
41
41
} 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
+ const consoleLogFunctionNames = [
47
+ 'log' ,
48
+ 'trace' ,
49
+ 'debug' ,
50
+ 'info' ,
51
+ 'warn' ,
52
+ 'error' ,
53
+ ] as const ;
54
+ type LogSeverity = ( typeof consoleLogFunctionNames ) [ number ] ;
55
+ interface LogMessage {
56
+ readonly severity : LogSeverity ;
57
+ readonly message : string ;
58
+ }
59
+
60
+ function forwardLog ( onDidFork : Event < ChildProcess > ) : void {
61
+ if ( environment . electron . isDevMode ( ) ) {
62
+ return ;
63
+ }
64
+ const buffer : LogMessage [ ] = [ ] ;
65
+ let backendProcess : ChildProcess | undefined = undefined ;
66
+ for ( const name of consoleLogFunctionNames ) {
67
+ console [ name ] = function ( ) {
68
+ // eslint-disable-next-line prefer-rest-params
69
+ const messages = Object . values ( arguments ) ;
70
+ const message = formatLog ( ...messages ) ;
71
+ const logMessage : LogMessage = { severity : name , message } ;
72
+ if ( backendProcess ) {
73
+ backendProcess . send ( logMessage ) ;
74
+ } else {
75
+ buffer . push ( logMessage ) ;
76
+ }
77
+ } ;
78
+ }
79
+ onDidFork ( ( cp ) => {
80
+ backendProcess = cp ;
81
+ let logMessage = buffer . shift ( ) ;
82
+ while ( logMessage ) {
83
+ cp . send ( logMessage ) ;
84
+ logMessage = buffer . shift ( ) ;
85
+ }
86
+ } ) ;
87
+ }
88
+ const onDidForkEmitter = new Emitter < ChildProcess > ( ) ;
89
+ forwardLog ( onDidForkEmitter . event ) ;
42
90
43
91
app . commandLine . appendSwitch ( 'disable-http-cache' ) ;
44
92
@@ -185,7 +233,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
185
233
186
234
private attachFileAssociations ( cwd : string ) : void {
187
235
// OSX: register open-file event
188
- if ( os . isOSX ) {
236
+ if ( isOSX ) {
189
237
app . on ( 'open-file' , async ( event , path ) => {
190
238
event . preventDefault ( ) ;
191
239
const resolvedPath = await this . resolvePath ( path , cwd ) ;
@@ -493,6 +541,9 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
493
541
args ,
494
542
await this . getForkOptions ( )
495
543
) ;
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 ) ;
496
547
console . log ( `Starting backend process. PID: ${ backendProcess . pid } ` ) ;
497
548
return new Promise ( ( resolve , reject ) => {
498
549
// The backend server main file is also supposed to send the resolved http(s) server port via IPC.
0 commit comments