Node Advanced Features
Deploy Tracking
Scout can track deploys, making it easier to correlate specific deploys to changes in performance.
To ensure scout tracks your deploy, please provide the SCOUT_REVISION_SHA
environment variable. You may also set the revisionSHA
on a ScountConfiguration
object instance:
const config = scout.buildScoutConfiguration({
monitor: true,
key: "<app key>",
name: "<app name>",
revisionSHA: "<sha>",
});
Request Queuing
Our Node integration can measure the time it takes a request to reach your application from farther upstream (a load balancer or web server). This appears in Scout as “Request Queueing” and provides an indication of your application’s capacity. Large request queueing time is an indication that your app needs more capacity.
Please view request queueing section to learn how to get these insights.
Custom Context
Context lets you see the key attributes of requests. For example, you can add custom context to answer critical questions like:
- Which plan was the customer who had a slow request on?
- How many users are impacted by slow requests?
- How many trial customers are impacted by slow requests?
- How much of an impact are slow requests having on our highest paying customers?
It’s simple to add custom context to your app:
// Express only: Add context inside a handler function
app.get("/", (req, req) => {
scout.api.Context.add("Key", "Value"); // returns a Promise
})
Context Key Restrictions
The Context key
must be a String
with only printable characters. Custom
context keys may contain alphanumeric characters, dashes, and underscores.
Spaces are not allowed.
Attempts to add invalid context will be ignored.
Context Value Types
Context values can be any json-serializable type. Examples:
"1.1.1.1"
"free"
100
PII
To best help protect your data, we suggest using ids instead of explicit names and emails
Custom Instrumentation
You can extend Scout to trace transactions outside our officially supported libraries (e.g. Cron jobs and other web frameworks) and time the execution of sections of code that falls outside our provided instrumentation.
Asynchronous functionality can be marked as a transaction with code similar to the following:
scout.api.WebTransaction.run("transaction-name", (finishTransaction) => {
yourAsyncFunction()
.then(() => finishTransaction())
.catch(err => {
// error handling code goes here
finishTransaction();
});
});
For Asynchronous functionality in a callback-passing style:
scout.api.WebTransaction.run("transaction-name", (finishTransaction) => {
yourCallbackStyleAsyncFunction((err) => {
if (err) {
// error handling code goes here
return;
}
finishTransaction();
});
});
Synchronous functionality can be marked as transactions with code similar to the following:
scout.api.WebTransaction.runSync("sync-transaction-name", () => {
yourSyncFunction();
});
Transactions & Timing
Scout’s instrumentation is divided into 2 areas:
- Transactions: these wrap around an entire flow of work, like a web request or Cron job. The Scout Web UI groups data under transactions.
- Timing: these measure small pieces of code that occur inside of a transaction, like an HTTP request to an outside service, or a database call. This is displayed within a transaction trace in the UI.
Instrumenting Transactions
A transaction groups a sequence of work under in the Scout UI. These are used to generate transaction traces. For example, you may create a transaction that wraps around the entire execution of a NodeJS script that is ran as a Cron Job.
The Express integration does this all for you. You only will need to manually instrument transactions in special cases. Contact us at support@scoutapm.com for help.
Limits
We limit the number of unique transactions that can be instrumented. Tracking too many uniquely named transactions can impact the performance of the UI. Do not dynamically generate transaction names in your instrumentation as this can quickly exceed our rate limits. Use context to add high-dimensionality information instead.
Web or Background transactions?
Scout distinguishes between two types of transactions:
WebTransaction
: For transactions that impact the user-facing experience. Time spent in these transactions will appear on your app overboard dashboard and appear in the “Web” area of the UI.BackgroundTransaction
: For transactions that don’t have an impact on the user-facing experience (example: cron jobs). These will be available in the “Background Jobs” area of the UI.
scout.api.WebTransaction.run("GET /users", () => { ... your code ... });
scout.api.BackgroundTransaction.run("your-bg-transaction", () => { ... your code ... });
Timing functions and blocks of code
In existing transactions, both automatically created with Express instruments, and also manually created, you can time sections of code that are interesting to your application.
Traces that allocate significant amount of time to Controller
or Job
layers
are good candidates to add custom instrumentation. This indicates a significant
amount of time is falling outside our default instrumentation.
Asynchronous functionality may be instrumented with code similar to the following:
// NOTE: The transaction is *implicit* inside of express route handlers, if you are using the express middleware
scout.api.WebTransaction.run("transaction-name", (finishTransaction) => {
// Start the first instrumentation
const first = scout.api.instrument("instrument-name", (finishInstrument) => {
// instrument code
return yourAsyncFunction()
.then(() => finishInstrument());
});
// Start the second instrumentation
scout.api.instrument("instrument-name", (finishInstrument) => {
// instrument code
return yourAsyncFunction()
.then(() => finishInstrument());
});
// Finish the transaction once all instrumentations are recorded
Promise.all([first, second])
.then(() => finishTransaction());
});
For Asynchronous functionality in a callback-passing style:
// NOTE: The transaction is *implicit* inside of express route handlers, if you are using the express middleware
scout.api.WebTransaction.run("transaction-name", (finishTransaction) => {
// Start the first instrumentation
const first = scout.api.instrument("first-instrumentation", (finishFirst) => {
// instrument code
yourCallbackStyleAsyncFunction((err) => {
if (err) {
// error handling code here
return;
}
finishFirst();
// Start a second instrumentation
const second = scout.api.instrument("second-instrumentation", (finishSecond) => {
// instrument code
yourCallbackStyleAsyncFunction((err) => {
if (err) {
// error handling code here
return;
}
finishSecond();
finishTransaction();
});
});
});
});
});
Synchronous functionality can be instrumented with code similar to the following:
// NOTE: The transaction is *implicit* inside of express route handlers, if you are using the express middleware
scout.api.WebTransaction.runSync("sync-transaction-name", (finishTransaction) => {
scout.api.instrumentSync("first-instrumentation", () => {
yourSyncFunction();
});
scout.api.instrumentSync("second-instrumentation", () => {
yourSyncFunction();
});
});
Limits
We limit the number of metrics that can be instrumented. Tracking too many unique metrics can impact the performance of our UI. Do not dynamically generate metric types in your instrumentation as this can quickly exceed our rate limits.
For high-cardinality details, use context.
Getting Started
With existing code like:
// A handler that handles GET /
const handler = (req, res) => {
// Functionality here
};
The express middleware automatically wraps your request and handler with a transaction/instrumentation as if you’d written the following:
scout.api.WebTransaction.run("Controller/GET /<your route>", finishTransaction => { // transaction name format is `<kind>/<name>`
scout.api.instrument("Controller/GET /<your route>", finishSpan => { // instrumentation name format is `<kind>/<name>`
// The original handler code
});
});
-
kind
- A high level area of the application. This defaults toCustom
. Your whole application should have a very low number of unique strings here. In our built-in instruments, this is things likeTemplate
andSQL
. For custom instrumentation, it can be strings likeMongoDB
orHTTP
or similar. This should not change based on input or state of the application. -
name
- A semi-detailed version of what the section of code is. It should be static between different invocations of the method. Individual details like a user ID, or counts or other data points can be added as tags. Names likeretreive_from_api
orGET
are good names. -
span
- An object that represents instrumenting this section of code. You can set tags on it by calling$span->tag("key", "value")
-
tags
- A dictionary of key/value pairs. Key should be a string, but value can be any json-able structure. High-cardinality fields like a user ID are permitted.ARM and graviton Support
We now have support for ARM and graviton.
- Our Node agent does not automatically detect ARM support, currently
- To explicitly connect, the core_agent_triple configuration setting must be specified.
- Our Node agent does not automatically detect ARM support, currently