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.
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.
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.
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.
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.
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.
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 notHeartbeat.stop()
, to avoid any potential confusion where it could be mistaken for the counterpart ofHeartbeat.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 anasync
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, theHeartbeat
class is also exposed, allowing finer control to customers who might want to send thestart
andfinish
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.