appsignal / appsignal-nodejs

🟩 AppSignal for Node.js
https://www.appsignal.com/nodejs/
MIT License
28 stars 9 forks source link

Implement heartbeats for Node.js #1015

Closed unflxw closed 6 months ago

unflxw commented 7 months ago

Hoo boy. Here we go.

Attempt to use global config in Transmitter

When a transmitter is initialised, it should attempt to use the configuration object of the global client, if one exists.

Implement heartbeats in Node.js

Implement heartbeats with a conceptually similar implementation to that of the [Ruby][ruby] and [Elixir][elixir] integrations.

Note, however, that unlike the Ruby and Elixir implementations, the heartbeats are sent asynchronously, in a "fire and forget" manner, without blocking the execution of the heartbeat-wrapped function.

Await heartbeat promises on shutdown

When AppSignal is "gracefully stopped" by calling Appsignal.stop(), the heartbeats might not have finished sending. AppSignal should await the delivery of those heartbeats before shutting down.

To do this, the pending heartbeats are collected into a set, which automatically cleans itself up when all promises are settled. A promise for the settling of all remaining promises in the set is awaited when Appsignal.stop() is called.

The method that awaits these promises is named Heartbeat.shutdown(), and not Heartbeat.stop(), to avoid any potential confusion where it could be mistaken for the counterpart of Heartbeat.start().

This is a sort of breaking change, in that Appsignal.stop() is now an asynchronous method, meaning that it returns a promise, and as such it must be awaited.

However, note that Appsignal.stop() should have already been awaiting the promise returned by the OpenTelemetry SDK's .shutdown() method. As such, this change makes sense beyond the heartbeats.

Support async functions

In practice, most long-running operations in Node.js applications take place as a sequence of await operations on functions that return promises, within an async function that itself returns a promise.

As such, in order for AppSignal to be able to track the duration of the heartbeat, when the return value is a promise, AppSignal should send the heartbeat when the promise is resolved, and not when the function that returns the promise finishes.

Allow manually awaiting heartbeat events

In addition to the easy-to-use heartbeat function, the Heartbeat class is also exposed, allowing finer control to customers who might want to send the start and finish events independently from each other.

Since the events are sent asynchronously, it makes sense to return the corresponding promise so that it can be awaited by the caller.

However, since awaiting a rejected promise raises an error, and we strongly avoid causing AppSignal-related errors to be raised in customers' applications, the promise is overriden on rejection with an empty resolved promise, making it safe to await it.

Assert mocked URLs are called

Nock will not implicitly assert that the URLs that are mocked by it are being called. You must explicitly keep track of the scope object that it returns, and call scope.done() to assert that all the mocked endpoints have actually been called.

Write tests for heartbeats

Test the expected behaviour of the heartbeat module against mocked Nock requests.

Return the same promise from Appsignal.heartbeat() that was returned by the function given to it as an argument, instead of returning a wrapper promise that emits the heartbeat.

Fix a bug where the timestamp was sent in milliseconds instead of seconds.

backlog-helper[bot] commented 6 months ago

This is a message from the daily scheduled checks.

New issue guide | Backlog management | Rules | Feedback

backlog-helper[bot] commented 6 months ago

This is a message from the daily scheduled checks.

New issue guide | Backlog management | Rules | Feedback

backlog-helper[bot] commented 6 months ago

This is a message from the daily scheduled checks.

New issue guide | Backlog management | Rules | Feedback