Skip to content

Commit

Permalink
[v11] Log shared process stdout and stderr (#1046) (#1336)
Browse files Browse the repository at this point in the history
  • Loading branch information
gzdunek committed Nov 8, 2022
1 parent 0347a6e commit c3f557a
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 40 deletions.
5 changes: 3 additions & 2 deletions web/packages/teleterm/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { app, globalShortcut, shell } from 'electron';
import MainProcess from 'teleterm/mainProcess';
import { getRuntimeSettings } from 'teleterm/mainProcess/runtimeSettings';
import { enableWebHandlersProtection } from 'teleterm/mainProcess/protocolHandler';
import createLoggerService from 'teleterm/services/logger';
import { LoggerColor, createFileLoggerService } from 'teleterm/services/logger';
import Logger from 'teleterm/logger';
import * as types from 'teleterm/types';
import { ConfigServiceImpl } from 'teleterm/services/config';
Expand Down Expand Up @@ -145,10 +145,11 @@ function initializeApp(): void {
}

function initMainLogger(settings: types.RuntimeSettings) {
const service = createLoggerService({
const service = createFileLoggerService({
dev: settings.dev,
dir: settings.userDataDir,
name: 'main',
loggerNameColor: LoggerColor.Magenta,
});

Logger.init(service);
Expand Down
31 changes: 23 additions & 8 deletions web/packages/teleterm/src/mainProcess/mainProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import fs from 'fs/promises';

import {
app,
ipcMain,
shell,
dialog,
ipcMain,
Menu,
MenuItemConstructorOptions,
shell,
} from 'electron';

import { FileStorage, Logger, RuntimeSettings } from 'teleterm/types';
import { subscribeToFileStorageEvents } from 'teleterm/services/fileStorage';
import createLoggerService from 'teleterm/services/logger';
import { LoggerColor, createFileLoggerService } from 'teleterm/services/logger';
import { ChildProcessAddresses } from 'teleterm/mainProcess/types';
import { getAssetPath } from 'teleterm/mainProcess/runtimeSettings';

Expand Down Expand Up @@ -80,23 +80,24 @@ export default class MainProcess {
this.logger.info(`Starting tsh daemon from ${binaryPath}`);

this.tshdProcess = spawn(binaryPath, flags, {
stdio: 'pipe',
stdio: 'pipe', // stdio must be set to `pipe` as the gRPC server address is read from stdout
windowsHide: true,
env: {
...process.env,
TELEPORT_HOME: homeDir,
},
});

const tshdLogger = createLoggerService({
const tshdPassThroughLogger = createFileLoggerService({
dev: this.settings.dev,
dir: this.settings.userDataDir,
name: 'tshd',
loggerNameColor: LoggerColor.Cyan,
passThroughMode: true,
});

tshdLogger.pipeProcessOutputIntoLogger(this.tshdProcess.stdout);
tshdLogger.pipeProcessOutputIntoLogger(this.tshdProcess.stderr);
tshdPassThroughLogger.pipeProcessOutputIntoLogger(this.tshdProcess.stdout);
tshdPassThroughLogger.pipeProcessOutputIntoLogger(this.tshdProcess.stderr);

this.tshdProcess.on('error', error => {
this.logger.error('tshd failed to start', error);
Expand All @@ -112,9 +113,23 @@ export default class MainProcess {
path.join(__dirname, 'sharedProcess.js'),
[`--runtimeSettingsJson=${JSON.stringify(this.settings)}`],
{
stdio: 'pipe',
stdio: 'pipe', // stdio must be set to `pipe` as the gRPC server address is read from stdout
}
);
const sharedProcessPassThroughLogger = createFileLoggerService({
dev: this.settings.dev,
dir: this.settings.userDataDir,
name: 'shared',
loggerNameColor: LoggerColor.Yellow,
passThroughMode: true,
});

sharedProcessPassThroughLogger.pipeProcessOutputIntoLogger(
this.sharedProcess.stdout
);
sharedProcessPassThroughLogger.pipeProcessOutputIntoLogger(
this.sharedProcess.stderr
);

this.sharedProcess.on('error', error => {
this.logger.error('shared process failed to start', error);
Expand Down
4 changes: 2 additions & 2 deletions web/packages/teleterm/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ChannelCredentials } from '@grpc/grpc-js';

import createTshClient from 'teleterm/services/tshd/createClient';
import createMainProcessClient from 'teleterm/mainProcess/mainProcessClient';
import createLoggerService from 'teleterm/services/logger';
import { createFileLoggerService } from 'teleterm/services/logger';
import Logger from 'teleterm/logger';
import { createPtyService } from 'teleterm/services/pty/ptyService';
import {
Expand All @@ -18,7 +18,7 @@ import { ElectronGlobals, RuntimeSettings } from 'teleterm/types';

const mainProcessClient = createMainProcessClient();
const runtimeSettings = mainProcessClient.getRuntimeSettings();
const loggerService = createLoggerService({
const loggerService = createFileLoggerService({
dev: runtimeSettings.dev,
dir: runtimeSettings.userDataDir,
name: 'renderer',
Expand Down
3 changes: 1 addition & 2 deletions web/packages/teleterm/src/services/logger/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
import createLoggerService from './loggerService';
export default createLoggerService;
export * from './loggerService';
98 changes: 80 additions & 18 deletions web/packages/teleterm/src/services/logger/loggerService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
import { createLogger as createWinston, format, transports } from 'winston';
import winston, {
createLogger as createWinston,
format,
transports,
} from 'winston';
import { isObject } from 'lodash';

import split2 from 'split2';

import { Logger, NodeLoggerService } from './types';
import { Logger, LoggerService, NodeLoggerService } from './types';

export default function createLoggerService(opts: Options): NodeLoggerService {
/**
* stdout logger should be used in child processes.
* It sends logs directly to stdout, so the parent logger can process that output
* (e.g. show it in the terminal and save to a file).
* It also allows parent to log errors emitted by the child process.
*/
export function createStdoutLoggerService(): LoggerService {
const instance = createWinston({
level: 'info',
exitOnError: false,
format: format.combine(
format.printf(({ level, message, context }) => {
const text = stringifier(message as unknown as unknown[]);
return `[${context}] ${level}: ${text}`;
})
),
transports: [new transports.Console()],
});

return {
createLogger(context = 'default'): Logger {
const logger = instance.child({ context });
return createLoggerFromWinston(logger);
},
};
}

/**
* File logger saves logs directly to the file and shows them in the terminal in dev mode.
* Can be used as a parent logger and process logs from child processes.
*/
export function createFileLoggerService(
opts: FileLoggerOptions
): NodeLoggerService {
const instance = createWinston({
level: 'info',
exitOnError: false,
Expand All @@ -17,8 +54,8 @@ export default function createLoggerService(opts: Options): NodeLoggerService {
const text = stringifier(message as unknown as unknown[]);
const contextAndLevel = opts.passThroughMode
? ''
: ` [${context}] ${level}`;
return `[${timestamp}]${contextAndLevel}: ${text}`;
: ` [${context}] ${level}:`;
return `[${timestamp}]${contextAndLevel} ${text}`;
})
),
transports: [
Expand All @@ -35,8 +72,16 @@ export default function createLoggerService(opts: Options): NodeLoggerService {
instance.add(
new transports.Console({
format: format.printf(({ level, message, context }) => {
const loggerName =
opts.loggerNameColor &&
`\x1b[${opts.loggerNameColor}m${opts.name.toUpperCase()}\x1b[0m`;

const text = stringifier(message as unknown as unknown[]);
return opts.passThroughMode ? text : `[${context}] ${level}: ${text}`;
const logMessage = opts.passThroughMode
? text
: `[${context}] ${level}: ${text}`;

return [loggerName, logMessage].filter(Boolean).join(' ');
}),
})
);
Expand All @@ -50,17 +95,28 @@ export default function createLoggerService(opts: Options): NodeLoggerService {
},
createLogger(context = 'default'): Logger {
const logger = instance.child({ context });
return {
error: (...args) => {
logger.error(args);
},
warn: (...args) => {
logger.warn(args);
},
info: (...args) => {
logger.info(args);
},
};
return createLoggerFromWinston(logger);
},
};
}

// maps color names to ANSI colors
export enum LoggerColor {
Magenta = '45',
Cyan = '46',
Yellow = '43',
}

function createLoggerFromWinston(logger: winston.Logger): Logger {
return {
error: (...args) => {
logger.error(args);
},
warn: (...args) => {
logger.warn(args);
},
info: (...args) => {
logger.info(args);
},
};
}
Expand All @@ -79,9 +135,15 @@ function stringifier(message: unknown[]): string {
.join(' ');
}

type Options = {
type FileLoggerOptions = {
dir: string;
name: string;
/**
* Specifies color for the logger name e.g. SHARED, TSHD.
* Logger name is printed in the terminal, only in dev mode.
* If not specified, the logger name will not be printed.
*/
loggerNameColor?: LoggerColor;
dev?: boolean;
/**
* Mode for logger handling logs from other sources. Log level and context are not included in the log message.
Expand Down
13 changes: 5 additions & 8 deletions web/packages/teleterm/src/sharedProcess/sharedProcess.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Server, ServerCredentials } from '@grpc/grpc-js';

import createLoggerService from 'teleterm/services/logger';
import { createStdoutLoggerService } from 'teleterm/services/logger';

import {
createInsecureServerCredentials,
createServerCredentials,
Expand All @@ -16,7 +17,7 @@ import { PtyHostService } from './api/protogen/ptyHostService_grpc_pb';
import { createPtyHostService } from './ptyHost/ptyHostService';

const runtimeSettings = getRuntimeSettings();
initializeLogger(runtimeSettings);
initializeLogger();
initializeServer(runtimeSettings);

function getRuntimeSettings(): RuntimeSettings {
Expand All @@ -34,12 +35,8 @@ function getRuntimeSettings(): RuntimeSettings {
return runtimeSettings;
}

function initializeLogger(runtimeSettings: RuntimeSettings): void {
const loggerService = createLoggerService({
dev: runtimeSettings.dev,
dir: runtimeSettings.userDataDir,
name: 'shared',
});
function initializeLogger(): void {
const loggerService = createStdoutLoggerService();

Logger.init(loggerService);
const logger = new Logger();
Expand Down

0 comments on commit c3f557a

Please sign in to comment.