diff --git a/packages/nodejs/.changesets/stop-minutely-probes-when-stopping-appsignal.md b/packages/nodejs/.changesets/stop-minutely-probes-when-stopping-appsignal.md new file mode 100644 index 000000000..03b10f19f --- /dev/null +++ b/packages/nodejs/.changesets/stop-minutely-probes-when-stopping-appsignal.md @@ -0,0 +1,8 @@ +--- +bump: "patch" +type: "fix" +--- + +The minutely probes are now stopped when `Appsignal.stop()` is called. +In addition, the probes can be stopped independently of the rest of +the integration by calling `probes.stop()`. diff --git a/packages/nodejs/src/__tests__/client.test.ts b/packages/nodejs/src/__tests__/client.test.ts index b2745cc8e..bcce2c3b2 100644 --- a/packages/nodejs/src/__tests__/client.test.ts +++ b/packages/nodejs/src/__tests__/client.test.ts @@ -26,9 +26,11 @@ describe("BaseClient", () => { }) it("stops the client", () => { - const stopSpy = jest.spyOn(client.extension, "stop") + const extensionStopSpy = jest.spyOn(client.extension, "stop") + const probesStopSpy = jest.spyOn(client.metrics().probes(), "stop") client.stop() - expect(stopSpy).toHaveBeenCalled() + expect(extensionStopSpy).toHaveBeenCalled() + expect(probesStopSpy).toHaveBeenCalled() }) it("stores the client on global object", () => { diff --git a/packages/nodejs/src/__tests__/metrics.test.ts b/packages/nodejs/src/__tests__/metrics.test.ts index 7306375cc..130f1c4c1 100644 --- a/packages/nodejs/src/__tests__/metrics.test.ts +++ b/packages/nodejs/src/__tests__/metrics.test.ts @@ -1,7 +1,5 @@ import { BaseClient } from "../client" import { BaseMetrics as Metrics } from "../metrics" -import { NoopProbes } from "../noops" -import { BaseProbes } from "../probes" describe("Metrics", () => { let metrics: Metrics @@ -11,14 +9,14 @@ describe("Metrics", () => { metrics = new Metrics() }) - it("has `Probes` when minutely probes are on", () => { - expect(metrics.probes()).toBeInstanceOf(BaseProbes) + it("is not stopped when minutely probes are on", () => { + expect(metrics.probes().isStopped).toEqual(false) }) - it("has `NoopProbes` when minutely probes are off", () => { + it("is stopped when minutely probes are off", () => { new BaseClient({ enableMinutelyProbes: false }) metrics = new Metrics() - expect(metrics.probes()).toBeInstanceOf(NoopProbes) + expect(metrics.probes().isStopped).toEqual(true) }) it("sets a gauge", () => { diff --git a/packages/nodejs/src/__tests__/probes.test.ts b/packages/nodejs/src/__tests__/probes.test.ts index 3a77df7c1..5b2e0658b 100644 --- a/packages/nodejs/src/__tests__/probes.test.ts +++ b/packages/nodejs/src/__tests__/probes.test.ts @@ -23,6 +23,30 @@ describe("Probes", () => { return fn } + describe("when stopped", () => { + it("is stopped", () => { + expect(probes.isStopped).toEqual(false) + probes.stop() + expect(probes.isStopped).toEqual(true) + }) + + it("does not register or call an already registered probe", () => { + const fn = registerMockProbe() + probes.stop() + jest.runOnlyPendingTimers() + expect(fn).not.toHaveBeenCalled() + expect(probes.count).toEqual(0) + }) + + it("does not register or call a newly registered probe", () => { + probes.stop() + const fn = registerMockProbe() + jest.runOnlyPendingTimers() + expect(fn).not.toHaveBeenCalled() + expect(probes.count).toEqual(0) + }) + }) + it("registers a probe", () => { const fn = registerMockProbe() jest.runOnlyPendingTimers() diff --git a/packages/nodejs/src/client.ts b/packages/nodejs/src/client.ts index 6ba55fd6e..91b8ea82e 100644 --- a/packages/nodejs/src/client.ts +++ b/packages/nodejs/src/client.ts @@ -105,6 +105,7 @@ export class BaseClient implements Client { console.log("Stopping AppSignal") } + this.metrics().probes().stop() this.extension.stop() } diff --git a/packages/nodejs/src/interfaces/probes.ts b/packages/nodejs/src/interfaces/probes.ts index 2eaecd600..10c08ea0f 100644 --- a/packages/nodejs/src/interfaces/probes.ts +++ b/packages/nodejs/src/interfaces/probes.ts @@ -2,6 +2,17 @@ * The Minutely probes object. */ export interface Probes { + /** + * Permanently stops the probes system, unregistering all probes + * and clearing the timers. + */ + stop(): this + + /** + * Whether the probes system has been stopped. + */ + readonly isStopped: boolean + /** * Number of probes that are registered. */ diff --git a/packages/nodejs/src/metrics.ts b/packages/nodejs/src/metrics.ts index f9df6ad36..48b06d450 100644 --- a/packages/nodejs/src/metrics.ts +++ b/packages/nodejs/src/metrics.ts @@ -1,6 +1,5 @@ import { Metrics, Probes } from "./interfaces" import { BaseProbes } from "./probes" -import { NoopProbes } from "./noops" import { metrics } from "./extension_wrapper" import { Data } from "./internal/data" import { BaseClient } from "./client" @@ -16,11 +15,7 @@ export class BaseMetrics implements Metrics { constructor() { let enableMinutelyProbes = BaseClient.config.data.enableMinutelyProbes - if (enableMinutelyProbes) { - this.#probes = new BaseProbes() - } else { - this.#probes = new NoopProbes() - } + this.#probes = new BaseProbes({ stopped: !enableMinutelyProbes }) } /** diff --git a/packages/nodejs/src/noops/index.ts b/packages/nodejs/src/noops/index.ts index c1e744c1e..46c856fd7 100644 --- a/packages/nodejs/src/noops/index.ts +++ b/packages/nodejs/src/noops/index.ts @@ -1,4 +1,3 @@ export * from "./span" export * from "./tracer" export * from "./metrics" -export * from "./probes" diff --git a/packages/nodejs/src/noops/metrics.ts b/packages/nodejs/src/noops/metrics.ts index 70f9863ae..a7bde7737 100644 --- a/packages/nodejs/src/noops/metrics.ts +++ b/packages/nodejs/src/noops/metrics.ts @@ -1,8 +1,8 @@ import { Metrics, Probes } from "../interfaces" -import { NoopProbes } from "../noops" +import { BaseProbes } from "../probes" export class NoopMetrics implements Metrics { - #probes = new NoopProbes() + #probes = new BaseProbes({ stopped: true }) public setGauge( key: string, diff --git a/packages/nodejs/src/noops/probes.ts b/packages/nodejs/src/noops/probes.ts deleted file mode 100644 index f01ba7148..000000000 --- a/packages/nodejs/src/noops/probes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Probes } from "../interfaces" - -export class NoopProbes implements Probes { - public get count(): number { - return 0 - } - - public register(name: string, fn: () => void): this { - return this - } - - public unregister(name: string): this { - return this - } - - public clear(): this { - return this - } -} diff --git a/packages/nodejs/src/probes/index.ts b/packages/nodejs/src/probes/index.ts index 18d9adfe6..f1279c757 100644 --- a/packages/nodejs/src/probes/index.ts +++ b/packages/nodejs/src/probes/index.ts @@ -4,7 +4,72 @@ import { Probes } from "../interfaces" /** * The Minutely probes object. */ -export class BaseProbes extends EventEmitter implements Probes { +export class BaseProbes implements Probes { + #probes: InternalProbes + #stopped = false + + constructor({ stopped = false } = {}) { + this.#probes = new StartedProbes() + if (stopped) this.stop() + } + + public stop(): this { + this.#probes.clear() + this.#probes = new StoppedProbes() + this.#stopped = true + return this + } + + get isStopped(): boolean { + return this.#stopped + } + + get count(): number { + return this.#probes.count + } + + public register(name: string, fn: () => void): this { + this.#probes.register(name, fn) + return this + } + + public unregister(name: string): this { + this.#probes.unregister(name) + return this + } + + public clear(): this { + this.#probes.clear() + return this + } +} + +type InternalProbes = { + readonly count: number + register(name: string, fn: () => void): void + unregister(name: string): void + clear(): void +} + +class StoppedProbes implements InternalProbes { + public get count(): number { + return 0 + } + + public register(_name: string, _fn: () => void): this { + return this + } + + public unregister(_name: string): this { + return this + } + + public clear(): this { + return this + } +} + +class StartedProbes extends EventEmitter implements InternalProbes { #timers = new Map() constructor() { @@ -48,7 +113,7 @@ export class BaseProbes extends EventEmitter implements Probes { * Unregisters all probes and clears the timers. */ public clear(): this { - this.#timers.forEach(t => clearInterval(t)) + this.#timers.forEach(clearInterval) this.#timers = new Map() this.removeAllListeners() return this