API

How to use RevDeBug API and interact with RevDeBug on the runtime.

Runtime RevDeBug API

RevDeBug exposes its runtime API from a global reachable object named revdebug:

Custom JavaScript project layouts

The RevDeBug runtime code can be incorporated into your project in one of several ways, direct inject into your source or index.html files, local import or global import or separate script load in the case of web projects.

For inject, the injection can be either into a Javascript source file or an HTML-ish index file. For a Javascript injection, a main file must be specified and the RevDeBug code is inserted directly into this file and this file must be the entry point of execution. This may get tricky with import declarations since those are always executed before the actual file that does the importing, and hence their contents are executed first. main must be the first file that executes CHRONOLOGICALLY, not necessarily the topmost level file.

For an inject into an HTML-ish index file an index filename must be provided and this file will be scanned for a <head> section. Once this is found the entire RevDeBug runtime script is inserted as the first script to run in this section.

For local, the RevDeBug runtime is copied into the topmost level directory of the project or subproject as __revdebug.js and the individual project files import or require() this file as needed. In this mode, as with global, there is no main entry point and any of the project files may be executed first. For web projects using this mode the individual Javascript scripts must be loaded as modules.

For global the behavior is different between node and web projects. For a node project the runtime is loaded via import or require() but unlike a local path like import "../__revdebug" the runtime is assumed to be available either globally or as an installed NPM module dependency and is imported with an absolute path like import "revdebug".

For a global web project the runtime is assumed to have been loaded as an individual script previously in an HTML file somewhere via something like <script src="__revdebug.js"></script> where PATH is the top-level path of your web project where the runtime will be copied. This must be loaded and executed before any Javascript files which were instrumented. If you provide an index filename then the script tag described will be automatically inserted into that file, otherwise, you are responsible for loading the RevDeBug runtime somewhere yourself.

Subprojects, RevDeBug and the global scope

RevDeBug works by injecting itself into the global scope where it is running and recording a project in that scope. There can only be one project or subproject per scope because otherwise, their instrumentation IDs would overlap. This means that for a given node or web project you should have only one main project. But if your overall solution comprises a node server and a web component for example then each should be its own subproject. The exception to this is when using WebWorkers or worker threads as those each gets their own global scope when they are created and can thus host a completely different project from the code which created them.

What this means basically is that all code that will run in the same global scope of your solution should be part of the same project. However, if you have multiple types of workers or threads which run in their own global scopes then each one can have its own project with different options. You could include them all in a single project if you wish but they would have to share the same options such as target, runtime and type which may be inconvenient or impossible across subprojects.

Post-processing options and source maps

Post-processing options of RevDeBug allow multilevel mapping of source maps back to original pre-RevDeBug uninstrumented source code in case more processing is done after RevDeBug instrumentation of original source files (like for example with a Typescript compiler or webpack).

This is only really useful for web projects in order to be able to see the original source code in a dev console or correct line numbers in stack traces - RevDeBug will present the recordings on the original source code either way.

In order to preserve the information needed for post-processing you must turn on source maps when instrumenting with RevDeBug, as well as in whatever compilation steps you take after ReDeBug instrumentation. After you have instrumented your source files like this and after completing whatever compilation steps follow RevDeBug, when you have your final output files, you will run revd --post a second time so that it can merge the two levels of source maps (one from the RevDeBug instrumentation pass and the second from the following compilation passes) into a single level of source maps pointing back to the original code.

In order to do this you will need to specify postPath where the final output files live along with their source map files. if postPath is not provided it will default to the root directory of the project. Likewise, you will need to specify postFiles in the same way that files are specified. The postFiles may be a single output webpacked file or multiple files selected via wildcards. An example would be source files of *.ts and postFiles as *.js to process all the Javascript files compiled from original Typescript files.

revd --post will only process output files once, running it again after this will have no effect on files that have already been processed, but will reprocess any individual files which may have been recompiled.

Note: As of the time of writing this RevDeBug only works with individual standalone source map files, it does not work with source maps embedded in Javascript files.

Custom object stringification

If any object has a __revdrec method on itself or in its prototype chain then this method will be called on the object during recording when the value of the object needs to be stringified. The object is assumed to be mutable so this call happens whenever the object is accessed in code that is being recorded. The call is of the form obj.__revdrec(obj) so the actual object can be accessed as this or in case of arrow function as the first argument.

The method should return a string which will be shown as the value of the object at this point. Any exceptions in this method are suppressed but shown as the value of the object. Recording is turned off when this function is called and in fact by default the body of the function is not instrumented by RevDeBug for performance (or actually any function which starts with "__revd").

