Using RevDeBug in runtime
How to use RevDeBug during runtime?
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 istalled NPM module dependancy 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.

Instrumentation

In order to record runtime values of variables and things like that RevDeBug instruments your scripts to call into its own runtime to store those values along with information on where they came from. The resulting code runs slower than the original and may be unacceptable for some time-critical sections of your application. For this reason you can turn instrumentation on or off in your code through the use of directives. These directives can apply to individual functions or methods or to an entire script. The instrumentation setting is inherited in nested functions from the script or enclosing function.
The directives are revdon to turn instrumentation on, revdoff to turn it off (for best performance), and revderr for a hybrid where most of the code is not instrumented but functions are wrapped in a try / catch block in order to help exception localization. In modern Javascript engines try / catch blocks are not really a slowdown if no exception occurs so they are not a problem even for functions which need to be performant.

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 of a node server and a web component for example then each should be their own subproject. The exception to this is when using WebWorkers or worker threads as those each get 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 which 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 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

This allows 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.
In order to preserve the information needed for post-processing you must turn on source maps when instrumenting with RevDeBug, as well 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 a postPath where the final output files live along with their source map files. if postPath is not provided it will defult 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 which 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 which 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 if 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 par 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 paramenter 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:
1
REVDEBUG_APPLICATIONS_HOST
2
REVDEBUG_APPLICATIONS_PORT
3
REVDEBUG_SECURE
4
REVDEBUG_CONNECTION_TIMEOUT
5
REVDEBUG_RECONNECT_WAIT
6
REVDEBUG_SOLUTION
7
REVDEBUG_APPLICATION
8
REVDEBUG_VERSION
9
REVDEBUG_RELEASE
10
REVDEBUG_AUTH
11
12
REVDEBUG_APM
13
REVDEBUG_APM_HOST
14
REVDEBUG_APM_PORT
15
SW_AGENT_NAME
16
SW_AGENT_DISABLE_PLUGINS
17
SW_AGENT_MAX_BUFFER_SIZE
18
SW_COLD_ENDPOINT
19
SW_TRACE_IGNORE_PATH
20
SW_IGNORE_SUFFIX
21
22
REVDEBUG_MODE
23
REVDEBUG_LOGGING
24
REVDEBUG_LOGTIME
25
REVDEBUG_MUTABLE
26
REVDEBUG_RECORDINGS
27
REVDEBUG_BACKLOG
28
REVDEBUG_BLOCKS
29
REVDEBUG_SYMBOLS
30
REVDEBUG_CLASSPROPS
31
REVDEBUG_FUNCTIONPROPS
32
REVDEBUG_STDOBJECTS
33
REVDEBUG_TYPEDARRAYS
34
REVDEBUG_OBJECTIDS
35
REVDEBUG_DEPTH
36
REVDEBUG_STRLEN
Copied!
Note: REVDEBUG_APM, REVDEBUG_BLOCKS, REVDEBUG_CLASSPROPS, REVDEBUG_STDOBJECTS and REVDEBUG_TYPEDARRAYS should be "true" or "false".

Runtime API:

These methods are called as revdebug.flush() for example.
Method
flush()
Flush the record buffer of current thread to network if live recording, otherwise does nothing.
exception(error)
Signal an unhandled exception to be recorded in a live recording and sent as a crash event.
setRecMode(mode)
Set the current recording mode to Continuous / Live, OnEvent / Crash or Off. The recording mode can only be set as high as the server allows, so if the server does not allow continuous recording then that will not happen and only unhandled exceptions will be sent to the server. The available recording modes are revdebug.Off, revdebug.Continuous, revdebug.Live, revdebug.OnEvent and revdebug.Crash. Continuous and Live are aliases as are OnEvent and Crash.
snapshot(name)
Send a snapshot of the current thread execution recording, name is optional and will be an automatically generated increasing sequence name if not provided.
str(obj)
Convert object to a string using standard RevDeBug stringification. Intended for use in custom user __revdrec() stringification functions for stringifying subobjects. Enforces maximum depth and protects against circular reference cycles. This function will return an incorrect depth recursion level for objects ourside of a __revdrec() function. For a correct depth there use strd().
strd(obj[, depth])
The way object recursion depth is tracked in stringification necessitates a separate function to be able to get the same recursion depth level for an object outside of __revdrec() custom stringification handlers as inside. Calling this function without a depth parameter anywhere will return an object recursed to the default depth level set for the project, regardless of how many nested stringification levels down the code currently is. You can also specify an explicit depth level for this call, also regardless of current nesting level. Like str() this function also protexts against circular reference cycles.
blk = block([exit])
Open a new recording isolation block. The optional exit flag specifies whether the block should be completely isolated from other blocks or parent if it is falsey. Otherwise a truthy value indicates that the block should be isolated all the way back to the point chronologically where it was started, before which point all executed code will be shown. This is useful for example for APM exit spans (client requests) where you will probably be interested in any setup code which occurred before the actual request is made.
blk.async([prev])
Go asynchronous with this block. Anything past this call chronologically in this async context will not be considered part of the block until a resync(). Async tasks created inside the block that proc after this will still have the context of the block and be considered part of it. The optional last parameter can specify an async task context to return to. This may have been returned by a previous blk.resync() call, or if omitted will be the previous context stored when revdebug.block() or revdebug.resync() was called (which can be accessed as blk.prev).
blk.resync()
Re-synchronize this block with the current async context. Anything past this call chronologically in this async context will be considered part of the block until an async() or the async context ends. This function returns the async task context which was present upon calling. In order to implement a proper chain of potentially multiple layers of async and resync, you should always async() to the block returned form resync(), or async() with nothing if the block was just created with revdebug.block().
revdebug.blk
The current async execution block.

Gotchas if 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 modified 5mo ago