-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
08c15c9
commit 1f45e04
Showing
27 changed files
with
280 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Bug fixes and performance improvements |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
const fs = require('fs'); | ||
const { SourceMapConsumer } = require('source-map'); | ||
const path = require('path'); | ||
|
||
const USAGE_GUIDE = `This script is intended to be used manually. | ||
It makes JavaScript errors from user logs more readable by converting the stacktrace references from minified file addresses to source code addresses. | ||
To use the script, build the application first. | ||
Make sure the application version you build is the same as the version of the application that produced the logs. | ||
Then run any of these commands: | ||
npm run resolve-stacktrace <error> | ||
npm run resolve-stacktrace <dist-directory> <error> | ||
Where <error> is an error string from a log file exported by the application, | ||
and <dist-directory> is the path to a directory with the application's sourcemaps (default: dist). | ||
Examples: | ||
npm run resolve-stacktrace ${JSON.stringify('{"name":"Error","message":"Test","stack":"Error: Test\n at t.BitBuilder.writeVarUint (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:25840)\n at t.BitBuilder.writeCoins (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:26382)"}')} | ||
npm run resolve-stacktrace "Error: Test\n at t.BitBuilder.writeVarUint (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:25840)\n at t.BitBuilder.writeCoins (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:26382)"`; | ||
|
||
const DEFAULT_MAP_DIRECTORY = path.join(__dirname, '..', 'dist'); | ||
|
||
const { mapDirectory, stackTrace } = resolveArguments(); | ||
const resolvedStackTrace = resolveStackTrace(mapDirectory, stackTrace); | ||
process.stdout.write(`${resolvedStackTrace}\n`); | ||
process.exit(0); | ||
|
||
function resolveArguments() { | ||
const arguments = process.argv.slice(2); | ||
switch (arguments.length) { | ||
case 0: | ||
process.stderr.write(`Too few arguments!\n\n${USAGE_GUIDE}\n`) | ||
process.exit(1); | ||
break; | ||
case 1: | ||
return { | ||
mapDirectory: DEFAULT_MAP_DIRECTORY, | ||
stackTrace: parseStackTrace(arguments[0]), | ||
}; | ||
case 2: | ||
return { | ||
mapDirectory: arguments[0], | ||
stackTrace: parseStackTrace(arguments[1]), | ||
}; | ||
default: | ||
process.stderr.write(`Too many arguments!\n\n${USAGE_GUIDE}\n`) | ||
process.exit(1); | ||
break; | ||
} | ||
} | ||
|
||
function parseStackTrace(inputText) { | ||
const decoders = [parseJsonStackTrace, parsePlainStackTrace]; | ||
|
||
for (const decoder of decoders) { | ||
const decoded = decoder(inputText); | ||
if (decoded) { | ||
return decoded; | ||
} | ||
} | ||
|
||
process.stderr.write(`Unknown input error format. Check the examples.\n\n${USAGE_GUIDE}\n`) | ||
process.exit(1); | ||
} | ||
|
||
// Decodes a line from a log file as is. For example: | ||
// "{\"name\":\"TypeError\",\"message\":\"Cannot... | ||
function parseJsonStackTrace(inputText) { | ||
let data | ||
try { | ||
data = JSON.parse(inputText); | ||
} catch { | ||
return null; | ||
} | ||
|
||
if (!data || typeof data !== 'object' || typeof data.stack !== 'string') { | ||
return null; | ||
} | ||
|
||
return data.stack; | ||
} | ||
|
||
function parsePlainStackTrace(inputText) { | ||
if (/^(.+)(\n.*:\d+:\d+)+$/.test(inputText)) { | ||
return inputText; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
function resolveStackTrace(mapDirectory, stackTrace) { | ||
const consumerCache = {}; | ||
|
||
return stackTrace | ||
.split('\n') | ||
.map(line => resolveStackTraceLine(mapDirectory, consumerCache, line)) | ||
.join('\n'); | ||
} | ||
|
||
function resolveStackTraceLine(mapDirectory, consumerCache, line) { | ||
const parsedLine = parseStackTraceLine(line); | ||
if (!parsedLine) { | ||
return line; | ||
} | ||
|
||
const newTrace = resolveTrace( | ||
mapDirectory, | ||
consumerCache, | ||
parsedLine.fileUrl, | ||
parsedLine.lineNumber, | ||
parsedLine.columnNumber, | ||
); | ||
if (!newTrace) { | ||
return line; | ||
} | ||
|
||
return `${parsedLine.lineIndent}at ${newTrace.name ?? ''} ${newTrace.filePath}:${newTrace.lineNumber}:${newTrace.columnNumber}`; | ||
} | ||
|
||
function parseStackTraceLine(line) { | ||
// Example: at t.BitBuilder.writeCoins (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:26382) | ||
const chromeRegex1 = /^(\s*)at\s.+\((.+):(\d+):(\d+)\)\s*$/; | ||
// Example: at async https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:1906473 | ||
const chromeRegex2 = /^(\s*)at(?:\sasync)?\s(.+):(\d+):(\d+)\s*$/; | ||
// Example: safeExec@http://localhost:4321/main.0f90301c98b9aa1b7228.js:55739:14 | ||
// Example: @http://localhost:4321/main.0f90301c98b9aa1b7228.js:49974:25 | ||
// Example: ./src/lib/teact/teact.ts/runUpdatePassOnRaf</<@http://localhost:4321/main.0f90301c98b9aa1b7228.js:49974:32 | ||
const safariAndFirefoxRegex = /^(\s*)\S*@(.+):(\d+):(\d+)\s*$/; | ||
|
||
const match = chromeRegex1.exec(line) || chromeRegex2.exec(line) || safariAndFirefoxRegex.exec(line); | ||
if (!match) { | ||
return null; | ||
} | ||
|
||
const [, lineIndent, fileUrl, lineNumber, columnNumber] = match; | ||
return { lineIndent, fileUrl, lineNumber: Number(lineNumber), columnNumber: Number(columnNumber) }; | ||
} | ||
|
||
function resolveTrace(mapDirectory, consumerCache, fileUrl, lineNumber, columnNumber) { | ||
const mapFile = findSourceMapFile(mapDirectory, fileUrl); | ||
if (!mapFile) { | ||
return null; | ||
} | ||
|
||
if (!consumerCache[mapFile]) { | ||
const sourceMap = JSON.parse(fs.readFileSync(path.join(mapDirectory, mapFile), 'utf8')); | ||
const consumer = new SourceMapConsumer(sourceMap); | ||
consumerCache[mapFile] = consumer; | ||
} | ||
|
||
const sourcePosition = consumerCache[mapFile].originalPositionFor({ line: lineNumber, column: columnNumber }); | ||
if (sourcePosition.line === null) { | ||
return null; | ||
} | ||
|
||
return { | ||
name: sourcePosition.name, | ||
filePath: resolveSourceFilePath(sourcePosition.source), | ||
lineNumber: sourcePosition.line, | ||
columnNumber: sourcePosition.column, | ||
}; | ||
} | ||
|
||
function findSourceMapFile(mapDirectory, fileUrl) { | ||
const filePath = extractFilePathFromUrl(fileUrl); | ||
const bundleId = extractBundleIdFromFilePath(filePath, '.js'); | ||
if (bundleId === null) { | ||
return null; | ||
} | ||
|
||
const directoryContent = fs.readdirSync(mapDirectory, { withFileTypes: true }); | ||
const isDesiredMap = (item) => item.isFile() && extractBundleIdFromFilePath(item.name, '.js.map') === bundleId; | ||
return directoryContent.find(isDesiredMap)?.name ?? null; | ||
} | ||
|
||
function extractFilePathFromUrl(fileUrl) { | ||
return fileUrl.replace(/^https?:\/\/[^\/]*\//, ''); | ||
} | ||
|
||
function extractBundleIdFromFilePath(filePath, extension) { | ||
if (!filePath.endsWith(extension)) { | ||
return null; | ||
} | ||
|
||
const match = /^([\w.-]*)\.[\w]{16,}$/.exec(filePath.slice(0, filePath.length - extension.length)); | ||
if (!match) { | ||
return null; | ||
} | ||
|
||
return match[1]; | ||
} | ||
|
||
function resolveSourceFilePath(sourceFileUrl) { | ||
return sourceFileUrl.replace(/^webpack:\/\/[^\/]*\//, ''); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
3.1.1 | ||
3.1.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.