The size of the string returned by this function is not truncated in any way by RevDeBug so keep this in mind if your function can potentially generate huge stringified values. RevDeBug takes care of detecting and truncating recursive object cycles. RevDeBug's own stringification functions are made available to use in your own stringification function via revdebug.str(obj) or more specifically revdebug.str.type(obj) where type is one of the primitive names function, boolean, number, bigint, array or object. Do not call a type function on an object which is not of that type, if you are not sure of the type then use revdebug.str(obj).

Stringification using this method is not affected by the global mutable mode which is set. Objects with these functions will always be stringified at record time even if this is turned off for all other objects with mutable: 'none'.

Recording code block isolation

This feature allows the isolation of individual async task trees from their siblings in order to get a recording of an individual service handler or request. When you declare an async task block via revdebug.block() you are marking this async context as well as any child async tasks it creates as isolated, meaning that if a revdebug.snapshot() or an revdebug.exception() in this async context or its children it will only include the task and its children in the recording, not siblings or parent. When used in the APM portion of RevDeBug, this allows the isolation of a single given HTTP handler for example which may have an error from any number of other handlers which may be running concurrently.

The particular details of how async context propagates in Node are a little wonky and possibly subject to change in future versions of the V8 Javascript engine used in Node. This means that in order to use the block() functionality correctly you need to understand when the async task context changes in async code (it does not change for example upon initial entry into an async function). That information is beyond the scope of this documentation but is available in the documentation for the Node async_hooks package.

The idea is that at the start of the code you want to isolate you call block = revdebug.block(), and when you are done with the part you want to isolate simply call revdebug.async(). If you need to return synchronous control to some other async task without ending your isolation block yet (it will get control later), do the async() before returning control to non-isolated code. Upon re-entry to your code which you want to isolate execute a lastBlock = block.resync(), execute your code, and return to the previously executing context via block.async(lastBlock). The lastBlock here is important because it allows recursive isolation of code blocks, otherwise the wrong previous context may be set if simply calling async() (though this is ok after the initial revdebug.block() because in this case we are sure we did not recurse into this block.

One last detail about this functionality is that the isolation can be total, only code in the block is shown as part of the recording. Or it can be partial where all code is included in the recording up until the initial revdebug.block(), after which only code that is part of the block is shown. This is specified as either no parameter or a falsey parameter to revdebug.block(falsey) for complete isolation, or a truthy parameter for partial isolation.

Environment Variables

The following environment variables can override various configuration settings at runtime (for NodeJS projects):

    REVDEBUG_APPLICATIONS_HOST
    REVDEBUG_APPLICATIONS_PORT
    REVDEBUG_SECURE
    REVDEBUG_CONNECTION_TIMEOUT
    REVDEBUG_RECONNECT_WAIT
    REVDEBUG_SOLUTION
    REVDEBUG_APPLICATION
    REVDEBUG_VERSION
    REVDEBUG_RELEASE
    REVDEBUG_AUTH

    REVDEBUG_APM
    REVDEBUG_APM_HOST
    REVDEBUG_APM_PORT
    SW_AGENT_NAME
    SW_AGENT_DISABLE_PLUGINS
    SW_AGENT_MAX_BUFFER_SIZE
    SW_COLD_ENDPOINT
    SW_TRACE_IGNORE_PATH
    SW_IGNORE_SUFFIX

    REVDEBUG_MODE
    REVDEBUG_LOGGING
    REVDEBUG_LOGTIME
    REVDEBUG_MUTABLE
    REVDEBUG_RECORDINGS
    REVDEBUG_BACKLOG
    REVDEBUG_BLOCKS
    REVDEBUG_SYMBOLS
    REVDEBUG_CLASSPROPS
    REVDEBUG_FUNCTIONPROPS
    REVDEBUG_STDOBJECTS
    REVDEBUG_TYPEDARRAYS
    REVDEBUG_OBJECTIDS
    REVDEBUG_DEPTH
    REVDEBUG_STRLEN

Note: REVDEBUG_APM, REVDEBUG_BLOCKS, REVDEBUG_CLASSPROPS, REVDEBUG_STDOBJECTS and REVDEBUG_TYPEDARRAYS should be "true" or "false".

Possible minification issues when working on the runtime

  • The pseudoMinify() function which is applied to the runtime when it is injected into javascript or HTML files can potentially break the code if it finds a "// " (slash, slash, space) sequence inside a string constant, so be aware of it.

  • Also, pseudoMinify() will not remove /* */ style comments.

  • If you are getting a minified production runtime that works, but a non-minified one that does not then most likely you did not terminate a "name = function(){}" or "name = class {}" with a semicolon ";". All such assignments must be terminated like this since newlines are removed in pseudo-minification and won't act as delimiters.

Last updated