Skip to content

Commit ab7f3c7

Browse files
committed
Implement opentelemetryInstrumentations getter
Implement an `opentelemetryInstrumentations` getter on the client, to allow users of the `initializeOpentelemetrySdk` config option to configure OpenTelemetry in an AppSignal-compatible way. This getter honors the settings in the `disableDefaultInstrumentations` and `additionalInstrumentations` configuration options. Implementing this was a bit of a refactor, so I took a moment to add tests for this and the `initializeOpentelemetrySdk` config option.
1 parent 7a21d95 commit ab7f3c7

File tree

5 files changed

+94
-31
lines changed

5 files changed

+94
-31
lines changed

.changesets/add-initialize-opentelemetry-sdk-config-option.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ Add `initializeOpentelemetrySdk` configuration option. This allows those who
77
would rather take control of how OpenTelemetry is initialised in their
88
application to skip AppSignal's initialization of the OpenTelemetry SDK.
99

10+
Additionally, add an `opentelemetryInstrumentations` method on the client,
11+
which returns AppSignal's default OpenTelemetry instrumentations, already
12+
configured to work correctly with AppSignal. The provided list of
13+
instrumentations will follow the `additionalInstrumentations` and
14+
`disableDefaultInstrumentations` config options, if those are set.
15+
1016
This is not the recommended way to use AppSignal for Node.js. Only use this
11-
config option if you're really sure that you know what you're doing.
17+
config option and this method if you're really sure that you know what
18+
you're doing.
1219

1320
When initialising OpenTelemetry, it is necessary to add the AppSignal span
1421
processor in order for data to be sent to AppSignal. For example, using the
@@ -20,12 +27,17 @@ import { SpanProcessor, Appsignal } from "@appsignal/nodejs";
2027

2128
const sdk = new NodeSDK({
2229
spanProcessor: new SpanProcessor(Appsignal.client)
30+
instrumentations: Appsignal.client.opentelemetryInstrumentations()
2331
});
2432

