diff --git a/packages/node-utils/src/http.ts b/packages/node-utils/src/http.ts index 52ca4482e7f6..259638728a4b 100644 --- a/packages/node-utils/src/http.ts +++ b/packages/node-utils/src/http.ts @@ -3,7 +3,7 @@ import * as net from 'net'; import * as url from 'url'; import type { RequiredKey } from '@trezor/type-utils'; -import { LogMessage, TypedEmitter } from '@trezor/utils'; +import { Log, TypedEmitter } from '@trezor/utils'; import { arrayPartition } from '@trezor/utils'; import { getFreePort } from './getFreePort'; @@ -11,22 +11,6 @@ import { getFreePort } from './getFreePort'; type Request = RequiredKey; type EventMap = { [event: string]: any }; -type LogFn = (message: string | string[]) => void; -type Logger = { - info: LogFn; - warn: LogFn; - error: LogFn; - getLog: () => LogMessage[]; -}; - -type OriginalLogFn = (topic: string, message: string | string[]) => void; -type OriginalLogger = { - info: OriginalLogFn; - warn: OriginalLogFn; - error: OriginalLogFn; - getLog?: () => LogMessage[]; -}; - type RequestWithParams = Request & { params: Record; }; @@ -42,7 +26,7 @@ export type Handler = ( request: R, response: Response, next: Next, - { logger }: { logger: Logger }, + { logger }: { logger: Log }, ) => void; type Route = { @@ -66,28 +50,18 @@ type BaseEvents = { */ export class HttpServer extends TypedEmitter { public server: http.Server; - public logger: Logger; + public logger: Log; private routes: Route[] = []; private readonly emitter: TypedEmitter = this; private port?: number; private sockets: Record = {}; - constructor({ logger, port }: { logger: OriginalLogger; port?: number }) { + constructor({ logger, port }: { logger: Log; port?: number }) { super(); this.port = port; - // this class accepts subset of suite-desktop-core "ILogger" interface. - // - in order to omit need for passing the first argument "topic" in each call, we wrap the logger and prepend "http: ${this.port}" to each call - // - here it implements also only a subset of ILogger functionality - // - todo: unify loggers across the codebase - this.logger = { - info: (message: string | string[]) => logger.info(`${this.logName}`, message), - warn: (message: string | string[]) => logger.warn(`${this.logName}`, message), - error: (message: string | string[]) => logger.error(`${this.logName}`, message), - getLog: () => (logger.getLog ? logger.getLog() : []), - }; - // this.logger = logger; + this.logger = logger; this.server = http.createServer(this.onRequest); } @@ -337,7 +311,7 @@ const checkOrigin = ({ request: Parameters[0]; allowedOrigin: string[]; pathname: string; - logger: Logger; + logger: Log; }) => { const { origin } = request.headers; const origins = allowedOrigin ?? []; @@ -373,7 +347,7 @@ const checkReferer = ({ request: Parameters[0]; allowedReferer: string[]; pathname: string; - logger: Logger; + logger: Log; }) => { const { referer } = request.headers; const referers = allowedReferer ?? []; diff --git a/packages/node-utils/src/tests/http.test.ts b/packages/node-utils/src/tests/http.test.ts index 05b1099f8a1f..dd941bb22c08 100644 --- a/packages/node-utils/src/tests/http.test.ts +++ b/packages/node-utils/src/tests/http.test.ts @@ -14,6 +14,7 @@ describe('HttpServer', () => { let server: HttpServer; beforeEach(() => { server = new HttpServer({ + // @ts-expect-error logger: muteLogger, }); }); @@ -45,6 +46,7 @@ describe('HttpServer', () => { }); test('a port can be passed to the constructor', async () => { + // @ts-expect-error server = new HttpServer({ logger: muteLogger, port: 65526 }); await server.start(); expect(server.getServerAddress()).toMatchObject({ diff --git a/packages/suite-desktop-core/src/index.d.ts b/packages/suite-desktop-core/src/index.d.ts index ac12127f0a55..c736bc050039 100644 --- a/packages/suite-desktop-core/src/index.d.ts +++ b/packages/suite-desktop-core/src/index.d.ts @@ -1,6 +1,13 @@ // Include suite globals (as some dependencies from @suite can rely on them) /// +type LogMessage = { + date: Date; + level: LogLevel; + topic: string; + text: string; +}; + declare interface ILogger { /** * Exit the Logger (will correctly end the log file) @@ -48,6 +55,10 @@ declare interface ILogger { * @param options(Partial) - Log options */ config; + /** + * If logs are stored in memory, return array of logs + */ + getLog: () => LogMessage[]; } // Globals diff --git a/packages/suite-desktop-core/src/libs/http-receiver.ts b/packages/suite-desktop-core/src/libs/http-receiver.ts index 1f1c5126ba31..4539d211a4a0 100644 --- a/packages/suite-desktop-core/src/libs/http-receiver.ts +++ b/packages/suite-desktop-core/src/libs/http-receiver.ts @@ -4,6 +4,7 @@ import { xssFilters } from '@trezor/utils'; import { HttpServer, allowReferers } from '@trezor/node-utils'; import { HTTP_ORIGINS_DEFAULT } from './constants'; +import { convertILoggerToLog } from '../utils/IloggerToLog'; type TemplateOptions = { title?: string; @@ -38,7 +39,10 @@ const applyTemplate = (content: string, options?: TemplateOptions) => { }; export const createHttpReceiver = () => { - const httpReceiver = new HttpServer({ logger: global.logger, port: 21335 }); + const httpReceiver = new HttpServer({ + logger: convertILoggerToLog(global.logger), + port: 21335, + }); httpReceiver.use([ (request, response, next) => { diff --git a/packages/suite-desktop-core/src/libs/logger.ts b/packages/suite-desktop-core/src/libs/logger.ts index 7b2daa534319..a4d502946fff 100644 --- a/packages/suite-desktop-core/src/libs/logger.ts +++ b/packages/suite-desktop-core/src/libs/logger.ts @@ -23,19 +23,14 @@ export type Options = { outputPath: string; // path for the output logFormat: string; // Output format of the log dedupeTimeout: number; // After how many ms are the same messages deduplicated + writeToMemory?: boolean; // Output is stored in memory + memoryCap: number; // Maximum number of messages stored in memory }; const logLevelSwitchValue = app?.commandLine.getSwitchValue('log-level'); const logLevelByEnv = isDevEnv ? 'debug' : 'error'; const logLevelDefault = isLogLevel(logLevelSwitchValue) ? logLevelSwitchValue : logLevelByEnv; -type LogMessage = { - date: Date; - level: LogLevel; - topic: string; - text: string; -}; - type RepeatedLogMessage = LogMessage & { repetition?: number; }; @@ -46,6 +41,7 @@ export class Logger implements ILogger { private defaultOptions: Options; private options: Options; private logLevel = 0; + private memory: LogMessage[] = []; constructor(level?: LogLevel, options?: Partial) { const logLevel = level || logLevelDefault; @@ -64,6 +60,8 @@ export class Logger implements ILogger { (userDataDir ? `${userDataDir}/logs` : process.cwd()), logFormat: '%dt - %lvl(%top): %rep%msg', dedupeTimeout: 500, + writeToMemory: false, + memoryCap: 1000, }; this.options = { @@ -116,9 +114,9 @@ export class Logger implements ILogger { } private log(level: LogLevel, topic: string, message: string | string[]) { - const { writeToConsole, writeToDisk, logFormat } = this.options; + const { writeToConsole, writeToDisk, writeToMemory, logFormat } = this.options; - if ((!writeToConsole && !writeToDisk) || !logFormat) { + if ((!writeToConsole && !writeToDisk && !writeToMemory) || !logFormat) { return; } @@ -175,6 +173,14 @@ export class Logger implements ILogger { rep: (repetition ?? 0) > 1 ? `(${repetition}x) ` : '', }); + // write to memory + if (this.options.writeToMemory) { + this.memory.push({ date, level, topic, text }); + if (this.memory.length > this.options.memoryCap) { + this.memory.shift(); + } + } + if (this.options.writeToConsole) { console.log(this.options.colors ? this.color(level, message) : message); } @@ -274,4 +280,10 @@ export class Logger implements ILogger { this.logBasicInfo(); } } + + public getLog() { + console.log('=====get log called', this.memory); + + return this.memory; + } } diff --git a/packages/suite-desktop-core/src/modules/bridge.ts b/packages/suite-desktop-core/src/modules/bridge.ts index 1974867d3a83..03da16b57a60 100644 --- a/packages/suite-desktop-core/src/modules/bridge.ts +++ b/packages/suite-desktop-core/src/modules/bridge.ts @@ -6,6 +6,8 @@ import { TrezordNode } from '@trezor/transport-bridge'; import { app, ipcMain } from '../typed-electron'; import { BridgeProcess } from '../libs/processes/BridgeProcess'; import { b2t } from '../libs/utils'; +import { Logger } from '../libs/logger'; +import { convertILoggerToLog } from '../utils/IloggerToLog'; import type { Module, Dependencies } from './index'; @@ -53,20 +55,18 @@ const getBridgeInstance = (store: Dependencies['store']) => { return new BridgeProcess(); } + const bridgeLogger = new Logger('info', { + writeToDisk: false, + writeToConsole: false, + writeToMemory: true, + }); + return new TrezordNode({ port: 21325, api: bridgeDev || bridgeTest ? 'udp' : 'usb', assetPrefix: '../build/node-bridge', // passing down ILogger where Log is expected. - // @ts-expect-error - logger: { - ...global.logger, - log: (...args) => logger.info('trezord-node', args.join(' ')), - info: (...args) => logger.info('trezord-node', args.join(' ')), - warn: (...args) => logger.warn('trezord-node', args.join(' ')), - debug: (...args) => logger.debug('trezord-node', args.join(' ')), - error: (...args) => logger.error('trezord-node', args.join(' ')), - }, + logger: convertILoggerToLog(bridgeLogger), }); }; diff --git a/packages/suite-desktop-core/src/utils/IloggerToLog.ts b/packages/suite-desktop-core/src/utils/IloggerToLog.ts new file mode 100644 index 000000000000..2254359f95d7 --- /dev/null +++ b/packages/suite-desktop-core/src/utils/IloggerToLog.ts @@ -0,0 +1,29 @@ +import { Log, LogMessage as UtilsLogMessage } from '@trezor/utils'; + +/** take an instance of ILogger and return instance of Log while keeping more or less the same behavior */ +export const convertILoggerToLog = (iLogger: ILogger): Log => { + return { + log: (msg: string) => iLogger.info('', msg), + info: (msg: string) => iLogger.info('', msg), + debug: (msg: string) => iLogger.debug('', msg), + warn: (msg: string) => iLogger.warn('', msg), + error: (msg: string) => iLogger.error('', msg), + prefix: '', + messages: [], + enabled: true, + css: '', + MAX_ENTRIES: 1000, + setColors: (_colors: any) => {}, + setWriter: (_logWriter: any) => {}, + addMessage: (_msg: UtilsLogMessage) => {}, + logWriter: undefined, + getLog: (): UtilsLogMessage[] => { + return iLogger.getLog().map(log => ({ + message: [log.text], + prefix: '', + level: log.level, + timestamp: log.date.getTime(), + })); + }, + }; +}; diff --git a/packages/transport-bridge/src/bin.ts b/packages/transport-bridge/src/bin.ts index 0a2be9293f5c..47a38ab6e1ce 100644 --- a/packages/transport-bridge/src/bin.ts +++ b/packages/transport-bridge/src/bin.ts @@ -1,5 +1,11 @@ +import { Log } from '@trezor/utils'; + import { TrezordNode } from './http'; -const trezordNode = new TrezordNode({ port: 21325, api: 'usb' }); +const trezordNode = new TrezordNode({ + port: 21325, + api: 'usb', + logger: new Log('@trezor/transport-bridge', true), +}); trezordNode.start(); diff --git a/packages/transport-bridge/src/http.ts b/packages/transport-bridge/src/http.ts index 5325601d0a96..173f3290a076 100644 --- a/packages/transport-bridge/src/http.ts +++ b/packages/transport-bridge/src/http.ts @@ -47,11 +47,10 @@ export class TrezordNode { port: number; api: 'usb' | 'udp'; assetPrefix?: string; - logger?: Log; + logger: Log; }) { this.port = port || defaults.port; - this.logger = logger || new Log('@trezor/transport-bridge', true); - + this.logger = logger; this.descriptors = []; this.listenSubscriptions = []; @@ -271,7 +270,7 @@ export class TrezordNode { intro: `To download full logs go to http://127.0.0.1:${this.port}/logs`, version: this.version, devices: enumerateResult.success ? enumerateResult.payload.descriptors : [], - logs: app.logger.getLog().slice(-20), + logs: this.logger.getLog().slice(-20), }; res.end(str(props));