async function frameFromCallSite()

in lib/stacktraces.js [287:452]


async function frameFromCallSite(
  log,
  callsite,
  cwd,
  sourceLinesAppFrames,
  sourceLinesLibraryFrames,
) {
  // getFileName can return null, e.g. with a `at Generator.next (<anonymous>)` frame.
  const filename = filePathFromCallSite(callsite) || '';
  const lineno = callsite.getLineNumber();
  const colno = callsite.getColumnNumber();

  // Caching
  const cacheKey = [
    filename,
    lineno,
    colno,
    sourceLinesAppFrames,
    sourceLinesLibraryFrames,
  ].join(':');
  const cachedFrame = frameCache.get(cacheKey);
  if (cachedFrame !== undefined) {
    frameCacheStats.hits++;

    // Guard against later JSON serialization mistakenly changing duplicate
    // frames in a stacktrace (a totally legal thing) into '[Circular]' as a
    // guard against serializing an object with circular references.
    const clonedFrame = Object.assign({}, cachedFrame);
    if (clonedFrame.pre_context) {
      clonedFrame.pre_context = clonedFrame.pre_context.slice();
    }
    if (clonedFrame.post_context) {
      clonedFrame.post_context = clonedFrame.post_context.slice();
    }

    return clonedFrame;
  }

  function cacheIt(frame) {
    frameCacheStats.misses++;
    frameCache.set(cacheKey, frame);
  }

  let mappedFilename = null;
  let absMappedFilename = null;
  let mappedLineno = null;

  // If the file has a sourcemap, we use that for: filename, lineno, source
  // context.
  let sourceMapConsumer = null;
  try {
    sourceMapConsumer = await getSourceMapConsumer(callsite);
  } catch (sourceMapErr) {
    log.debug(
      { filename, err: sourceMapErr },
      'could not process file source map',
    );
  }
  if (sourceMapConsumer) {
    let pos;
    try {
      pos = sourceMapConsumer.originalPositionFor({
        line: lineno,
        column: colno,
      });
    } catch (posErr) {
      log.debug(
        { filename, line: lineno, err: posErr },
        'could not get position from sourcemap',
      );
      pos = {
        source: null,
        line: null,
        column: null,
        name: null,
      };
    }
    if (pos.source !== null) {
      mappedFilename = pos.source;
      absMappedFilename = path.resolve(path.dirname(filename), mappedFilename);
    }
    if (pos.line !== null) {
      mappedLineno = pos.line;
    }
    // TODO: Is `pos.name` relevant for `frame.function` if minifying?
  }

  const isApp = isCallSiteApp(callsite);
  const frame = {
    filename: getRelativeFileName(absMappedFilename || filename, cwd),
    lineno: mappedLineno || lineno,
    function: getCallSiteFunctionNameSanitized(callsite),
    library_frame: !isApp,
  };
  if (!Number.isFinite(frame.lineno)) {
    // An early comment in stackman suggested this is "sometimes not" an int.
    frame.lineno = 0;
  }
  if (filename) {
    frame.abs_path = absMappedFilename || filename;
  }

  // Finish early if we do not need to collect source lines of context.
  var linesOfContext = isApp ? sourceLinesAppFrames : sourceLinesLibraryFrames;
  if (linesOfContext === 0 || !filename || isCallSiteNode(callsite)) {
    cacheIt(frame);
    return frame;
  }

  // Attempt to use "sourcesContent" in a sourcemap, if available.
  if (sourceMapConsumer && mappedFilename && mappedLineno) {
    // To use `sourceMapConsumer.sourceContentFor` we need the filename as
    // it is in the "sources" field of the source map. `mappedFilename`,
    // from `sourceMapConsume.originalPositionFor` above, was made relative
    // to "sourceRoot" -- sourceFilename undoes that.
    const sourceFilename = sourceMapConsumer.sourceRoot
      ? path.relative(sourceMapConsumer.sourceRoot, mappedFilename)
      : mappedFilename;
    var source = sourceMapConsumer.sourceContentFor(sourceFilename, true);
    log.trace(
      {
        sourceRoot: sourceMapConsumer.sourceRoot,
        mappedFilename,
        sourceFilename,
        haveSourceContent: !!source,
      },
      'sourcemap sourceContent lookup',
    );
    if (source) {
      addSourceContextToFrame(
        frame,
        source.split(/\r?\n/g),
        mappedLineno,
        linesOfContext,
      );
      cacheIt(frame);
      return frame;
    }
  }

  // If the file looks like it minimized (as we didn't have a source-map in
  // the processing above), then skip adding source context because it
  // is mostly useless and the typically 500-char lines result in over-large
  // APM error objects.
  if (filename.endsWith('.min.js')) {
    cacheIt(frame);
    return frame;
  }

  // Otherwise load the file from disk, if available.
  let lines;
  try {
    lines = await fileCache.fetch(frame.abs_path);
  } catch (fileErr) {
    log.debug(
      { filename: frame.abs_path, err: fileErr },
      'could not read file for source context',
    );
  }
  if (lines) {
    addSourceContextToFrame(frame, lines, frame.lineno, linesOfContext);
  }

  cacheIt(frame);
  return frame;
}