2533
sdk.start()
2634
```
2735

2836
The above snippet assumes that the AppSignal client has been initialised
29-
beforehand. For an optimal experience with AppSignal, those making use of this
30-
config option should configure their OpenTelemetry instrumentation in a
31-
similar way as it is done in the AppSignal integration's source code.
37+
beforehand.
38+
39+
When making use of this config option, the OpenTelemetry instrumentations
40+
must be configured in the same way as it is done in the AppSignal integration.
41+
In the above snippet, the `instrumentations` property in the OpenTelemetry SDK
42+
is set to the AppSignal client's list of OpenTelemetry instrumentations, which
43+
are configured to work correctly with AppSignal.

src/__tests__/client.test.ts

+53-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { InstrumentationTestRegistry } from "../../test/instrumentation_registry"
1+
import {
2+
InstrumentationTestRegistry,
3+
instrumentationNames
4+
} from "../../test/instrumentation_registry"
25
import { Extension } from "../extension"
36
import { Client } from "../client"
47
import { Metrics } from "../metrics"
@@ -16,6 +19,7 @@ describe("Client", () => {
1619
const DEFAULT_OPTS = { name, pushApiKey }
1720

1821
beforeEach(() => {
22+
InstrumentationTestRegistry.clear()
1923
client = new Client({ ...DEFAULT_OPTS })
2024
})
2125

@@ -142,16 +146,37 @@ describe("Client", () => {
142146
expect(meter).toBeInstanceOf(Metrics)
143147
})
144148

149+
it("initializes the OpenTelemetry SDK when active", () => {
150+
client = new Client({ ...DEFAULT_OPTS, active: true })
151+
expect(InstrumentationTestRegistry.didInitializeSDK).toEqual(true)
152+
})
153+
154+
it("does not initialize the OpenTelemetry SDK when its config option is false", () => {
155+
client = new Client({
156+
...DEFAULT_OPTS,
157+
active: true,
158+
initializeOpentelemetrySdk: false
159+
})
160+
expect(InstrumentationTestRegistry.didInitializeSDK).toEqual(false)
161+
})
162+
145163
describe("Instrumentations", () => {
146164
it("registers the default instrumentations", () => {
147165
client = new Client({ ...DEFAULT_OPTS, active: true })
148166
// Not testing against all of them or a fixed number so
149167
// that we don't have to change these tests every time we
150168
// add a new instrumentation.
151-
const instrumentationNames =
169+
const registryInstrumentationNames =
152170
InstrumentationTestRegistry.instrumentationNames()
153-
expect(instrumentationNames.length).toBeGreaterThan(10)
154-
expect(instrumentationNames).toContain(
171+
expect(registryInstrumentationNames.length).toBeGreaterThan(10)
172+
expect(registryInstrumentationNames).toContain(
173+
"@opentelemetry/instrumentation-http"
174+
)
175+
176+
const opentelemetryInstrumentations =
177+
client.opentelemetryInstrumentations()
178+
expect(opentelemetryInstrumentations.length).toBeGreaterThan(10)
179+
expect(instrumentationNames(opentelemetryInstrumentations)).toContain(
155180
"@opentelemetry/instrumentation-http"
156181
)
157182
})
@@ -165,6 +190,7 @@ describe("Client", () => {
165190
const instrumentationNames =
166191
InstrumentationTestRegistry.instrumentationNames()
167192
expect(instrumentationNames).toEqual([])
193+
expect(client.opentelemetryInstrumentations()).toEqual([])
168194
})
169195

170196
it("can disable some default instrumentations", () => {
@@ -173,12 +199,19 @@ describe("Client", () => {
173199
active: true,
174200
disableDefaultInstrumentations: ["@opentelemetry/instrumentation-http"]
175201
})
176-
const instrumentationNames =
202+
const registryInstrumentationNames =
177203
InstrumentationTestRegistry.instrumentationNames()
178-
expect(instrumentationNames).not.toContain(
204+
expect(registryInstrumentationNames).not.toContain(
205+
"@opentelemetry/instrumentation-http"
206+
)
207+
expect(registryInstrumentationNames.length).toBeGreaterThan(10)
208+
209+
const opentelemetryInstrumentations =
210+
client.opentelemetryInstrumentations()
211+
expect(instrumentationNames(opentelemetryInstrumentations)).not.toContain(
179212
"@opentelemetry/instrumentation-http"
180213
)
181-
expect(instrumentationNames.length).toBeGreaterThan(0)
214+
expect(opentelemetryInstrumentations.length).toBeGreaterThan(10)
182215
})
183216

184217
it("can add additional instrumentations", () => {
@@ -211,13 +244,22 @@ describe("Client", () => {
211244
additionalInstrumentations: [new TestInstrumentation()]
212245
})
213246

214-
const instrumentationNames =
247+
const registryInstrumentationNames =
215248
InstrumentationTestRegistry.instrumentationNames()
216-
expect(instrumentationNames).toContain(
249+
expect(registryInstrumentationNames).toContain(
250+
"@opentelemetry/instrumentation-http"
251+
)
252+
expect(registryInstrumentationNames.length).toBeGreaterThan(10)
253+
expect(registryInstrumentationNames).toContain("test/instrumentation")
254+
const opentelemetryInstrumentations =
255+
client.opentelemetryInstrumentations()
256+
expect(instrumentationNames(opentelemetryInstrumentations)).toContain(
217257
"@opentelemetry/instrumentation-http"
218258
)
219-
expect(instrumentationNames.length).toBeGreaterThan(10)
220-
expect(instrumentationNames).toContain("test/instrumentation")
259+
expect(opentelemetryInstrumentations.length).toBeGreaterThan(10)
260+
expect(instrumentationNames(opentelemetryInstrumentations)).toContain(
261+
"test/instrumentation"
262+
)
221263
})
222264
})
223265
})

src/client.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ type DefaultInstrumentationsConfigMap = {
7070
>
7171
}
7272

73-
type AdditionalInstrumentationsOption = NodeSDKConfiguration["instrumentations"]
73+
type NodeSDKInstrumentationsOption = NodeSDKConfiguration["instrumentations"]
7474

7575
export type Options = AppsignalOptions & {
76-
additionalInstrumentations: AdditionalInstrumentationsOption
76+
additionalInstrumentations: NodeSDKInstrumentationsOption
7777
}
7878

7979
/**
@@ -93,6 +93,7 @@ export class Client {
9393

9494
#metrics: Metrics
9595
#sdk?: NodeSDK
96+
#additionalInstrumentations: NodeSDKInstrumentationsOption
9697

9798
/**
9899
* Global accessors for the AppSignal client
@@ -138,6 +139,8 @@ export class Client {
138139
* Creates a new instance of the `Appsignal` object
139140
*/
140141
constructor(options: Partial<Options> = {}) {
142+
this.#additionalInstrumentations = options.additionalInstrumentations || []
143+
141144
this.config = new Configuration(options)
142145
this.extension = new Extension()
143146
this.integrationLogger = this.setUpIntegrationLogger()
@@ -153,9 +156,7 @@ export class Client {
153156
this.start()
154157
this.#metrics = new Metrics()
155158
if (this.config.data.initializeOpentelemetrySdk) {
156-
this.#sdk = this.initOpenTelemetry(
157-
options.additionalInstrumentations || []
158-
)
159+
this.#sdk = this.initOpenTelemetry()
159160
}
160161
}
161162
} else {
@@ -364,12 +365,16 @@ export class Client {
364365
)
365366
}
366367

368+
public opentelemetryInstrumentations(): NodeSDKInstrumentationsOption {
369+
return this.#additionalInstrumentations.concat(
370+
this.defaultInstrumentations()
371+
)
372+
}
373+
367374
/**
368375
* Initialises OpenTelemetry instrumentation
369376
*/
370-
private initOpenTelemetry(
371-
additionalInstrumentations: AdditionalInstrumentationsOption
372-
) {
377+
private initOpenTelemetry() {
373378
const testMode = process.env["_APPSIGNAL_TEST_MODE"]
374379
const testModeFilePath = process.env["_APPSIGNAL_TEST_MODE_FILE_PATH"]
375380
let spanProcessor
@@ -380,9 +385,7 @@ export class Client {
380385
spanProcessor = new SpanProcessor(this)
381386
}
382387

383-
const instrumentations = additionalInstrumentations.concat(
384-
this.defaultInstrumentations()
385-
)
388+
const instrumentations = this.opentelemetryInstrumentations()
386389

387390
const sdk = new NodeSDK({
388391
instrumentations,

test/instrumentation_registry.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,28 @@ import { Instrumentation } from "@opentelemetry/instrumentation"
33

44
type InstrumentationsOption = NodeSDKConfiguration["instrumentations"]
55

6+
export function instrumentationNames(instrumentations: InstrumentationsOption) {
7+
return instrumentations.map(
8+
instrumentation => (instrumentation as Instrumentation).instrumentationName
9+
)
10+
}
11+
612
export class InstrumentationTestRegistry {
713
static instrumentations?: InstrumentationsOption
14+
static didInitializeSDK = false
815

916
static setInstrumentations(instrumentations: InstrumentationsOption) {
1017
this.instrumentations = instrumentations
18+
this.didInitializeSDK = true
1119
}
1220

1321
static instrumentationNames() {
14-
return (this.instrumentations || []).map(
15-
instrumentation =>
16-
(instrumentation as Instrumentation).instrumentationName
17-
)
22+
return instrumentationNames(this.instrumentations || [])
1823
}
1924

2025
static clear() {
2126
this.instrumentations = undefined
27+
this.didInitializeSDK = false
2228
}
2329
}
2430

test/integration/diagnose

0 commit comments

Comments
 (0)