From faefc11c66471b39b5245e19a4b51429c46a9a04 Mon Sep 17 00:00:00 2001 From: 0fatal <72899968+0fatal@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:37:27 +0800 Subject: [PATCH] feat(runtime&server): support display error/log line (#1813) * feat(runtime&server): support display error/log line * fix * improve * chore --- runtimes/nodejs/package-lock.json | 18 ++++++++++ runtimes/nodejs/package.json | 1 + runtimes/nodejs/src/config.ts | 7 ++++ runtimes/nodejs/src/handler/invoke.ts | 18 ++++++++++ runtimes/nodejs/src/index.ts | 7 ++++ runtimes/nodejs/src/support/engine/console.ts | 34 ++++++++++++++++++- runtimes/nodejs/src/support/engine/module.ts | 23 +++++++------ runtimes/nodejs/src/support/engine/types.ts | 1 + .../function-template.service.ts | 5 ++- server/src/function/function.service.ts | 7 ++-- server/src/utils/lang.ts | 19 +++++++---- 11 files changed, 118 insertions(+), 22 deletions(-) diff --git a/runtimes/nodejs/package-lock.json b/runtimes/nodejs/package-lock.json index fe901cb09d..4eda94882b 100644 --- a/runtimes/nodejs/package-lock.json +++ b/runtimes/nodejs/package-lock.json @@ -34,6 +34,7 @@ "node-modules-utils": "^1.0.0-beta.14", "nodemailer": "^6.6.3", "pako": "^2.1.0", + "source-map-support": "^0.5.21", "typescript-language-server": "^3.3.2", "validator": "^13.7.0", "vscode-languageserver": "^9.0.1", @@ -5240,6 +5241,23 @@ "npm": ">= 3.0.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", diff --git a/runtimes/nodejs/package.json b/runtimes/nodejs/package.json index c7eba0ff06..eb49911867 100644 --- a/runtimes/nodejs/package.json +++ b/runtimes/nodejs/package.json @@ -51,6 +51,7 @@ "node-modules-utils": "^1.0.0-beta.14", "nodemailer": "^6.6.3", "pako": "^2.1.0", + "source-map-support": "^0.5.21", "typescript-language-server": "^3.3.2", "validator": "^13.7.0", "vscode-languageserver": "^9.0.1", diff --git a/runtimes/nodejs/src/config.ts b/runtimes/nodejs/src/config.ts index 57dbce8152..038d165771 100644 --- a/runtimes/nodejs/src/config.ts +++ b/runtimes/nodejs/src/config.ts @@ -37,6 +37,13 @@ export default class Config { return (process.env['LOG_LEVEL'] as any) || 'debug' } + /** + * the logger display line level : 'info', 'warn', 'error', 'debug' + */ + static get DISPLAY_LINE_LOG_LEVEL(): 'debug' | 'info' | 'warn' | 'error' { + return (process.env['DISPLAY_LINE_LOG_LEVEL'] as any) || 'error' + } + /** * the object depth limit when logging */ diff --git a/runtimes/nodejs/src/handler/invoke.ts b/runtimes/nodejs/src/handler/invoke.ts index d1cfcecf01..e08177dc49 100644 --- a/runtimes/nodejs/src/handler/invoke.ts +++ b/runtimes/nodejs/src/handler/invoke.ts @@ -161,6 +161,17 @@ async function invokeDebug( requestId, }) } + + // for debug usage + require('source-map-support').install({ + emptyCacheBetweenOperations: true, + overrideRetrieveFile: true, + retrieveFile: (path) => + funcName === path + ? funcData.source.compiled + : FunctionCache.get(path)?.source.compiled, + }) + const debugConsole = new DebugConsole(funcName) const executor = new FunctionDebugExecutor(funcData, debugConsole) @@ -218,5 +229,12 @@ async function invokeDebug( } catch (error) { debugConsole.error(requestId, 'failed to invoke error', error) return ctx.response.status(500).send('Internal Server Error') + } finally { + // restore + require('source-map-support').install({ + emptyCacheBetweenOperations: true, + overrideRetrieveFile: true, + retrieveFile: (path) => FunctionCache.get(path)?.source.compiled, + }) } } diff --git a/runtimes/nodejs/src/index.ts b/runtimes/nodejs/src/index.ts index 17e31635e9..c59789ad32 100644 --- a/runtimes/nodejs/src/index.ts +++ b/runtimes/nodejs/src/index.ts @@ -25,6 +25,13 @@ import url from 'url' import { LspWebSocket } from './support/lsp' import { createCloudSdk } from './support/cloud-sdk' +import { FunctionCache } from './support/engine' + +require('source-map-support').install({ + emptyCacheBetweenOperations: true, + overrideRetrieveFile: true, + retrieveFile: (path) => FunctionCache.get(path)?.source.compiled, +}) // hack: set createCloudSdk to global object to make it available in @lafjs/cloud package globalThis.createCloudSdk = createCloudSdk diff --git a/runtimes/nodejs/src/support/engine/console.ts b/runtimes/nodejs/src/support/engine/console.ts index 60f0e9d64d..6522a78176 100644 --- a/runtimes/nodejs/src/support/engine/console.ts +++ b/runtimes/nodejs/src/support/engine/console.ts @@ -28,6 +28,26 @@ export class Console { const fn = chalk.blueBright(`[${this.category}]`) + let location = '' + if (this._shouldDisplayLine(level)) { + try { + throw new Error() + } catch (e) { + try { + // exclude involuntary log + if (e.stack.includes('Executor.invoke')) { + const msgs = e.stack.includes('at DebugConsole._format') + ? e.stack.split('\n')[4].split(':') + : e.stack.split('\n')[3].split(':') + const line = msgs[1] + let loc = msgs[0].match(/at (.*?) \(/)[1] + loc = loc === 'default_1' ? 'MAIN' : loc + location = chalk.gray(`(${loc}:${line})`) + } + } catch {} + } + } + const content = params .map((param) => { if (typeof param === 'string') return this._colorize(level, param) @@ -42,7 +62,7 @@ export class Console { .join(' ') // content = this._colorize(level, content) - const data = `${time} ${levelStr} ${fn} ${content}` + const data = `${time} ${levelStr} ${fn}${location} ${content}` return data } @@ -106,6 +126,18 @@ export class Console { const configLevelValue = LogLevelValue[configLevel] ?? 0 return LogLevelValue[level] >= configLevelValue } + + protected _shouldDisplayLine(level: LogLevel) { + const LogLevelValue = { + [LogLevel.INFO]: 1, + [LogLevel.WARN]: 2, + [LogLevel.ERROR]: 3, + [LogLevel.DEBUG]: 4, + } + const configLevel = (Config.DISPLAY_LINE_LOG_LEVEL || 'error').toUpperCase() + const configLevelValue = LogLevelValue[configLevel] ?? 3 + return LogLevelValue[level] >= configLevelValue + } } export class DebugConsole extends Console { diff --git a/runtimes/nodejs/src/support/engine/module.ts b/runtimes/nodejs/src/support/engine/module.ts index 715e7db50d..a486f90c2a 100644 --- a/runtimes/nodejs/src/support/engine/module.ts +++ b/runtimes/nodejs/src/support/engine/module.ts @@ -53,6 +53,9 @@ export class FunctionModule { // build function module const data = FunctionCache.get(fn) + if (!data) { + throw new Error(`function ${fn} not found`) + } const mod = this.compile(fn, data.source.compiled, fromModule) // cache module @@ -86,7 +89,7 @@ export class FunctionModule { consoleInstance, ) const options: RunningScriptOptions = { - filename: `FunctionModule.${functionName}`, + filename: functionName, displayErrors: true, contextCodeGeneration: { strings: true, @@ -94,7 +97,7 @@ export class FunctionModule { }, } as any - const script = this.createScript(wrapped, {}) + const script = this.createScript(wrapped, options) return script.runInNewContext(sandbox, options) } @@ -103,15 +106,12 @@ export class FunctionModule { } protected static wrap(code: string): string { - return ` - const require = (name) => { - __from_modules.push(__filename) - return __require(name, __from_modules, __filename) - } - - ${code} - module.exports; - ` + // ensure 1 line to balance line offset of error stack + return [ + `function require(name){__from_modules.push(__filename);return __require(name,__from_modules,__filename);}`, + `${code}`, + `\nmodule.exports;`, + ].join(' ') } /** @@ -162,6 +162,7 @@ export class FunctionModule { exports: _module.exports, console: fConsole, __require: this.require.bind(this), + RegExp: RegExp, Buffer: Buffer, setImmediate: setImmediate, clearImmediate: clearImmediate, diff --git a/runtimes/nodejs/src/support/engine/types.ts b/runtimes/nodejs/src/support/engine/types.ts index 7874693848..5e672eb884 100644 --- a/runtimes/nodejs/src/support/engine/types.ts +++ b/runtimes/nodejs/src/support/engine/types.ts @@ -18,6 +18,7 @@ export interface FunctionModuleGlobalContext { console: any __require: RequireFuncType Buffer: typeof Buffer + RegExp: typeof RegExp setTimeout: typeof setTimeout clearTimeout: typeof clearTimeout setInterval: typeof setInterval diff --git a/server/src/function-template/function-template.service.ts b/server/src/function-template/function-template.service.ts index 8e5d86ba32..e8eacbc440 100644 --- a/server/src/function-template/function-template.service.ts +++ b/server/src/function-template/function-template.service.ts @@ -360,7 +360,10 @@ export class FunctionTemplateService { name: functionTemplateItem.name, source: { code: functionTemplateItem.source.code, - compiled: compileTs2js(functionTemplateItem.source.code), + compiled: compileTs2js( + functionTemplateItem.source.code, + functionTemplateItem.name, + ), version: 0, }, desc: functionTemplateItem.desc || '', diff --git a/server/src/function/function.service.ts b/server/src/function/function.service.ts index 2cd2cc9588..fa397de09d 100644 --- a/server/src/function/function.service.ts +++ b/server/src/function/function.service.ts @@ -46,7 +46,7 @@ export class FunctionService { name: dto.name, source: { code: dto.code, - compiled: compileTs2js(dto.code), + compiled: compileTs2js(dto.code, dto.name), version: 0, }, desc: dto.description, @@ -110,6 +110,7 @@ export class FunctionService { { $set: { name: dto.newName, + 'source.compiled': compileTs2js(func.source.code, dto.newName), desc: dto.description, methods: dto.methods, tags: dto.tags || [], @@ -163,7 +164,7 @@ export class FunctionService { $set: { source: { code: dto.code, - compiled: compileTs2js(dto.code), + compiled: compileTs2js(dto.code, func.name), version: func.source.version + 1, }, desc: dto.description, @@ -311,7 +312,7 @@ export class FunctionService { source: { ...func.source, code: dto.code, - compiled: compileTs2js(dto.code), + compiled: compileTs2js(dto.code, func.name), version: func.source.version + 1, }, updatedAt: new Date(), diff --git a/server/src/utils/lang.ts b/server/src/utils/lang.ts index 3b2a5998f9..44922d8c38 100644 --- a/server/src/utils/lang.ts +++ b/server/src/utils/lang.ts @@ -4,12 +4,19 @@ import * as ts from 'typescript' * compile typescript code to javascript * @param source typescript source code */ -export function compileTs2js(source: string) { - const jscode = ts.transpile(source, { - module: ts.ModuleKind.Node16, - target: ts.ScriptTarget.ES2022, - removeComments: true, - }) +export function compileTs2js(source: string, name: string) { + const jscode = ts.transpile( + source, + { + module: ts.ModuleKind.Node16, + target: ts.ScriptTarget.ES2022, + removeComments: true, + inlineSourceMap: true, + }, + name, + undefined, + name, + ) return jscode }