contextlines.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. var {
  2. _optionalChain
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const fs = require('fs');
  6. const core = require('@sentry/core');
  7. const utils = require('@sentry/utils');
  8. const FILE_CONTENT_CACHE = new utils.LRUMap(100);
  9. const DEFAULT_LINES_OF_CONTEXT = 7;
  10. const INTEGRATION_NAME = 'ContextLines';
  11. // TODO: Replace with promisify when minimum supported node >= v8
  12. function readTextFileAsync(path) {
  13. return new Promise((resolve, reject) => {
  14. fs.readFile(path, 'utf8', (err, data) => {
  15. if (err) reject(err);
  16. else resolve(data);
  17. });
  18. });
  19. }
  20. const _contextLinesIntegration = ((options = {}) => {
  21. const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
  22. return {
  23. name: INTEGRATION_NAME,
  24. // TODO v8: Remove this
  25. setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
  26. processEvent(event) {
  27. return addSourceContext(event, contextLines);
  28. },
  29. };
  30. }) ;
  31. const contextLinesIntegration = core.defineIntegration(_contextLinesIntegration);
  32. /**
  33. * Add node modules / packages to the event.
  34. * @deprecated Use `contextLinesIntegration()` instead.
  35. */
  36. // eslint-disable-next-line deprecation/deprecation
  37. const ContextLines = core.convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration)
  38. ;
  39. async function addSourceContext(event, contextLines) {
  40. // keep a lookup map of which files we've already enqueued to read,
  41. // so we don't enqueue the same file multiple times which would cause multiple i/o reads
  42. const enqueuedReadSourceFileTasks = {};
  43. const readSourceFileTasks = [];
  44. if (contextLines > 0 && _optionalChain([event, 'access', _2 => _2.exception, 'optionalAccess', _3 => _3.values])) {
  45. for (const exception of event.exception.values) {
  46. if (!_optionalChain([exception, 'access', _4 => _4.stacktrace, 'optionalAccess', _5 => _5.frames])) {
  47. continue;
  48. }
  49. // We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache.
  50. // This ends up prioritizes source context for frames at the top of the stack instead of the bottom.
  51. for (let i = exception.stacktrace.frames.length - 1; i >= 0; i--) {
  52. const frame = exception.stacktrace.frames[i];
  53. // Call cache.get to bump the file to the top of the cache and ensure we have not already
  54. // enqueued a read operation for this filename
  55. if (frame.filename && !enqueuedReadSourceFileTasks[frame.filename] && !FILE_CONTENT_CACHE.get(frame.filename)) {
  56. readSourceFileTasks.push(_readSourceFile(frame.filename));
  57. enqueuedReadSourceFileTasks[frame.filename] = 1;
  58. }
  59. }
  60. }
  61. }
  62. // check if files to read > 0, if so, await all of them to be read before adding source contexts.
  63. // Normally, Promise.all here could be short circuited if one of the promises rejects, but we
  64. // are guarding from that by wrapping the i/o read operation in a try/catch.
  65. if (readSourceFileTasks.length > 0) {
  66. await Promise.all(readSourceFileTasks);
  67. }
  68. // Perform the same loop as above, but this time we can assume all files are in the cache
  69. // and attempt to add source context to frames.
  70. if (contextLines > 0 && _optionalChain([event, 'access', _6 => _6.exception, 'optionalAccess', _7 => _7.values])) {
  71. for (const exception of event.exception.values) {
  72. if (exception.stacktrace && exception.stacktrace.frames) {
  73. await addSourceContextToFrames(exception.stacktrace.frames, contextLines);
  74. }
  75. }
  76. }
  77. return event;
  78. }
  79. /** Adds context lines to frames */
  80. function addSourceContextToFrames(frames, contextLines) {
  81. for (const frame of frames) {
  82. // Only add context if we have a filename and it hasn't already been added
  83. if (frame.filename && frame.context_line === undefined) {
  84. const sourceFileLines = FILE_CONTENT_CACHE.get(frame.filename);
  85. if (sourceFileLines) {
  86. try {
  87. utils.addContextToFrame(sourceFileLines, frame, contextLines);
  88. } catch (e) {
  89. // anomaly, being defensive in case
  90. // unlikely to ever happen in practice but can definitely happen in theory
  91. }
  92. }
  93. }
  94. }
  95. }
  96. // eslint-disable-next-line deprecation/deprecation
  97. /**
  98. * Reads file contents and caches them in a global LRU cache.
  99. * If reading fails, mark the file as null in the cache so we don't try again.
  100. *
  101. * @param filename filepath to read content from.
  102. */
  103. async function _readSourceFile(filename) {
  104. const cachedFile = FILE_CONTENT_CACHE.get(filename);
  105. // We have already attempted to read this file and failed, do not try again
  106. if (cachedFile === null) {
  107. return null;
  108. }
  109. // We have a cache hit, return it
  110. if (cachedFile !== undefined) {
  111. return cachedFile;
  112. }
  113. // Guard from throwing if readFile fails, this enables us to use Promise.all and
  114. // not have it short circuiting if one of the promises rejects + since context lines are added
  115. // on a best effort basis, we want to throw here anyways.
  116. // If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it
  117. let content = null;
  118. try {
  119. const rawFileContents = await readTextFileAsync(filename);
  120. content = rawFileContents.split('\n');
  121. } catch (_) {
  122. // if we fail, we will mark the file as null in the cache and short circuit next time we try to read it
  123. }
  124. FILE_CONTENT_CACHE.set(filename, content);
  125. return content;
  126. }
  127. exports.ContextLines = ContextLines;
  128. exports.contextLinesIntegration = contextLinesIntegration;
  129. //# sourceMappingURL=contextlines.js.map