Source Maps

edit

It is common practice to minify client-side JavaScript code for several reasons, e.g. performance gain. This can make debugging very difficult, as it is hard to read the minified files. Source mapping can help for debugging minfied JavaScript files, by mapping code from the minified files to the original source code.

APM Server has a source map upload endpoint which accepts source maps complying to the Source map revision 3 proposal.

Uploaded source maps are used to map stack trace information from recorded transaction and error documents to the original source code files for easier debugging.

How source maps are applied

edit

When source maps have been uploaded and frontend support is enabled, source mapping is automatically applied to the stack trace frames of all errors and transactions recorded with the JavaScript frontend agent.

The server tries to find an uploaded source map for every stack trace frame of the record. The following information is used to find the previously uploaded source map entry:

  • the record’s service.name is matched against the source map’s service_name
  • the record’s service.version is matched against the source map’s service_version
  • the stack trace frame’s abs_path is matched against the source map’s bundle_filepath

If multiple source maps with the same meta information are found, the source map with the latest upload timestamp is used.

In case a matching source map is found and the source map can be applied to the stack trace frame, the frame’s information is updated with the mapped information before the record is indexed. The following information is changed to reflect the original source code file, when source mapping is applied:

  • filename
  • function
  • line number
  • column number
  • abs path: is cleaned to be the shortest path name equivalent to the given path name

Upload Endpoint

edit

To upload a source map you need to send a HTTP POST request with Content-Type set to multipart/form-data to the APM Server source maps endpoint:

http(s)://{hostname}:{port}/v1/client-side/sourcemaps
Request Fields
edit

The request consists of some meta information and the actual source map. The meta information must contain the following fields:

  • service_name
  • service_version
  • bundle_filepath: needs to be the absolute path of the final bundle as it is used in the web application

The meta information is used to identify a source map when source mapping is applied.

The actual source map must be attached to the request as a file upload and it must match the specification for Source map revision 3 proposal.

Example

edit

Send an example source map to the APM Server:

curl -X POST http://127.0.0.1:8200/v1/client-side/sourcemaps \
  -F service_name="test-service"
  -F service_version="1.0"
  -F bundle_filepath="/static/js/main.js.map"
  -F sourcemap=@docs/data/intake-api/generated/sourcemap/bundle.js.map
Source map json file
edit

Example of an acceptable source map file:

{
    "version": 3,
    "sources": [
        "webpack:///bundle.js",
        "webpack:///webpack/bootstrap 6002740481c9666b0d38",
        "webpack:///./scripts/index.js",
        "webpack:///./index.html",
        "webpack:///./scripts/app.js"
    ],
    "names": [
        "modules",
        "__webpack_require__",
        "moduleId",
        "installedModules",
        "exports",
        "module",
        "id",
        "loaded",
        "call",
        "m",
        "c",
        "p",
        "foo",
        "console",
        "log",
        "foobar"
    ],
    "mappings": "CAAS,SAAUA,GCInB,QAAAC,GAAAC,GAGA,GAAAC,EAAAD,GACA,MAAAC,GAAAD,GAAAE,OAGA,IAAAC,GAAAF,EAAAD,IACAE,WACAE,GAAAJ,EACAK,QAAA,EAUA,OANAP,GAAAE,GAAAM,KAAAH,EAAAD,QAAAC,IAAAD,QAAAH,GAGAI,EAAAE,QAAA,EAGAF,EAAAD,QAvBA,GAAAD,KAqCA,OATAF,GAAAQ,EAAAT,EAGAC,EAAAS,EAAAP,EAGAF,EAAAU,EAAA,GAGAV,EAAA,KDMM,SAASI,EAAQD,EAASH,GE3ChCA,EAAA,GAEAA,EAAA,GAEAW,OFmDM,SAASP,EAAQD,EAASH,GGxDhCI,EAAAD,QAAAH,EAAAU,EAAA,cH8DM,SAASN,EAAQD,GI9DvB,QAAAQ,KACAC,QAAAC,IAAAC,QAGAH",
    "file": "bundle.js",
    "sourcesContent": [
        "/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t// Webpack\n\t__webpack_require__(1)\n\t\n\t__webpack_require__(2)\n\t\n\tfoo()\n\n\n/***/ },\n/* 1 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tmodule.exports = __webpack_require__.p + \"index.html\"\n\n/***/ },\n/* 2 */\n/***/ function(module, exports) {\n\n\tfunction foo() {\n\t    console.log(foobar)\n\t}\n\t\n\tfoo()\n\n\n/***/ }\n/******/ ]);\n\n\n/** WEBPACK FOOTER **\n ** bundle.js\n **/",
        " \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap 6002740481c9666b0d38\n **/",
        "// Webpack\nrequire('../index.html')\n\nrequire('./app')\n\nfoo()\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./scripts/index.js\n ** module id = 0\n ** module chunks = 0\n **/",
        "module.exports = __webpack_public_path__ + \"index.html\"\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./index.html\n ** module id = 1\n ** module chunks = 0\n **/",
        "function foo() {\n    console.log(foobar)\n}\n\nfoo()\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./scripts/app.js\n ** module id = 2\n ** module chunks = 0\n **/"
    ],
    "sourceRoot": ""
}