diff --git a/examples/.eslintrc.js b/examples/.eslintrc.js deleted file mode 100644 index 4747b86602..0000000000 --- a/examples/.eslintrc.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2021 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -module.exports = { - rules: { - 'no-console': 'off', - 'node/no-extraneous-require': 'off', - 'node/no-missing-require': 'off' - } -} diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index 2cedd54c25..0000000000 --- a/examples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -newrelic.js diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index b6bed12892..0000000000 --- a/examples/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# node-newrelic examples - -We are in the process of migrating these examples over to [newrelic-node-examples](https://github.com/newrelic/newrelic-node-examples) as more robust applications. If you are looking for the api/background-transactions, api/distributed-tracing, or api/segments examples, they are now [here](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation) as stand-alone applications. - -The *shim* subdirectory and its revised contents will also be moved over to newrelic-node-examples at a later date. - -To request additional examples, please [file an issue](https://github.com/newrelic/node-newrelic/issues)! diff --git a/examples/instrumentation.md b/examples/instrumentation.md new file mode 100644 index 0000000000..940f24cc25 --- /dev/null +++ b/examples/instrumentation.md @@ -0,0 +1,13 @@ + +We have moved our examples and tutorials over to [newrelic-node-examples](https://github.com/newrelic/newrelic-node-examples) as more robust applications. Here are some quick links for implementing custom instrumentation and using our shim API: + +* [instrument](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/instrument) - example application that uses the [newrelic.instrument API](https://newrelic.github.io/node-newrelic/API.html#instrument) and associated [shim API](https://newrelic.github.io/node-newrelic/Shim.html) to instrument a toy queue library called Job Queue +* [instrumentDatastore](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/instrument-datastore) - example application that uses the [newrelic.instrumentDatastore API](https://newrelic.github.io/node-newrelic/API.html#instrumentDatastore) and [datastore shim API](https://newrelic.github.io/node-newrelic/DatastoreShim.html) to instrument a toy datastore called Simple Datastore +* [instrumentMessages](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/instrument-messages) - example application that uses the [newrelic.instrumentMessages API](https://newrelic.github.io/node-newrelic/API.html#instrumentMessages) and associated [messaging shim API](https://newrelic.github.io/node-newrelic/MessageShim.html) to instrument a toy messaging library called Nifty Messages +* [instrumentWebframework](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/instrument-webframework) - example application that uses the [newrelic.instrumentWebframework API](https://newrelic.github.io/node-newrelic/API.html#instrumentWebframework) and associated [WebFramework shim API](https://newrelic.github.io/node-newrelic/WebFrameworkShim.html) to instrument a hypothetical web framework +* [attributesAndEvents](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/attributes-and-events) - example application that demonstrates how to share custom [attributes](https://newrelic.github.io/node-newrelic/API.html#addCustomAttribute) and [events](https://newrelic.github.io/node-newrelic/API.html#recordCustomEvent) +* [backgroundTransactions](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/background-transactions) - example application that uses the newrelic API to create [background transactions](https://newrelic.github.io/node-newrelic/API.html#startBackgroundTransaction) +* [segments](https://github.com/newrelic/newrelic-node-examples/tree/main/custom-instrumentation/segments) - example application that demonstrates how to use the [newrelic.startSegment API](https://newrelic.github.io/node-newrelic/API.html#startSegment) in a variety of cases: callback-based, promise-based, asyncronously, and syncronously +* [distributed tracing](https://github.com/amychisholm03/newrelic-node-examples/blob/main/custom-instrumentation/distributed-tracing) - example application that demonstrates distributed tracing + +To request additional examples, please [file an issue](https://github.com/newrelic/node-newrelic/issues)! diff --git a/examples/jsdoc.json b/examples/jsdoc.json new file mode 100644 index 0000000000..dfd8c9bd3f --- /dev/null +++ b/examples/jsdoc.json @@ -0,0 +1,5 @@ +{ + "instrumentation":{ + "title": "Instrumentation Examples" + } +} \ No newline at end of file diff --git a/examples/shim/Context-Preservation.md b/examples/shim/Context-Preservation.md deleted file mode 100644 index 8ec57c4e30..0000000000 --- a/examples/shim/Context-Preservation.md +++ /dev/null @@ -1,322 +0,0 @@ - -### Introduction - -This tutorial goes over an example instrumentation of a library that loses -transaction state, and some of the difficulties associated with doing so. The -sample instrumentation will be [`generic-pool`][1]. - -Here is the full code for this instrumentation, which will be broken down in -more detail below: - -```js -module.exports = function initialize(agent, generic, moduleName, shim) { - var proto = generic && generic.Pool && generic.Pool.prototype - - function wrapPool(pool) - shim.wrap(pool, 'acquire', function wrapAcquire(shim, original, name, callback) { - return function wrappedAcquire(callback, priority) { - return original.call(this, shim.bindSegment(callback), priority) - } - }) - } - - if (proto && proto.acquire) { - wrapPool(proto) - } else { - shim.wrapReturn(generic, 'Pool', function wrapPooler(shim, original, name, pooler) { - wrapPool(pooler) - }) - } -} -``` - - -### Motivation - -We would like to time the execution of all code that was caused by a certain -event. This package of associated timings is called a transaction, and an http -request is the typical event that caused it. This can be difficult since Node -executes javascript in an asynchronous fashion, leading to potentially disparate -call stacks. - -When javascript is executed by Node, more code can be queued in libuv or v8 to -execute asynchronously through functions like `setTimeout`. These asynchronous -functions will delay execution of their callbacks until some point in the -future, and upon execution there will be no context data stored about the origin -of the callback. These asynchronous boundaries pose an issue when trying to -associate where a particular piece of code was queued. - -As of writing this, there are no general solutions to carrying state over these -asynchronous boundaries. Domains were an attempt at this, but have since been -deprecated. This is one of the important roles of instrumentation. Even if -generating timing information for a certain module isn't desired, it may be -necessary to create instrumentation for it to maintain transaction state over an -asynchronous boundary introduced in the module. - -There are two major symptoms of uninstrumented asynchronous methods: complete -transaction state loss and confounded transactions. Completely losing -transaction state typically manifests in lost data and is most commonly caused -by calls into asynchronous native code that the agent is unaware of. Conflated -transactions can lead to one transaction's data ending up on another, -effectively making the data untrustworthy and useless. - -The way we treat both situations is the same: when the asynchronous function is -invoked, wrap the callback in a closure that will restore context before -executing the original reentry code. - -### Generic Pool Breakdown - -The `generic-pool` module exports a [constructor][2] it uses for its job -pooling. In order to follow execution in the pool, we need to use {@link -Shim#bindSegment} on the [`acquire`][3] method. As of v2 of the library this -method is placed on the prototype of the constructor, wrapping this as so will -suffice: - -```js -module.exports = function initialize(agent, generic, moduleName, shim) { - var proto = generic && generic.Pool && generic.Pool.prototype - - function wrapPool(pool) - shim.wrap(pool, 'acquire', function wrapAcquire(shim, original, name, callback) { - return function wrappedAcquire(callback, priority) { - return original.call(this, shim.bindSegment(callback), priority) - } - }) - } - - - wrapPool(proto) -} -``` - -However, in v1 of the module, there is no prototype on this constructor, and we -must wrap [`acquire`][4] on the constructed object using {@link Shim#wrapReturn} -as so: - -```js -module.exports = function initialize(agent, generic, moduleName, shim) { - function wrapPool(pool) - shim.wrap(pool, 'acquire', function wrapAcquire(shim, original, name, callback) { - return function wrappedAcquire(callback, priority) { - return original.call(this, shim.bindSegment(callback), priority) - } - }) - } - - shim.wrapReturn(generic, 'Pool', function wrapPooler(shim, original, name, pooler) { - wrapPool(pooler) - }) -} -``` - -In order to handle both cases, we will check for `acquire` on the prototype and -wrap on that before defaulting to wrapping the return value of the constructor: - -```js -module.exports = function initialize(agent, generic, moduleName, shim) { - var proto = generic && generic.Pool && generic.Pool.prototype - - function wrapPool(pool) - shim.wrap(pool, 'acquire', function wrapAcquire(shim, original, name, callback) { - return function wrappedAcquire(callback, priority) { - return original.call(this, shim.bindSegment(callback), priority) - } - }) - } - - if (proto && proto.acquire) { - wrapPool(proto) - } else { - shim.wrapReturn(generic, 'Pool', function wrapPooler(shim, original, name, pooler) { - wrapPool(pooler) - }) - } -} -``` - -Note we don't write instrumentation for v3 of the library, since it is promise -based and state should be preserved by our promise instrumentation. - -### Toy example - -In order to fully illustrate the process let's instrument a homemade work -queuing system. Consider the following code which queues functions and executes -them in batches every second: - -```js -'use strict' - -function Queue() { - this.jobs = [] -} - -function run(jobs) { - while (jobs.length) { - jobs.pop()() - } -} - -Queue.prototype.scheduleJob = function scheduleJob(job) { - var queue = this - process.nextTick(function() { - if (queue.jobs.length === 0) { - setTimeout(run, 1000, queue.jobs) - } - queue.jobs.push(job) - }) -} - -module.exports = Queue -``` - -With example code that uses it: - -```js -var Queue = require('jobQueue') -var queue = new Queue() - -queue.scheduleJob(function() { - console.log('this prints first') -}) - -var i = 0 -queue.scheduleJob(function counter() { - console.log(i++) - queue.scheduleJob(counter) -}) -``` - -In order to properly instrument this system, we'd like to call {@link -Shim#bindSegment} on the job functions on their way into the system. To prove -that the instrumentation is broken, let's modify the example to illustrate the -undesired behavior. - -```js -var nr = require('newrelic') - -var Queue = require('jobQueue') -var queue = new Queue() - -nr.startBackgroundTransaction('firstTransaction', function first() { - var transaction = nr.getTransaction() - queue.scheduleJob(function firstJob() { - // Do some work - transaction.end() - }) -}) - -nr.startBackgroundTransaction('secondTransaction', function second() { - var transaction = nr.getTransaction() - queue.scheduleJob(function secondJob() { - // Do some work - - // Transaction state will be lost here because 'firstTransaction' will have - // already ended the transaction - transaction.end() - }) -}) -``` - -Without instrumentation executing this code will cause `'firstTransaction'` to -be the active transaction in both `firstJob` and `secondJob`. This confounding -of transactions is due to the `scheduleJob` method using `setTimeout` to queue -the running of jobs. When the callback to `setTimeout` is called the -transaction that was active when `setTimeout` was invoked will be restored. -Since `'firstTransaction'` is active when `setTimeout` is called, this is restored -and `firstJob` is invoked, ending the transaction. When the queue gets around -to executing `secondJob` the `'firstTransaction'` has already been ended, and -the current executing transaction will be `null`. If `firstJob` didn't end the -transaction, the work done in `secondJob` would be incorrectly associated with -`'firstTransaction'`. Now that we have a test case we can start writing our -instrumentation for the work queue. This behavior can be seen in the UI: - -[confounded transactions][5] - -The instrumentation will be relatively simple, we just need to call {@link -Shim#bindSegment} on the jobs as they are passed in. The purpose of this -instrumentation is to wrap the job in a function that will restore transaction -context for the duration of the job's execution. Note this instrumentation will -not handle timing the queue or the work executed by it, in order to do that we -would use {@link Shim#record}. Enough with the prose, here is the -instrumentation in its entirety: - -```js -var nr = require('newrelic') -nr.instrument( - 'jobQueue', - function onRequire(shim, jobQueue) { - shim.wrap( - jobQueue.prototype, - 'scheduleJob', - function wrapJob(shim, original){ - return function wrappedScheduleJob(job) { - return original.call(this, shim.bindSegment(job)) - } - } - ) - } -) -``` - -After executing the above code before calling `require('jobQueue')` we can tell -the agent to instrument the module on require as so: - -```js -var nr = require('newrelic') - -nr.instrument( - 'jobQueue', - function onRequire(shim, jobQueue) { - shim.wrap( - jobQueue.prototype, - 'scheduleJob', - function wrapJob(shim, original){ - return function wrappedScheduleJob(job) { - return original.call(this, shim.bindSegment(job)) - } - } - ) - } -) - -var Queue = require('jobQueue') -var queue = new Queue() - -nr.startBackgroundTransaction('firstTransaction', function first() { - var transaction = nr.getTransaction() - queue.scheduleJob(function firstJob() { - // Do some work - transaction.end() - }) -}) - -nr.startBackgroundTransaction('secondTransaction', function second() { - var transaction = nr.getTransaction() - queue.scheduleJob(function secondJob() { - // Do some work - - // Transaction state will be lost here because 'firstTransaction' will have - // already ended the transaction - transaction.end() - }) -}) -``` - -The correct instrumentation should be noticeable in the UI as now both -transactions appear, and the timer used by the work queue appears as a segment -in `'firstTransaction'` - -[transaction breakdown][5] - - -### Questions? - -We have an extensive [help site](https://support.newrelic.com/) as well as -[documentation](https://docs.newrelic.com/). If you can't find your answers -there, please drop us a line on the [community forum](https://discuss.newrelic.com/). - -[1]: https://www.npmjs.com/package/generic-pool -[2]: https://github.com/coopernurse/node-pool/blob/v2.5.4/lib/generic-pool.js#L630 -[3]: https://github.com/coopernurse/node-pool/blob/v2.5.4/lib/generic-pool.js#L428 -[4]: https://github.com/coopernurse/node-pool/blob/v1.0.12/lib/generic-pool.js#L262 -[5]: https://docs.newrelic.com/docs/apm/applications-menu/monitoring/transactions-page diff --git a/examples/shim/Datastore-Simple.md b/examples/shim/Datastore-Simple.md deleted file mode 100644 index 4413d30895..0000000000 --- a/examples/shim/Datastore-Simple.md +++ /dev/null @@ -1,302 +0,0 @@ -### Pre-requisite - -{@tutorial Instrumentation-Basics} - -### Introduction - -This tutorial goes over a simple datastore instrumentation. This is directly -pulled from our actual [`cassandra-driver`][1] instrumentation. It is meant to -be an introduction to instrumentation. - -Here is the full code for this instrumentation, don't worry if it doesn't make -sense at first glance, we'll break it down line by line below. - -```js -function instrumentCassandra(shim, cassandra, moduleName) { - shim.setDatastore(shim.CASSANDRA) - - var proto = cassandra.Client.prototype - shim.recordOperation(proto, ['connect', 'shutdown'], new shim.specs.OperationSpec({callback: shim.LAST}) - shim.recordQuery(proto, '_execute', new shim.specs.QuerySpec({query: shim.FIRST, callback: shim.LAST})) - shim.recordBatchQuery(proto, 'batch', new shim.specs.QuerySpec({ - query: findBatchQueryArg, - callback: shim.LAST - })) -} - -function findBatchQueryArg(shim, batch, fnName, args) { - var sql = (args[0] && args[0][0]) || '' - return sql.query || sql -} -``` - -### What to Record - -For a datastore, there are just two types of things to record: operations and -queries. Not all datastores have both actions, for example with Redis you do not -write queries, only operations (or commands in their terminology). - -**Operation** - -Operations are any actions that do not send a query to be executed by the -datastore server. Examples for classic RDBs include connecting to the database, -closing the connection, or setting configurations on the server. Often these are -actions that modify the connection or datastore, but not the data. Operations -are recoded using the [`DatastoreShim`]{@link DatastoreShim} method -[`recordOperation`]{@link DatastoreShim#recordOperation}. - -**Queries** - -Queries are any action that manipulate or fetch data using a specialized query -language. For a SQL database, this is any action that sends SQL code to the -server for execution. These are recorded using the method -[`recordQuery`]{@link DatastoreShim#recordQuery}. In some cases, the datastore -in use may support sending multiple queries in a single request. These are -considered "batch queries" and are recorded using -[`recordBatchQuery`]{@link DatastoreShim#recordBatchQuery}. - -Once we're done, we can view the results on the transaction breakdown page. For -example, this simple Express route handler would generate a graph like the one -below. - -```js -server.get('/shim-demo', function(req, res, next) { - client.execute('SELECT * FROM test.testFamily WHERE pk_column = 111', function() { - res.send('foo') - }) -}) -``` - -[transaction breakdown][4] - - --------------------------------------------------------------------------------- - - -### The Instrumentation Function - -```js -function instrumentCassandra(shim, cassandra, moduleName) { - // ... -} -``` - -This is the function that we'll hand to the New Relic agent to perform our -instrumentation. It receives a {@link DatastoreShim}, the module to instrument -([`cassandra-driver`][1] in our case), and the name of the package (e.g. -`"cassandra-driver"`). Inside this function we'll perform all of our logic to -record operations and queries for Cassandra. - - -### Specifying the Datastore - -```js - shim.setDatastore(shim.CASSANDRA) -``` - -Here we simply tell the shim the name of our datastore. In our case we're using -one of the [predefined datastore names]{@link DatastoreShim.DATASTORE_NAMES}, -but we could have also passed in a string like `"Cassandra"` instead. This name -will show up on the [New Relic APM Databases page][2] like this: - -[databases overview][2] - - -### Recording Operations - -```js - var proto = cassandra.Client.prototype - shim.recordOperation(proto, ['connect', 'shutdown'], new shim.specs.OperationSpec({callback: shim.LAST})) -``` - -Now on to the actual instrumentation. In `cassandra-driver`, all of the -interaction with the database happens through this [`cassandra.Client`][3] class, -so we grab it's prototype to inject our code into. - -After grabbing the prototype, we tell the shim which methods we want to record -using [`shim.recordOperation`]{@link DatastoreShim#recordOperation}. In our -case, the interesting operations are `connect` and `shutdown`. Since these -methods are nicely named, we can pass them both at once and let the shim use -them to name our metrics. We can see the results of this on APM in the -transaction breakdown graph. The green layer labeled `Cassandra connect` is from -our recorded operation. - -[transaction breakdown][4] - -The third parameter is the ["spec" for this operation]{@link OperationSpec}. -Specs are simply objects that describe the interface for a function. For -operations, we just want to know when it has ended, which is indicated by the -callback being called, so we tell the shim which argument is the callback. We -can provide any numerical index for the callback argument, and the shim provides -[some predefined constants]{@link Shim#ARG_INDEXES}. `shim.LAST` indicates the -last argument passed in, we could have also provided `-1` as the index, but the -named constants are more readable. - -If we didn't like the names of the methods, we could supply an alternate name to -`shim.recordOperation` in the last parameter like this: - -```js - shim.recordOperation(proto, 'connect', new shim.specs.OperationSpec({name: 'Connect', callback: shim.LAST})) - shim.recordOperation(proto, 'shutdown', new shim.specs.OperationSpec({name: 'Shutdown', callback: shim.LAST})) -``` - -Note that since we want these operations named differently, if we specify the -name we must call `shim.recordOperation` for each operation. - -If the callback argument can't be easily identified by a positive or negative -offset in the arguments array, then a {@link CallbackBindFunction} could be -supplied instead. This function will receive the arguments and the segment, and -is responsible for connecting the two. Here's how that might look in our case: - -```js - shim.recordOperation(proto, ['connect', 'shutdown'], new shim.specs.OperationSpec({ - callback: function operationCallbackBinder(shim, opFunc, opName, segment, args) { - var cb = args[args.length - 1] - args[args.length - 1] = shim.bindSegment(cb, segment, true) - } - })) -``` - -Note that the `args` parameter is a proper [`Array`][5], so you can assign back -into it and use any other array manipulation that you want. - - -### Recording Queries - -```js - shim.recordQuery(proto, '_execute', new shim.specs.QuerySpec({query: shim.FIRST, callback: shim.LAST})) -``` - -The `cassandra.Client` class has three different methods for performing queries: -[`Client#eachRow`][6], [`Client#execute`][7], and [`Client#stream`][8]. We could -wrap all three of these methods, but with a little reading of the -[`cassandra-driver` source][9] we see that they call an internal function that -we can wrap instead. - -Now that we know where to wrap, we call -[`shim.recordQuery`]{@link DatastoreShim#recordQuery} and provide it the method -name and our {@link QuerySpec}. In addition to the callback, the `QuerySpec` -requires we identify query argument. So we tell the shim that our first argument -is the query and our last is the callback. - -Since Cassandra uses a SQL-like language, we can use the `DatastoreShim`'s -default query parser to pull the information it needs out of the query. So this -is all we need to do to record queries. - -In the transaction breakdown graph above, the recorded query is the purple layer -labeled `Cassandra test.testFamily select`. Because our instrumentation provided -the query to the shim we can see some basic information about it, in this case -the collection queried (`test.testFamily`) as well as the query operation -(`select`). - - -### Recording Batch Queries - -```js - shim.recordBatchQuery(proto, 'batch', new shim.specs.QuerySpec({ - query: findBatchQueryArg, - callback: shim.LAST - })) -``` - -Recording batches of queries is just like recording a single one, except we need -to do a little more work to pull out the query text. In this vein we call -[`shim.recordBatchQuery`]{@link DatastoreShim#recordBatchQuery} just like we did -for `shim.recordQuery`. This time we pass in a function for the spec's `query` -parameter, `findBatchQueryArg`, which just looks like this: - -```js -function findBatchQueryArg(shim, batch, fnName, args) { - var sql = (args[0] && args[0][0]) || '' - return sql.query || sql -} -``` - -The function is a {@link QueryFunction} callback, which takes in the current -shim, the function we're getting the query from, that function's name, and an -`Array` of arguments. For [`Client#batch`][10], the first argument is either an -array of strings or an array of objects that contain query strings. We want to -be very defensive when writing instrumentation, so we will default the query to -an empty string if no query was extractable. - - --------------------------------------------------------------------------------- - - -### Connecting it to the New Relic Agent - -Now that we've instrumented the module, we need to tell the New Relic agent to -use it when the `cassandra-driver` package is required. This is done using the -{@link API#instrumentDatastore} method as below. - -```js -var newrelic = require('newrelic') -newrelic.instrumentDatastore('cassandra-driver', instrumentCassandra) -``` - -This method tells the agent to call our instrumentation function when the module -is loaded by Node. It is critically important that we register our -instrumentation _before_ the module is loaded anywhere. Because we are using the -`instrumentDatastore` method, the agent will instantiate a {@link DatastoreShim} -when calling our instrumentation function. If we had used {@link API#instrument} -instead, we would get only the base class, {@link Shim}. - -The `instrumentDatastore` call could have also been written using named -parameters like this: - -```js -newrelic.instrumentDatastore({ - moduleName: 'cassandra-driver', - onRequire: instrumentCassandra -}) -``` - -This call is equivalent to the first one, it just depends on your preferred -style. - - -### Handling Errors - -While debugging your instrumentation it can be useful to get a handle on any -errors happening within it. Normally, the agent swallows errors and disables the -instrumentation. In order to get the error for your debugging purposes you can -provide a third argument to `instrumentDatastore` that receives the error. - -```js -newrelic.instrumentDatastore({ - moduleName: 'cassandra-driver', - onRequire: instrumentCassandra, - onError: function myErrorHandler(err) { - // Uh oh! Our instrumentation failed, lets see why: - console.error(err.message, err.stack) - - // Let's kill the application when debugging so we don't miss it. - process.exit(-1) - } -}) -``` - - -### Conclusion - -We have now instrumented the package and told the New Relic agent to use our -instrumentation, but how should we get it out there? Our instrumentation can -be published to NPM if we are confident in its usefulness to others. - - -### Questions? - -We have an extensive [help site](https://support.newrelic.com/) as well as -[documentation](https://docs.newrelic.com/). If you can't find your answers -there, please drop us a line on the [community forum](https://discuss.newrelic.com/). - -[1]: https://www.npmjs.com/package/cassandra-driver -[2]: https://docs.newrelic.com/docs/apm/applications-menu/monitoring/databases-slow-queries-page -[3]: https://github.com/datastax/nodejs-driver#basic-usage -[4]: https://docs.newrelic.com/docs/apm/applications-menu/monitoring/transactions-page -[5]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array -[6]: https://github.com/datastax/nodejs-driver#row-streaming-and-pipes -[7]: https://github.com/datastax/nodejs-driver#user-defined-types -[8]: https://github.com/datastax/nodejs-driver#row-streaming-and-pipes -[9]: https://github.com/datastax/nodejs-driver/blob/master/lib/client.js#L589 -[10]: https://github.com/datastax/nodejs-driver#batch-multiple-statements diff --git a/examples/shim/Instrumentation-Basics.md b/examples/shim/Instrumentation-Basics.md deleted file mode 100644 index 7b8558d344..0000000000 --- a/examples/shim/Instrumentation-Basics.md +++ /dev/null @@ -1,78 +0,0 @@ - -### Purpose of Instrumentation - -Instrumentation for Node.js holds two purposes. The first is to give users -detailed information about what happens on their server. The more things -instrumented, the more detailed this graph can be. - -[application overview] - -The second purpose is to maintain the transaction context. In order to properly -associate chunks of work with the correct transactions we must link the context -through asynchronous boundaries. This is a broad topic that is discussed in more details -in this separate tutorial {@tutorial Context-Preservation}. - -### Adding Custom Instrumentation to the New Relic Agent - -Calling `require('newrelic')` will return an API object, which contains the following -methods for registering custom instrumentation: - -* instrument -* instrumentDatastore -* instrumentWebframework -* instrumentMessages - -These methods are used to tell the New Relic agent to use the provided instrumentation -function when the specified module is loaded by Node. It is critically important that we -register our instrumentation _before_ the module is loaded anywhere. For example: - -```js -var newrelic = require('newrelic') -newrelic.instrument('my-module', instrumentMyModule) -var myModule = require('my-module') -``` - -All four methods have the same signature. The difference between them is in what type -of shim they provide when the instrumentation function is called. The `instrument` -method provides only the base shim {@link Shim}, while `instrumentDatastore`, `instrumentWebframework`, and `instrumentMessages` provide shims specific to the type of instrumentation -({@link DatastoreShim}, {@link WebFrameworkShim}, and {@link MessageShim} respectively). - -The `instrument` call could have also been written using named -parameters like this: - -```js -newrelic.instrument({ - moduleName: 'my-module', - onRequire: instrumentMyModule -}) -``` - -This call is equivalent to the first one, it just depends on your preferred -style. - -### Handling Errors - -While debugging your instrumentation it can be useful to get a handle on any -errors happening within it. Normally, the agent swallows errors and disables the -instrumentation. In order to get the error for your debugging purposes you can -provide a third argument to `instrument` that receives the error. - -```js -newrelic.instrument({ - moduleName: 'my-module', - onRequire: instrumentMyCustomModule, - onError: function myErrorHandler(err) { - // Uh oh! Our instrumentation failed, lets see why: - console.error(err.message, err.stack) - - // Let's kill the application when debugging so we don't miss it. - process.exit(-1) - } -}) -``` - -### Questions? - -We have an extensive [help site](https://support.newrelic.com/) as well as -[documentation](https://docs.newrelic.com/). If you can't find your answers -there, please drop us a line on the [community forum](https://discuss.newrelic.com/). diff --git a/examples/shim/Messaging-Simple.md b/examples/shim/Messaging-Simple.md deleted file mode 100644 index d815901163..0000000000 --- a/examples/shim/Messaging-Simple.md +++ /dev/null @@ -1,259 +0,0 @@ -### Pre-requisite - -{@tutorial Instrumentation-Basics} - -### Introduction - -This tutorial covers basic concepts of the Messaging instrumentation API. - -Modules that interact with message brokers will typically provide: - -* a function to publish a message -* a function to get a single message -* a function to subscribe to receive messages - -Publishing a message typically occurs as a part of an existing transaction. For example, an Express server receives an HTTP request, publishes a message to a message broker, and responds to the HTTP request. In this case, the interesting information to capture would be how long the publish operation took as well as any identifying information about the publish operation, such as the name of the queue we were publishing to. - -```js -var client = createMessageBrokerClient() - -// express server -var app = express() - -app.get('/', function(req, res) { - client.publish('queueName', 'some message', function(err) { - res.end() - }) -}) -``` - -Consuming messages can take two forms: Either the client pulls a message from a queue, or it subscribes to receive messages as they become available (pub/sub pattern). - -Pulling a message from the queue is a one-off operation, which would typically be part of an existing transaction. Similar to the publish example above, we want to know how long it took to get the message from the broker. - -```js -var client = createMessageBrokerClient() - -// express server -var app = express() - -app.get('/', function(req, res) { - client.getMessage('queueName', function(err, msg) { - // Do something with the message... - - res.end() - }) -}) -``` - -With the pub/sub pattern, the application is continuously listening to incoming messages, and therefore receiving a message does not necessarily occur inside an existing transaction. Instead, it is comparable to receiving an HTTP request, and can be thought of as a start of a new transaction. - -Here is an example of a client subscribing to receive messages: - -```js -var client = createMessageBrokerClient() - -client.subscribe('queueName', function consumeMessage(message) { - // get current transaction, in order to later signal that it should be ended - var transaction = newrelic.getTransaction() - - // do something with the message and when done, end the transaction - processMessage(message, function(err) { - transaction.end() - }) -}) -``` - -Every time `consumeMessage` is called, we want to record the work to process a message as a new transaction. - -### The Instrumentation Function - -Now that we have established what to instrument, let's start writing our instrumentation. First, we need to create a function that will contain our instrumentation: - -```js -function instrumentMyMessageBroker(shim, messageBrokerModule, moduleName) { -} -``` - -The instrumentation function receives the following arguments: - -* [shim]{@link MessageShim} - - The API object that contains methods for performing instrumentation. - -* messageBrokerModule - - The loaded module that should be instrumented. - -* moduleName - - The name of the loaded module. This is useful if the same instrumentation function was used to instrument multiple modules. - -The function can be included in the application code itself, or it can live in a separate instrumentation module. In either case, we need to register it in our application code in order for the agent to use it. This is done in our application by calling {@link API#instrumentMessages}: - -```js -var newrelic = require('newrelic') -newrelic.instrumentMessages('myMessageBroker', instrumentMyMessageBroker) -``` - -As a result, the agent will call our instrumentation function when the message broker module is required in the user's application code. For more details, see {@tutorial Instrumentation-Basics}. - -### Specifying the Message Broker - -Now that we have bootstrapped our instrumentation function, we can proceed with its implementation. - -The first thing the instrumentation should specify is the name of the message broker that the library being instrumented applies to. The value is used as a part of the metric names. - -```js - shim.setLibrary(shim.RABBITMQ) -``` - -[transaction breakdown] - -### Producing Messages - -An application can publish a message to the broker. When this happens as part of a transaction, the agent can record this call to the broker as a separate segment in the transaction trace. Here is an example of instrumenting a `publish` method on the `Client` class from the message broker module we are instrumenting. - -```js -function instrumentMyMessageBroker(shim, messageBrokerModule, moduleName) { - var Client = myMessageBrokerModule.Client - - shim.recordProduce(Client.prototype, 'publish', function(shim, fn, name, args) { - var queueName = args[0] - - // The message headers must be pulled to enable cross-application tracing. - var options = args[1] - var headers = options.headers - - // misc key/value parameters can be recorded as a part of the trace segment - var params = {} - - return new shim.specs.MessageSpec({ - callback: shim.LAST, - destinationName: queueName, - destinationType: shim.QUEUE, - headers: headers, - parameters: params - }) - }) -} -``` - -The `recordProduce` method wraps the `publish` function, so that when it's called we can extract information about the specific call from its arguments. This is done in the callback function (third argument), where we need to return a map of parameters that describe the current operation. - -* destinationType, destinationName - - Used to name the trace segment and metric - -* headers (optional) - - Used to transport information needed for cross-application traces. For more information about cross-application tracing, see the section at the bottom of this tutorial. - -* parameters (optional) - - Used to record additional information on the trace segment - -The call would be displayed in the transaction trace as: - -[transaction trace with produce segment] - -The transaction trace window also has the Messages tab, which shows all of the messages produced or consumed during the transaction: - -[transaction trace with produce segment] - -The agent will also record a metric that can be be queried in Insights. The format of the metric is: `MessageBroker/[libraryName]/Queue/Produce/Named/[queueName]`. - -### Consuming Messages - -An application can consume messages from the broker's queues. The mechanism for consuming messages can vary based on the broker and type of queues. Messages can either be consumed by the client explicitly asking for a message (e.g. a worker-queue pattern), or it can subscribe to a queue and receive messages as they become available (e.g. a pub/sub pattern). - -#### Pull pattern - -Let's assume that the client has a method `getMessage`. When the client calls -this, the message broker returns a message from the requested queue. The -instrumentation of this method would look like this: - -```js -function instrumentMyMessageBroker(shim, messageBrokerModule, moduleName) { - var Client = myMessageBrokerModule.Client - - shim.recordConsume(Client.prototype, 'getMessage', new shim.specs.MessageSpec({ - destinationName: shim.FIRST, - callback: shim.LAST, - after: function({ shim, args }) { - var message = args[1] - - // These headers are used to set up cross-application tracing. - var headers = message.properties.headers - - // misc key/value parameters can be recorded as a part of the trace segment - var params = { - routing_key: message.properties.routingKey - } - - return { - parameters: params, - headers: headers - } - } - })) -} -``` - -Similarly to the produce-messages case, `recordConsume` wraps the function used to call the message broker. The main difference here is that some parameters may be included as arguments to the `getMessage` call, while some might be extracted from the received message. As a result, there is an extra parameter called `messageHandler`, which refers to a function to be called when a message is received. In this function we can extract additional information from the message and pass on to the API. - -The call would be displayed in the transaction trace as: - -[transaction trace with consume segment] - -The agent will also record a metric that can be be queried in Insights. The format of the metric is `MessageBroker/[libraryName]/Queue/Produce/Named/[queueName]`. - -#### Pub/sub pattern - -For listening to messages sent by the broker, let's assume that the client has -a `subscribe` method, which registers a function for processing messages when -they are received. The instrumentation in this case would look like this: - -```js -function instrumentMyMessageBroker(shim, messageBrokerModule, moduleName) { - var Client = myMessageBrokerModule.Client - - shim.recordSubcribedConsume(Client.prototype, 'subscribe', new shim.specs.MessageSubscribeSpec({ - consumer: shim.LAST, - messageHandler: function(shim, args) { - var message = args[0] - - // These headers are used to set up cross-application tracing. - var headers = message.properties.headers - - return { - destinationName: message.properties.queueName, - destinationType: shim.QUEUE, - headers: headers - } - } - })) -} -``` - -The `recordSubscribedConsume` method has almost the same interface as `recordConsume`. The one difference is that we need to also specify which argument represents the consumer function (the function that will be called everytime a message arrives). This is specified using the `consumer` parameter. - -The `messageHandler` parameter works the same as in the `recordConsume` case. When a message is consumed, the `messageHandler` function will be called, and we can extract the information we need from the message. - -Each message processing will be shown as a separate transaction: - -[message transaction] - -### Cross application traces - -The messaging API has support for cross application tracing, when messages are used to communicate between two different (instrumented) apps. See [Introduction to cross application traces][1] for more information about how this works in general. - -Cross-application tracing relies on sending metadata along with the request and response messages. In the case of HTTP transactions, this data is transported as HTTP headers. In the case of message brokers, there is no standard way of sending headers. In order to tell the agent where this data should be attached, the API provides the `headers` parameter for both produce and consume operations. The value of this needs to be an object/map that the message broker library sends along with the message. The agent will automatically attach the needed data to this object. - -### Questions? - -We have an extensive [help site](https://support.newrelic.com/) as well as -[documentation](https://docs.newrelic.com/). If you can't find your answers -there, please drop us a line on the [community forum](https://discuss.newrelic.com/). - -[1]: https://docs.newrelic.com/docs/apm/transactions/cross-application-traces/introduction-cross-application-traces diff --git a/examples/shim/Webframework-Simple.md b/examples/shim/Webframework-Simple.md deleted file mode 100644 index 6308cee73e..0000000000 --- a/examples/shim/Webframework-Simple.md +++ /dev/null @@ -1,267 +0,0 @@ -### Pre-requisite - -{@tutorial Instrumentation-Basics} - -### Introduction - -This tutorial goes over basic concepts of using the WebFramework instrumentation API. -In order to demonstrate the topics, we will use a hypothetical web framework that has -similar concepts to popular web frameworks such as Express or Restify. - -Here is an example of how the web framework would be used in a user's code: - -```js -const myWebFramework = require('my-web-framework') -const authenticate = require('./lib/authenticate') - -// create server -let server = new myWebFramework.Server() - -server.all(function authenticateMiddleware(req, res, next) { - if (authenticate()) { - next() - } else { - res.statusCode = 403 - res.end() - } -}) - -server.get('/api/users', function(req, res) { - res.write(getUsers()) - res.end() -}) - -server.get('/home', function(req, res) { - this.render('home', function(err, renderedView) { - res.write(renderedView) - res.end() - }) -}) - -server.start(8080) -``` - -In this example, the web application server is serving two endpoints. Endpoint -`/api/users` returns a list of users as JSON data. The second endpoint `/home` renders -and returns an HTML view. It also has an authentication middleware that is executed for -any type of request. - -### The Instrumentation Function - -To add instrumentation to a web framework, you supply a single function which is -responsible for telling New Relic which framework methods should be instrumented -and what metrics and data to collect. The agent calls this instrumentation -function automatically when the web framework module is required in the user's -code. The function must be registered with the agent by calling {@link -API#instrumentWebframework}. - -```js -const newrelic = require('newrelic') -newrelic.instrumentWebframework('my-web-framework', instrumentMyWebFramework) -``` - -The instrumentation function can be included in the application code itself or -it can live in a separate instrumentation module. In either case, we need to -register it in our application code in order for the agent to use it. - -The instrumentation function has the following signature: - -```js -function instrumentMyWebFramework(shim, myModule, moduleName) { -``` - -It receives a {@link WebFrameworkShim}, the module to instrument and the name of the -package. - -For more details, see {@tutorial Instrumentation-Basics}. - -### Specifying the Framework - -The first thing the instrumentation should do is specify the name of the framework it is -instrumenting. The value is used as a part of metric names and transaction event -attributes. It is also displayed in the Environment view. - -```js - shim.setFramework('MyCustom') -``` - -[transaction breakdown] - -### What to Record - -Web framework instrumentation will generally be responsible for the following: - -* naming the transaction -* recording metrics for middleware functions -* recording metrics for rendering views -* reporting errors that are handled by the web framework - -### Transaction Naming - -A single transaction represents all activity that is tied to a single HTTP request from -the time it is received until the app server sends a response back. In order to see -meaningful metrics for similar transactions (e.g. ones that hit the same URL endpoint), -the instrumentation needs to determine and assign a name for each transaction. - -The Node agent names transactions using the HTTP verb and the request URI. The -verb is automatically determined from the HTTP request. However, the URI must -typically be normalized in order to group related transactions in a meaningful way. -For more details, see [Metric grouping issues](https://docs.newrelic.com/docs/agents/manage-apm-agents/troubleshooting/metric-grouping-issues). - -The API provides a basic function for setting the URI to use for naming - {@link WebFrameworkShim#setTransactionUri}. -This would be sufficient for a very basic use case when the URI can be determined in a -single point in the instrumentation code. - -However, many common web frameworks route each request through many functions, which may -contribute to the final name. In order to help with the common use case of nested -middlewares, the API provides a mechanism for naming transactions based on paths that -middleware functions are mounted on. - -### Middlewares - -Many frameworks have a concept of middlewares. Middleware is a function that is executed -in response to a request for a specific URL endpoint. Middleware can either respond to -the HTTP request or pass control to another middleware. - -Since there can be many middlewares executed for a single request, it is useful to know -how much time is spent in each middleware when troubleshooting performance. - -[transaction breakdown] - - -There are two API functions related to middleware - {@link WebFrameworkShim#recordMiddleware} -and {@link WebFrameworkShim#wrapMiddlewareMounter}. Based on our web app code, we would -expect to record a middleware metric for the authentication middleware, and also for the -endpoint middleware that responds to a specific request. Here is what the instrumenation -would look like: - -```js -let Server = myWebFramework.Server - -shim.wrapMiddlewareMounter(Server.prototype, ['all', 'get'], { - route: shim.FIRST, - wrapper: function wrapMiddleware(shim, fn, name, route) { - return shim.recordMiddleware(fn, new shim.specs.MiddlewareSpec({ - route: route, - type: shim.MIDDLEWARE, - req: shim.FIRST, - res: shim.SECOND, - next: shim.THIRD - })) - } -}) - -``` - -In order to record the correct middleware metrics, middleware functions should -be wrapped using the {@link WebFrameworkShim#recordMiddleware} API method. - -Note that middleware functions may not be exposed directly on the framework. So -in order to get access to the middleware function so you can call -`recordMiddleware`, you may need to wrap the framework method that is used to -register the middleware. - -A common pattern is a function that takes a path and one or more middleware -functions. For example, Express has routing methods such as `get`, `post`, -`put`, and `use`. The Restify framework has a similar pattern. In our example -framework, we use the `get` and `all` methods the same way. - -Since this is a common pattern, our API provides method {@link -WebFrameworkShim#wrapMiddlewareMounter} to make it easier to wrap middlewares in -this particular case. In cases where the framework has a different mechanism for -registering middlewares, the instrumentation would need to fallback to basic -wrapping ({@link Shim#wrap}) in order to get to the place where middleware -functions can be intercepted. For an example of instrumentation that does not -use {@link WebFrameworkShim#wrapMiddlewareMounter}, see our built-in Hapi -instrumentation. - -Going back to our instrumentation example... We will use the {@link -WebFrameworkShim#wrapMiddlewareMounter} method to wrap `all` and `get`. The -third argument in that call is the spec where we tell the instrumentation which -argument is the route path, and what to do with all other arguments that -represent the middleware functions. The instrumentation will call the `wrapper` -function for each middleware function, and here we need to wrap the original -function and return the wrapped version of it. - -This is where {@link WebFrameworkShim#recordMiddleware} comes in. It takes the -original middleware function as the first argument, and a spec describing the -middleware function signature. The `req`, `res`, and `next` parameters tell the -instrumentation which arguments are which. The `route` and `type` arguments are -used for correctly naming the middleware metrics. - -The following types are allowed: - -| type | description | generated metric | trace segment | -| --- | --- | --- | -| MIDDLEWARE | Represents a generic middleware function. This could be a function that is executed for all types of requests (e.g. authentication), or a responder function associated (mounted) with a specific URL path. | `Nodejs/Middleware///` | `Middleware: `

