JavaScript agent for the AppMap framework.
To install and configure appmap-agent-js
, see Getting started.
Table of contents:
The agent provides a CLI to spawn and record node processes.
By default, the agent will look for a configuration file at ./appmap.yml
.
The configuration format is detailed here.
- Named arguments Any configuration field.
This takes precedence over the options from the configuration file.
For instance:
Aliases:
npx appmap-agent-js --name my-appmap-name --app my-app-name
Alias Corresponding Name --log-level
log.level
--log-file
log.file
--app-port
intercept-track-port
--alt-remote-port
track-port
--appmap-dir
appmap_dir
In addition, the command can be encoded as positional argument. For instance, these commands have the same effect: npx appmap-agent-js -- node main.js npx appmap-agent-js --command 'node main.js'
- Environment variables
APPMAP_CONFIGURATION_PATH
: path to the configuration file, default:./appmap.yml
.APPMAP_REPOSITORY_DIRECTORY
: directory to the project's home directory, default:.
. Requirements:- [mandatory] Access to the
@appland/appmap-agent-js
npm module. - [preferred] Be a git repository.
- [preferred] Contain a valid
package.json
file.
- [mandatory] Access to the
The first option to automatically generate appmaps is called process recording. It involves recording node processes from start to finish and write the resulting trace to local files.
recorder: process
command: "node bin/bin.js argv1 argv2" # the usual command for running the project
appmap_dir: tmp/appmap
The second option to automatically generate appmaps is called mocha test case recording.
It involves recording mocha test cases (ie it
calls) in separate traces and write each one of them in separate files.
Note that mocha run the entire test suite within a single node process.
Hence all the exercised parts of the application will probably end up being included into every generated appmap.
The pruning
configuration option can be used to solve this issue.
If enabled, the appmap will be stripped of the elements of the classmap that did not cause any function applications.
recorder: mocha
command: "mocha --recursive 'test/**/*.js'" # the usual command for running mocha tests
pruning: true
Note that the agent will expect the mocha
executable as first token of the command.
It is also possible to run mocha via npx
.
However this will cause the recording of the npx
process as well.
To avoid recording this process, it should be blacklisted.
recorder: mocha
command: "npx mocha --recursive 'test/**/*.js'" # the usual command for running mocha tests
pruning: true
processes:
regexp: "/npx$" # do not record node processes whose entry script ends with npx
enabled: false
The third option to automatically generate appmaps is on-demand via HTTP requests. The remote recording web API is documented here.
recorder: remote
track-port: 8080
intercept-track-port: 8000
Remote recording requests can be delivered to two possible end points:
Configuration Field | Routing | Comment | |
---|---|---|---|
Dedicated Backend Port | track-port |
/{session}/{track} |
If session is "_appmap" then the (assumed) single active session will be selected. |
Intercept Frontend Port | intercept-track-port |
/_appmap/{track} |
Will not be active until the application deploy an HTTP server on that port. |
The agent also provides an API to manually record events in the node process in which it is imported.
import {createAppMap} from "@appland/appmap-agent-js";
// NB: Only a single concurrent appmap is allowed per process
const appmap = createAppMap(
repository_directory, // default: process.cwd()
configuration, // default: {}
configuration_directory, // default: repository_directory
);
// NB: An appmap can create multiple (concurrent) tracks
const track = "my-identifier";
appmap.startRecording(track, {
app: "my-app-name",
name: "my-appmap-name",
pruning: true,
recording: {
"defined-class": "defined-class",
"method-id": "method-id",
},
});
appmap.recordScript(
"(function main () { return 123; } ());",
"path/to/main.js",
);
const trace = appmap.stopRecording(track);
console.log(JSON.stringify(trace, null, 2));
appmap.terminate();
home <string>
The file url of the project. Default: file url of the current working directory.configuration <object>
Root configuration. Default:{}
.base <string>
The file url of the directory to resolve the relative paths of the configuration argument.- Returns
<appmap>
an appmap instance and listen to different event emitters.
Stop listening to events. Subsequent method invocations will throw exception.
track <string> | null
An identifier for the track. Default:null
, a random string will be usedconfiguration <object>
Configuration for extending the configuration of the appmap instance. Default:{}
.base <string> | null
: The file url of the directory to resolve the relative paths of the configuration argument. Default:null
, the presence of relative paths will throw an error.- Returns
<string>
the identifier for the track. This is useful when providingnull
for thetrack
argument.
track <string>
Identifier of the track.- Returns
<object>
the recorded trace in the appmap format -- ie: a JSON object.
content <string>
Script content.url <string>
Script location.- Returns
<any>
the completion value of the script.
The actual format requirements for configuration can be found as a json-schema here.
The agent filter files based on a format called Specifier
.
A specifier can be any of:
<RegexpSecifier>
Filter files based on a regular expression.regexp <string>
The regular expression's source.flags <string>
The regular expression's flags. Default:"u"
.
<GlobSpecifier>
Filter files based on a glob expressionglob <string>
The glob expression
<PathSpecifier>
Filter files based on a pathpath <string>
Path to a file or a directory (without trailing/
).recursive <boolean>
Indicates whether to whitelist files within nested directories. Default:true
.
<DistSpecifier>
Filter files based on a npm package name.dist <string>
Relative path that starts with a npm package name. For instance:"package/lib"
.recursive <boolean>
Indicates whether to whitelist files within nested directories. Default:true
.external <boolean>
Indicates whether to whitelist dependencies outside of the repository. Default:false
.
The agent filter code objects (functions or objects/classes) based on a format called Exclusion
. Which can be either a string or an object:
<string>
Shorthand,"foo\\.bar"
is the same as{"qualified-name":"foo\\.bar"}
<object>
combinator "and" | "or"
Indicates whether the four criteria -- ie:name
,qualified-name
,some-label
, andevery-label
-- should all be satisfied or if at least one should be satisfied. These criterion has two form: the pattern form which is a string or the static boolean form. Ifcombinator
is"and"
then the default value for these criterion istrue
. If the combinator is"or"
then the default value for these criterion isfalse
. Default: "and".name <string> | <boolean>
A pattern to match against the name of the code object. The agent will assign static names to code object with an algorithm that resemble the ECMAScript function naming algorithm.qualified-name <string> | <boolean>
A pattern to match against the qualified name of the code object. For classes/objects, the qualified name is the same as the name. For functions which are methods, the name of their enclosing object/class are prepended. For instance:foo#bar
for a static method namedbar
defined on an object namedfoo
.some-label <string> | <boolean>
A pattern that should match at least one label of the function. This criterion is not applicable to classes/objects. The only way for a function without labels to satisfy this criterion is to use the boolean form.every-label <string> | <boolean>
A pattern that should match all labels of the function. This criterion is not applicable to classes/objects. The only way for a function without labels to not satisfy this criterion is to use the boolean form.excluded <boolean>
Indicates whether the matching code object should be excluded or not. Default:true
.recursive <boolean>
Ifexcluded
istrue
, this indicates whether the children of the matched code object should be excluded as well. Default:true
.
command <string> | <string[]>
The command to record. It is either a string or a list of tokens that will be escaped with single quotes. It follows that only simple commands can currently be executed -- eg: piping and chaining is not possible. This limitation might be lifted in the future.command-win32 <string> | <string[]>
Same ascommand
but is preferred on Windows. This enables to configure a Windows-specific command such asnpx.cmd jest
.command-options <object>
Options to run the command, inspired by node'schild_process
library.shell null | string[]
An optional prefix for executing the command. Ifnull
, it will be["/bin/sh", "-c"]
on unix-like platforms and["cmd.exe", "/c"]
on windows (with support for theComSpec
environment variable).env <object>
Environment variables. Note that Unlike for the child_process#spawn, the environment variables from the parent process will always be included. Default:{}
-- ie: the environment variables from the parent process.stdio <string> | <string[]>
Stdio configuration, only"ignore"
and"inherit"
are supported.encoding "utf8" | "utf16le" | "latin1"
Encoding of all the child's stdio streams.timeout <number>
The maximum number of millisecond the child process is allowed to run before being killed. Default:0
(no timeout).killSignal <string>
The signal used to kill the child process when it runs out of time. Default:"SIGTERM"
.
recorder "process" | "remote" | "mocha" | "jest"
Defines the main algorithm used for recording. Defaultnull
.null
Will check whether"jest"
or"mocha"
is suitable. Else, it will default to"process"
."process"
Generate a single appmap which spans over the entire lifetime of the process."mocha"
Generate an appmap for each test case (ieit
calls) of the entire test suite (ie everydescribe
calls on every test file)."jest"
Generate an appmap for each test case (ietest
calls) of the entire test suite."remote"
Generate appmap on demand via HTTP requests.
socket "unix" | "net"
Defines the socket implementation to use:posix-socket
ornet.Socket
. Theposix-socket
module provide synchronous methods which avoid creating asynchronous resources that are observed whenordering
is"causal"
. To avoid infinite loop, the"net"
implementation buffers messages. The"unix"
is probably the better option but it requires node-gyp compilation and is not available on windows. If"unix"
is chosen but theposix-socket
could not be installed, the agent will fallback on"net"
. Default:"unix"
.heartbeat <number> | null
Defines the interval in millisecond where the socket should be flushed. This only has effect ifsocket
is"net"
. Default:1000
.threshold <number> | null
Defines the maximum number of message before the socket should be flushed. This only has effect ifsocket
is"net"
. Default:100
.proxy-port <number> | null
Defines where the proxy should be listening to intercept http traffic and record html applications running on the browser. Use0
for a random port. Default:null
which does not deploy the proxy.trace-port <number> | <string>
Defines the communication port between frontend and backend. A string indicates a path to a unix domain socket which is faster. Default:0
which will use a random available port.track-port <number> | <string>
: Port in the backend process for serving remote recording HTTP requests. Default:0
A random port will be used.intercept-track-port <string>
: Regular expression to whitelist the ports in the frontend process for intercepting remote recording HTTP requests. Default:"^"
Every detected HTTP ports will be spied upon.processes <boolean> | <string> | <EnabledSpecifier> | <EnabledSpecifier[]>
Whitelist files to decide whether a node process should be instrumented based on the path of its main module. AnEnabledSpecifier
can be any of<boolean>
Shorthand,true
is the same as{regexp:"^", enabled:true}
andfalse
is the same as{regexp:"^", enabled:false}
.<string>
Shorthand,"test/**/*.mjs"
is the same as{glob:"test/**/*.mjs", enabled:true}
.<object>
enabled <boolean>
Indicates whether whitelisted files are enabled or not. Default:true
.... <Specifier>
Extends from any specifier format. Default:[]
-- ie: the agent will be enabled for every process whose entry script resides in the repository directory.
scenarios <Configuration[]>
An array of child configuration.scenario <string>
A regular expression to whitelist scenarios for execution. If the root configuration contains a command, it will always be executed. Default:"^"
(every scenario will be executed).appmap_dir <string>
Path to directory for storing appmap files. Default:"tmp/appmap"
.appmap_file <string> | null
Base name (ie file name but without the extension) of the file where the appmap data should be written. Default:null
the agent will look at thename
configuration field, if it isnull
as well,"anonymous"
will be used.
log "debug" | "info" | "warning" | "error" | "off"
Usual log levels. Default:"info"
.packages <PackageSpecifier> | <PackageSpecifier[]>
File filtering for instrumentation. APackageSpecifier
can be any of:<string>
: Glob shorthand,"lib/**/*.js"
is the same as{glob: "lib/**/*.js"}
.<object>
enabled <boolean>
Indicates whether the filtered file should be instrumented or not. Default:true
.shallow <boolean>
Indicates whether the filtered file shouldexclude <Exclusion[]>
Additional code object filtering for the matched file.source-type "script" | "module" | null
ThesourceType
options given to@babel/parser
. Default:null
attempt to retrieve this information from the extension of the file, else provide"unambiguous"
.parsing <array>
A set of plugin names to give to@babel/parser
. Options can be given to plugins by providing a pair instead of just the name -- eg:["recordAndtuple", {syntaxType: bar}]
. Default:null
attempt to guess the plugins based on the extension and content of the file.... <Specifier>
Extends from any specifier format.
exclude <Exclusion[]>
Code object filtering to apply to every file.source <boolean>
Indicates whether to include source code in the appmap file. Defaultfalse
.hooks <object>
Flags controlling what the agent intercepts.cjs <boolean>
Indicates whether commonjs modules should be instrumented to record function applications. Default:true
.esm <boolean>
Indicates whether native modules should be instrumented to record function applications. Default:true
for the CLI andfalse
for the API.eval <boolean> | <String[]>
Defines the call expressions that should be considered as eval calls. More precisely, if a call expression has an identitifier as callee whose name appears inhooks.eval
then its first argument will be instrumented as aneval
code.true
is a shorthand for["eval"]
andfalse
is a shorthand for[]
. Default:false
.group <boolean>
Indicates whether asynchronous resources should be monitored to infer causality link between events. This provides more accurate appmaps but comes at the price of performance overhead. Default:true
.http <boolean>
Indicates whetherhttp
should be monkey patched to monitor http traffic. Default:true
.mysql <boolean>
Indicates whethermysql
should be monkey patched to monitor sql queries. The agent will crash if themysql
package is not available. Default:true
.pg <boolean>
Indicates whetherpg
should be monkey patched to monitor sql queries. The agent will crash if thepg
package is not available. Default:true
.sqlite3 <boolean>
Indicates whethersqlite3
should be monkey patched to monitor sql queries. The agent will crash if thesqlite3
package is not available. Default:true
.
ordering "chronological" | "causal"
Default:"causal"
.app <string>
Name of the recorded application. Default:null
the value found inpackage.json
if any.name <string>
Name of the appmap. Default:null
the agent will do its best to come up with a meaningful name.pruning <boolean>
Remove elements of the classmap which did not trigger any function application event. Default:true
.serialization <object>
Serialization options. Many options focus on defining how aggressive the serialization should be. Pure serialization is faster and avoids disturbing the flow of the observed application but is less detailed than impure serialization.maximum-print-length <number> | null
the maximum length of the string representation of values before being truncated.null
indicates no limitation. Default100
.maximum-properties-length <number> | null
the maximum of amount of properties serialized for hash objects. Objects are considered as hashes if their prototype is eithernull
orObject.prototype
.null
indicates no limitation. Default10
.impure-printing <boolean>
indicates whether to use a pure printing algorithm or not. For instance, an object can be printed either usingObject.prototype.toString.call(object)
which is pure orobject.toString()
which is impure. Defaulttrue
.impure-constructor-naming <boolean>
indicates whether the constructor name should be retrieved usingObject.prototype.toString.call(object)
which is pure or usingobject.constructor.name
which is impure. Defaulttrue
.impure-array-inspection <boolean>
indicates whether the length of an array should be retrieved which is an impure operation. Defaulttrue
.impure-error-inspection <boolean>
indicates whether the message and stack of an error should be retrieved which is an impure operation. Defaulttrue
.impure-hash-inspection <boolean>
indicates whether the properties of an hash object should be inspected which is an impure operation. Defaulttrue
.
hidden-identifier <string>
The prefix of hidden variables used by the agent. The instrumentation will fail if variables from the program under recording starts with this prefix. Default:"APPMAP"
.collapse-package-hiearchy <boolean>
Indicates whether packages should organized as a tree which mirrors the structure of the file system or if they should be flatten into a list. Default:true
.validate <boolean> | <object>
Validation options which are useful to debug the agent.<boolean>
Shorthand,true
is the same as{message: true, appmap:true}
andfalse
is the same as{message:true, appmap:true}
.<object>
message <boolean>
Indicates whether to validate trace elements as they are buffered. This is useful to help diagnose the root cause of some bugs. Defaultfalse
.appmap <boolean>
Indicates whether to validate the appmap before writing it to a file. Defaultfalse
.
postmortem-function-exclusion <boolean> | null
Indicates whether functions should be excluded after collecting the trace or during instrumentation. Postmortem function exclusion makes instrumentation faster but may record useless data. Default:null
functions will be excluded during instrumentation if and only if there is no source mapping. In that case we can reuse the parsing from instrumentation.
Warning bumpy road ahead
The appmap framework represent an application as a tree structure called classmap.
The base of a classmap tree mirrors the file structure of the recorded application with nested package
classmap nodes.
Within a file, some estree nodes are selected to be represented as class
classmap nodes based on their type.
The name of these classmap nodes are based on an algorithm that resembles the naming algorithm of functions.
If a node has no such name, a unique indexed name will be provided. Estree nodes of type ObjectExpression
, ClassExpression
, or ClassDeclaration
are qualified as class-like and are directly represented by a class
node.
Estree node of type: ArrowFunctionExpression
, FunctionExpresssion
, and FunctionDeclaration
are qualified as function-like and are represented by a class
classmap node which contains a function
classmap node as first child.
This circumvoluted representation is required because the appmap specification does not allow function
node to contain children. Other estree nodes are not represented in the classmap.
Example:
// main.js
function main () {
class Class {}
}
- type: package
name: main.js
children:
- type: class
name: main
children:
- type: function
name: "()"
- type: class
name: Class
children: []
Excluding some parts of the files for instrumentation is based on qualified name.
A qualified name is based on whether an estree node resides at the value
field of a Property
or a MethodDefinition
.
If it is the case the node is said to be bound, else it is said to be free.
The qualified name of a bound estree node is the combination of the name of its parent classmap node and the name if its classmap node.
The qualified name of a free estree node is the same as the name of its classmap name.
Examples:
function f () {}
isf
const o = { f () {} }
iso.f
class c { f () {} }
isc.f
class c { static () {} }
isc#f