dd-trace
Version: 
Datadog APM tracing client for JavaScript
135 lines (118 loc) • 4.34 kB
JavaScript
/**
 * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * Modifications copyright 2022 Datadog, Inc.
 *
 * Some functions are part of aws-lambda-nodejs-runtime-interface-client
 * https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/utils/UserFunction.ts
 */
const path = require('path')
const log = require('../../log')
const { getEnvironmentVariable } = require('../../config-helper')
const Hook = require('../../../../datadog-instrumentations/src/helpers/hook')
const instrumentations = require('../../../../datadog-instrumentations/src/helpers/instrumentations')
const {
  filename,
  pathSepExpr
} = require('../../../../datadog-instrumentations/src/helpers/register')
/**
 * Breaks the full handler string into two pieces: the module root
 * and the actual handler string.
 *
 * @param {string} fullHandler user's lambda handler, commonly stored in `DD_LAMBDA_HANDLER`.
 * @returns {string[]} an array containing the module root and the handler string.
 *
 * ```js
 * _extractModuleRootAndHandler('./api/src/index.nested.handler')
 * // => ['./api/src', 'index.nested.handler']
 * ```
 */
function _extractModuleRootAndHandler (fullHandler) {
  const handlerString = path.basename(fullHandler)
  const moduleRoot = fullHandler.slice(0, Math.max(0, fullHandler.indexOf(handlerString)))
  return [moduleRoot, handlerString]
}
/**
 * Splits the handler string into two pieces: the module name
 * and the path to the handler function.
 *
 * @param {string} handler a handler string containing the module and the handler path.
 * @returns {string[]} an array containing the module name and the handler path.
 *
 * ```js
 * _extractModuleNameAndHandlerPath('index.nested.handler')
 * // => ['index', 'nested.handler']
 * ```
 */
function _extractModuleNameAndHandlerPath (handler) {
  const FUNCTION_EXPR = /^([^.]*)\.(.*)$/
  const match = handler.match(FUNCTION_EXPR)
  if (!match || match.length !== 3) {
    // Malformed Handler Name
    return // TODO: throw error
  }
  return [match[1], match[2]] // [module, handler-path]
}
/**
 * Returns all possible paths of the files to be patched when required.
 *
 * @param {*} lambdaStylePath the path comprised of the `LAMBDA_TASK_ROOT`,
 * the root of the module of the Lambda handler, and the module name.
 * @returns the lambdaStylePath with appropiate extensions for the hook.
 */
function _getLambdaFilePaths (lambdaStylePath) {
  return [
    `${lambdaStylePath}.js`,
    `${lambdaStylePath}.mjs`,
    `${lambdaStylePath}.cjs`
  ]
}
/**
 * Register a hook for the Lambda handler to be executed when
 * the file is required.
 */
const registerLambdaHook = () => {
  const lambdaTaskRoot = getEnvironmentVariable('LAMBDA_TASK_ROOT')
  const originalLambdaHandler = getEnvironmentVariable('DD_LAMBDA_HANDLER')
  if (originalLambdaHandler !== undefined && lambdaTaskRoot !== undefined) {
    const [moduleRoot, moduleAndHandler] = _extractModuleRootAndHandler(originalLambdaHandler)
    const [_module] = _extractModuleNameAndHandlerPath(moduleAndHandler)
    const lambdaStylePath = path.resolve(lambdaTaskRoot, moduleRoot, _module)
    const lambdaFilePaths = _getLambdaFilePaths(lambdaStylePath)
    // TODO: Redo this like any other instrumentation.
    Hook(lambdaFilePaths, (moduleExports, name) => {
      require('./patch')
      for (const { hook } of instrumentations[name]) {
        try {
          moduleExports = hook(moduleExports)
        } catch (e) {
          log.error('Error executing lambda hook', e)
        }
      }
      return moduleExports
    })
  } else {
    const moduleToPatch = 'datadog-lambda-js'
    Hook([moduleToPatch], (moduleExports, moduleName, _) => {
      moduleName = moduleName.replace(pathSepExpr, '/')
      require('./patch')
      for (const { name, file, hook } of instrumentations[moduleToPatch]) {
        const fullFilename = filename(name, file)
        if (moduleName === fullFilename) {
          try {
            moduleExports = hook(moduleExports)
          } catch (e) {
            log.error('Error executing lambda hook for datadog-lambda-js', e)
          }
        }
      }
      return moduleExports
    })
  }
}
module.exports = {
  _extractModuleRootAndHandler,
  _extractModuleNameAndHandlerPath,
  _getLambdaFilePaths,
  registerLambdaHook
}