Note: If the middleware is nested under a ROUTE middleware, the path is omitted (since it's displayed in the ROUTE segment name). | -| ERRORWARE | Used for recording middlewares that are used for handling errors. | `Nodejs/Middleware///`

Note: The mounted path will reflect the path that was current when the error occurred. If the error handler itself is not mounted on a path, its path is appended to the originating path. | `Middleware: ` | -| PARAMWARE | This is a special type of middleware used for extracting parameters from URLs. | `Nodejs/Middleware////[param handler :]` | `Middleware: /[param handler :]` | -| ROUTE | Used for grouping multiple middleware functions under a single route path. | no metric | `Route Path: ` | -| ROUTER | Used to create a trace segment for a router object that is mounted on a path. | no metric | `Router: ` | -| APPLICATION | Used for application object that is mounted as a router. This is a concept in Express, and typically will not be used in other framework instrumenations. | no metric | `Mounted App: ` | - - -### Views - -Web frameworks will often provide mechanisms for rendering views from templates. -Rendering views can be time consuming, and therefore New Relic can collect and -display a specific metric (`View//Rendering`) related to this. In -order to capture these metrics, use the {@link WebFrameworkShim#recordRender} -API method. - -Let's assume that the Server object in our custom web framework has a `render` -function that takes the name of the view and a callback. The callback returns -the generated content. In order to record this work as the View metric, we will -do the following in our instrumentation: - -```js -let Server = myWebFramework.Server - -shim.recordRender(Server.prototype, 'render', new shim.specs.RenderSpec({ - view: shim.FIRST, - callback: shim.LAST -})) -``` - -[view segment] - -Note that in some cases (e.g. in Express) the `render` function may be directly -on the `req` object and may be invoked without accepting a callback. In this -case calling the `render` function could be ending the HTTP response itself. In -such a case, in order to capture correct timing for the render function, -additional instrumentation would be needed based on how the web framework is -implemented. - -### Errors - -Web frameworks will typically capture errors generated from middlewares (either -uncaught or provided by the user, e.g. by calling `next(error)` in Express) and -respond with a HTTP 500 error. If these errors are not handled by the user (e.g. -using an errorware function), then it is more useful to report the original -errors instead of a generic HTTP 500). The API provides the {@link -WebFrameworkShim#noticeError} method for reporting errors to New Relic. - -Let's assume that our web framework emits an event everytime an error occurs. We -can listen on this event, and call {@link WebFrameworkShim#noticeError}. - -```js -server.on('error', function(req, error) { - shim.noticeError(req, error) -}) -``` - -Now let's assume that at a later point, the web framework calls a middleware -where the user handles the error themselves. In this case, the error should no -longer be reported. We can use the {@link WebFrameworkShim#errorHandled} -function to remove the error: - -```js -shim.errorHandled(req, error) -``` - - -### Questions? - -We have an extensive [help site](https://support.newrelic.com/) as well as -[documentation](https://docs.newrelic.com/). If you can't find your answers -there, please drop us a line on the [community forum](https://discuss.newrelic.com/). diff --git a/examples/shim/breakdown-table-web-framework.png b/examples/shim/breakdown-table-web-framework.png deleted file mode 100644 index 98cd7dd1bc..0000000000 Binary files a/examples/shim/breakdown-table-web-framework.png and /dev/null differ diff --git a/examples/shim/confounded-txns.png b/examples/shim/confounded-txns.png deleted file mode 100644 index 3752cade53..0000000000 Binary files a/examples/shim/confounded-txns.png and /dev/null differ diff --git a/examples/shim/db-overview.png b/examples/shim/db-overview.png deleted file mode 100644 index 01f581ff8e..0000000000 Binary files a/examples/shim/db-overview.png and /dev/null differ diff --git a/examples/shim/jsdoc.json b/examples/shim/jsdoc.json deleted file mode 100644 index ed5e03f3f4..0000000000 --- a/examples/shim/jsdoc.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Instrumentation-Basics": { - "title": "Instrumentation Basics" - }, - "Context-Preservation": { - "title": "Transaction State Preservation" - }, - "Datastore-Simple": { - "title": "Intro to Datastore Instrumentation" - }, - "Webframework-Simple": { - "title": "Intro to Web Framework Instrumentation" - }, - "Messaging-Simple": { - "title": "Intro to Message Broker Instrumentation" - } -} diff --git a/examples/shim/messaging-breakdown-table.png b/examples/shim/messaging-breakdown-table.png deleted file mode 100644 index 3d75728b1b..0000000000 Binary files a/examples/shim/messaging-breakdown-table.png and /dev/null differ diff --git a/examples/shim/messaging-consume-segment.png b/examples/shim/messaging-consume-segment.png deleted file mode 100644 index 816e0f27d8..0000000000 Binary files a/examples/shim/messaging-consume-segment.png and /dev/null differ diff --git a/examples/shim/messaging-consume-transaction.png b/examples/shim/messaging-consume-transaction.png deleted file mode 100644 index ef044b1ecf..0000000000 Binary files a/examples/shim/messaging-consume-transaction.png and /dev/null differ diff --git a/examples/shim/messaging-produce-messages.png b/examples/shim/messaging-produce-messages.png deleted file mode 100644 index e979004417..0000000000 Binary files a/examples/shim/messaging-produce-messages.png and /dev/null differ diff --git a/examples/shim/messaging-produce-segment.png b/examples/shim/messaging-produce-segment.png deleted file mode 100644 index 9d0c5b6eb1..0000000000 Binary files a/examples/shim/messaging-produce-segment.png and /dev/null differ diff --git a/examples/shim/overview.png b/examples/shim/overview.png deleted file mode 100644 index e1921c6265..0000000000 Binary files a/examples/shim/overview.png and /dev/null differ diff --git a/examples/shim/proper-txns.png b/examples/shim/proper-txns.png deleted file mode 100644 index 624151095e..0000000000 Binary files a/examples/shim/proper-txns.png and /dev/null differ diff --git a/examples/shim/trace-summary-view.png b/examples/shim/trace-summary-view.png deleted file mode 100644 index 4493471b66..0000000000 Binary files a/examples/shim/trace-summary-view.png and /dev/null differ diff --git a/examples/shim/tx-breakdown-web-api.png b/examples/shim/tx-breakdown-web-api.png deleted file mode 100644 index 9132001745..0000000000 Binary files a/examples/shim/tx-breakdown-web-api.png and /dev/null differ diff --git a/examples/shim/tx-breakdown.png b/examples/shim/tx-breakdown.png deleted file mode 100644 index f4cd950345..0000000000 Binary files a/examples/shim/tx-breakdown.png and /dev/null differ diff --git a/jsdoc-conf.jsonc b/jsdoc-conf.jsonc index 952e091abe..ce8f044af0 100644 --- a/jsdoc-conf.jsonc +++ b/jsdoc-conf.jsonc @@ -7,7 +7,7 @@ "search": false, "shouldRemoveScrollbarStyle": true }, - "tutorials": "examples/shim", + "tutorials": "examples", "recurse": true }, "source": { diff --git a/package.json b/package.json index 369fc232ec..aa48bf3c5e 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "prepare-test": "npm run ssl && npm run docker-env", "lint": "eslint ./*.{js,mjs} lib test bin examples", "lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin examples", - "public-docs": "jsdoc -c ./jsdoc-conf.jsonc && cp examples/shim/*.png out/", + "public-docs": "jsdoc -c ./jsdoc-conf.jsonc", "publish-docs": "./bin/publish-docs.sh", "services": "DOCKER_PLATFORM=linux/$(uname -m) docker compose up -d --wait", "services:stop": "docker compose down",