@depup/elastic-apm-node 4.15.0-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +26 -0
  2. package/NOTICE.md +442 -0
  3. package/README.md +48 -0
  4. package/changes.json +78 -0
  5. package/index.d.ts +398 -0
  6. package/index.js +11 -0
  7. package/lib/InflightEventSet.js +53 -0
  8. package/lib/activation-method.js +119 -0
  9. package/lib/agent.js +941 -0
  10. package/lib/apm-client/apm-client.js +313 -0
  11. package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
  12. package/lib/apm-client/http-apm-client/README.md +485 -0
  13. package/lib/apm-client/http-apm-client/central-config.js +41 -0
  14. package/lib/apm-client/http-apm-client/container-info.js +111 -0
  15. package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
  16. package/lib/apm-client/http-apm-client/index.js +1975 -0
  17. package/lib/apm-client/http-apm-client/logging.js +31 -0
  18. package/lib/apm-client/http-apm-client/ndjson.js +20 -0
  19. package/lib/apm-client/http-apm-client/truncate.js +434 -0
  20. package/lib/apm-client/noop-apm-client.js +73 -0
  21. package/lib/async-hooks-polyfill.js +58 -0
  22. package/lib/cloud-metadata/aws.js +175 -0
  23. package/lib/cloud-metadata/azure.js +123 -0
  24. package/lib/cloud-metadata/callback-coordination.js +159 -0
  25. package/lib/cloud-metadata/gcp.js +133 -0
  26. package/lib/cloud-metadata/index.js +175 -0
  27. package/lib/config/config.js +458 -0
  28. package/lib/config/normalizers.js +701 -0
  29. package/lib/config/schema.js +1007 -0
  30. package/lib/constants.js +35 -0
  31. package/lib/errors.js +303 -0
  32. package/lib/filters/sanitize-field-names.js +69 -0
  33. package/lib/http-request.js +249 -0
  34. package/lib/instrumentation/azure-functions.js +519 -0
  35. package/lib/instrumentation/context.js +56 -0
  36. package/lib/instrumentation/dropped-spans-stats.js +112 -0
  37. package/lib/instrumentation/elasticsearch-shared.js +63 -0
  38. package/lib/instrumentation/express-utils.js +91 -0
  39. package/lib/instrumentation/generic-span.js +322 -0
  40. package/lib/instrumentation/http-shared.js +424 -0
  41. package/lib/instrumentation/ids.js +39 -0
  42. package/lib/instrumentation/index.js +1127 -0
  43. package/lib/instrumentation/modules/@apollo/server.js +30 -0
  44. package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
  45. package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
  46. package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
  47. package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
  48. package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
  49. package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
  50. package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
  51. package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
  52. package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
  53. package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
  54. package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
  55. package/lib/instrumentation/modules/_lambda-handler.js +40 -0
  56. package/lib/instrumentation/modules/apollo-server-core.js +49 -0
  57. package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
  58. package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
  59. package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
  60. package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
  61. package/lib/instrumentation/modules/aws-sdk.js +76 -0
  62. package/lib/instrumentation/modules/bluebird.js +93 -0
  63. package/lib/instrumentation/modules/cassandra-driver.js +280 -0
  64. package/lib/instrumentation/modules/elasticsearch.js +191 -0
  65. package/lib/instrumentation/modules/express-graphql.js +66 -0
  66. package/lib/instrumentation/modules/express-queue.js +28 -0
  67. package/lib/instrumentation/modules/express.js +162 -0
  68. package/lib/instrumentation/modules/fastify.js +172 -0
  69. package/lib/instrumentation/modules/finalhandler.js +41 -0
  70. package/lib/instrumentation/modules/generic-pool.js +85 -0
  71. package/lib/instrumentation/modules/graphql.js +256 -0
  72. package/lib/instrumentation/modules/handlebars.js +22 -0
  73. package/lib/instrumentation/modules/http.js +112 -0
  74. package/lib/instrumentation/modules/http2.js +320 -0
  75. package/lib/instrumentation/modules/https.js +68 -0
  76. package/lib/instrumentation/modules/ioredis.js +94 -0
  77. package/lib/instrumentation/modules/jade.js +18 -0
  78. package/lib/instrumentation/modules/kafkajs.js +476 -0
  79. package/lib/instrumentation/modules/knex.js +91 -0
  80. package/lib/instrumentation/modules/koa-router.js +74 -0
  81. package/lib/instrumentation/modules/koa.js +15 -0
  82. package/lib/instrumentation/modules/memcached.js +99 -0
  83. package/lib/instrumentation/modules/mimic-response.js +45 -0
  84. package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
  85. package/lib/instrumentation/modules/mongodb-core.js +206 -0
  86. package/lib/instrumentation/modules/mongodb.js +259 -0
  87. package/lib/instrumentation/modules/mysql.js +200 -0
  88. package/lib/instrumentation/modules/mysql2.js +140 -0
  89. package/lib/instrumentation/modules/pg.js +148 -0
  90. package/lib/instrumentation/modules/pug.js +18 -0
  91. package/lib/instrumentation/modules/redis.js +176 -0
  92. package/lib/instrumentation/modules/restify.js +52 -0
  93. package/lib/instrumentation/modules/tedious.js +159 -0
  94. package/lib/instrumentation/modules/undici.js +270 -0
  95. package/lib/instrumentation/modules/ws.js +59 -0
  96. package/lib/instrumentation/noop-transaction.js +81 -0
  97. package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
  98. package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
  99. package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
  100. package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
  101. package/lib/instrumentation/run-context/RunContext.js +151 -0
  102. package/lib/instrumentation/run-context/index.js +23 -0
  103. package/lib/instrumentation/shimmer.js +123 -0
  104. package/lib/instrumentation/span-compression.js +239 -0
  105. package/lib/instrumentation/span.js +621 -0
  106. package/lib/instrumentation/template-shared.js +43 -0
  107. package/lib/instrumentation/timer.js +84 -0
  108. package/lib/instrumentation/transaction.js +571 -0
  109. package/lib/lambda.js +992 -0
  110. package/lib/load-source-map.js +100 -0
  111. package/lib/logging.js +212 -0
  112. package/lib/metrics/index.js +92 -0
  113. package/lib/metrics/platforms/generic/index.js +40 -0
  114. package/lib/metrics/platforms/generic/process-cpu.js +22 -0
  115. package/lib/metrics/platforms/generic/process-top.js +157 -0
  116. package/lib/metrics/platforms/generic/stats.js +34 -0
  117. package/lib/metrics/platforms/generic/system-cpu.js +51 -0
  118. package/lib/metrics/platforms/linux/index.js +19 -0
  119. package/lib/metrics/platforms/linux/stats.js +213 -0
  120. package/lib/metrics/queue.js +90 -0
  121. package/lib/metrics/registry.js +52 -0
  122. package/lib/metrics/reporter.js +119 -0
  123. package/lib/metrics/runtime.js +77 -0
  124. package/lib/middleware/connect.js +16 -0
  125. package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
  126. package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
  127. package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
  128. package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
  129. package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
  130. package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
  131. package/lib/opentelemetry-bridge/README.md +244 -0
  132. package/lib/opentelemetry-bridge/index.js +15 -0
  133. package/lib/opentelemetry-bridge/oblog.js +23 -0
  134. package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
  135. package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
  136. package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
  137. package/lib/opentelemetry-bridge/otelutils.js +99 -0
  138. package/lib/opentelemetry-bridge/setup.js +76 -0
  139. package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
  140. package/lib/opentelemetry-metrics/index.js +50 -0
  141. package/lib/parsers.js +225 -0
  142. package/lib/propwrap.js +147 -0
  143. package/lib/stacktraces.js +537 -0
  144. package/lib/symbols.js +15 -0
  145. package/lib/tracecontext/index.js +118 -0
  146. package/lib/tracecontext/traceparent.js +185 -0
  147. package/lib/tracecontext/tracestate.js +388 -0
  148. package/lib/wildcard-matcher.js +52 -0
  149. package/loader.mjs +7 -0
  150. package/package.json +299 -0
  151. package/start.d.ts +8 -0
  152. package/start.js +29 -0
  153. package/types/aws-lambda.d.ts +98 -0
  154. package/types/connect.d.ts +23 -0
@@ -0,0 +1,519 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and other contributors where applicable.
3
+ * Licensed under the BSD 2-Clause License; you may not use this file except in
4
+ * compliance with the BSD 2-Clause License.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ // Instrumentation of Azure Functions.
10
+ // Spec: https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-azure-functions.md
11
+ //
12
+ // This instrumentation is started if the `FUNCTIONS_WORKER_RUNTIME` envvar
13
+ // indicates we are in an Azure Functions environment. This is different from
14
+ // most instrumentations that hook into user code `require()`ing a particular
15
+ // module.
16
+ //
17
+ // The azure-functions-nodejs-worker repo holds the "nodejsWorker.js" process
18
+ // code in which user Functions are executed. That repo monkey-patches
19
+ // `Module.prototype.require` to inject a virtual `@azure/functions-core`
20
+ // module which exposes a hooks mechanism for invocation start and end. See
21
+ // https://github.com/Azure/azure-functions-nodejs-worker/blob/v3.5.2/src/setupCoreModule.ts#L20-L54
22
+ // and `registerHook` usage below.
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ const constants = require('../constants');
28
+
29
+ let isInstrumented = false;
30
+ let hookDisposables = []; // This holds the `Disposable` objects with which to remove previously registered @azure/functions-core hooks.
31
+
32
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-azure-functions.md#deriving-cold-starts
33
+ let isFirstRun = true;
34
+
35
+ // The trigger types for which we support special handling.
36
+ const TRIGGER_OTHER = 1; //
37
+ const TRIGGER_HTTP = 2; // https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook
38
+ const TRIGGER_TIMER = 3; // https://learn.microsoft.com/en-ca/azure/azure-functions/functions-bindings-timer
39
+
40
+ const TRANS_TYPE_FROM_TRIGGER_TYPE = {
41
+ [TRIGGER_OTHER]: 'request',
42
+ [TRIGGER_HTTP]: 'request',
43
+ // Note: `transaction.type = "scheduled"` is not in the shared APM agent spec,
44
+ // but the Java agent used the same value for some instrumentations.
45
+ [TRIGGER_TIMER]: 'scheduled',
46
+ };
47
+ // See APM spec and OTel `faas.trigger` at
48
+ // https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/faas/
49
+ const FAAS_TRIGGER_TYPE_FROM_TRIGGER_TYPE = {
50
+ [TRIGGER_OTHER]: 'other',
51
+ [TRIGGER_HTTP]: 'http',
52
+ // Note: `faas.trigger = "timer"` is not in the shared APM agent spec yet.
53
+ [TRIGGER_TIMER]: 'timer',
54
+ };
55
+
56
+ const gHttpRouteFromFuncDir = new Map();
57
+ const DEFAULT_ROUTE_PREFIX = 'api';
58
+ let gRoutePrefix = null;
59
+
60
+ // Mimic a subset of `FunctionInfo` from Azure code
61
+ // https://github.com/Azure/azure-functions-nodejs-library/blob/v3.5.0/src/FunctionInfo.ts
62
+ // to help with handling.
63
+ // ...plus some additional functionality for `httpRoute` and `routePrefix`.
64
+ class FunctionInfo {
65
+ constructor(bindingDefinitions, executionContext, log) {
66
+ // Example `bindingDefinitions`:
67
+ // [{"name":"req","type":"httpTrigger","direction":"in"},
68
+ // {"name":"res","type":"http","direction":"out"}]
69
+ this.triggerType = TRIGGER_OTHER;
70
+ this.httpOutputName = '';
71
+ this.hasHttpTrigger = false;
72
+ this.hasReturnBinding = false;
73
+ this.outputBindingNames = [];
74
+ for (const bd of bindingDefinitions) {
75
+ if (bd.direction !== 'in') {
76
+ if (bd.type && bd.type.toLowerCase() === 'http') {
77
+ this.httpOutputName = bd.name;
78
+ }
79
+ this.outputBindingNames.push(bd.name);
80
+ if (bd.name === '$return') {
81
+ this.hasReturnBinding = true;
82
+ }
83
+ }
84
+ if (bd.type) {
85
+ const typeLc = bd.type.toLowerCase();
86
+ switch (typeLc) {
87
+ case 'httptrigger': // "type": "httpTrigger"
88
+ this.triggerType = TRIGGER_HTTP;
89
+ break;
90
+ case 'timertrigger':
91
+ this.triggerType = TRIGGER_TIMER;
92
+ break;
93
+ }
94
+ }
95
+ }
96
+
97
+ // If this is an HTTP triggered-function, then get its route template and
98
+ // route prefix.
99
+ // https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger#customize-the-http-endpoint
100
+ // A possible custom "route" is not included in the given context, so we
101
+ // attempt to load the "function.json" file. A possible custom route prefix
102
+ // is in "host.json".
103
+ this.httpRoute = null;
104
+ this.routePrefix = null;
105
+ if (this.triggerType === TRIGGER_HTTP) {
106
+ const funcDir = executionContext.functionDirectory;
107
+ if (!funcDir) {
108
+ this.httpRoute = executionContext.functionName;
109
+ } else if (gHttpRouteFromFuncDir.has(funcDir)) {
110
+ this.httpRoute = gHttpRouteFromFuncDir.get(funcDir);
111
+ } else {
112
+ try {
113
+ const fj = JSON.parse(
114
+ fs.readFileSync(path.join(funcDir, 'function.json')),
115
+ );
116
+ for (let i = 0; i < fj.bindings.length; i++) {
117
+ const binding = fj.bindings[i];
118
+ if (
119
+ binding.direction === 'in' &&
120
+ binding.type &&
121
+ binding.type.toLowerCase() === 'httptrigger'
122
+ ) {
123
+ if (binding.route !== undefined) {
124
+ this.httpRoute = binding.route;
125
+ } else {
126
+ this.httpRoute = executionContext.functionName;
127
+ }
128
+ gHttpRouteFromFuncDir.set(funcDir, this.httpRoute);
129
+ }
130
+ }
131
+ log.trace(
132
+ { funcDir, httpRoute: this.httpRoute },
133
+ 'azure-functions: loaded route',
134
+ );
135
+ } catch (httpRouteErr) {
136
+ log.debug(
137
+ 'azure-functions: could not determine httpRoute for function %s: %s',
138
+ executionContext.functionName,
139
+ httpRouteErr.message,
140
+ );
141
+ this.httpRoute = executionContext.functionName;
142
+ }
143
+ }
144
+
145
+ if (gRoutePrefix) {
146
+ this.routePrefix = gRoutePrefix;
147
+ } else if (!funcDir) {
148
+ this.routePrefix = gRoutePrefix = DEFAULT_ROUTE_PREFIX;
149
+ } else {
150
+ try {
151
+ const hj = JSON.parse(
152
+ fs.readFileSync(path.join(path.dirname(funcDir), 'host.json')),
153
+ );
154
+ if (
155
+ hj &&
156
+ hj.extensions &&
157
+ hj.extensions.http &&
158
+ hj.extensions.http.routePrefix !== undefined
159
+ ) {
160
+ const rawRoutePrefix = hj.extensions.http.routePrefix;
161
+ this.routePrefix = gRoutePrefix = normRoutePrefix(rawRoutePrefix);
162
+ log.trace(
163
+ { hj, routePrefix: this.routePrefix, rawRoutePrefix },
164
+ 'azure-functions: loaded route prefix',
165
+ );
166
+ } else {
167
+ this.routePrefix = gRoutePrefix = DEFAULT_ROUTE_PREFIX;
168
+ }
169
+ } catch (routePrefixErr) {
170
+ log.debug(
171
+ 'azure-functions: could not determine routePrefix: %s',
172
+ routePrefixErr.message,
173
+ );
174
+ this.routePrefix = gRoutePrefix = DEFAULT_ROUTE_PREFIX;
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ // Normalize a routePrefix to *not* have a leading slash.
182
+ //
183
+ // Given routePrefix='/foo' and functionName='MyFn', Microsoft.AspNetCore.Routing
184
+ // will create a route `//foo/MyFn`. Actual HTTP requests to `GET /foo/MyFn`,
185
+ // `GET //foo/MyFn`, and any number of leading slashes will work. So let's
186
+ // settle on the more typical single leading slash.
187
+ function normRoutePrefix(routePrefix) {
188
+ return routePrefix.startsWith('/') ? routePrefix.slice(1) : routePrefix;
189
+ }
190
+
191
+ /**
192
+ * Set transaction data for HTTP triggers from the Lambda function result.
193
+ */
194
+ function setTransDataFromHttpTriggerResult(trans, hookCtx) {
195
+ if (hookCtx.error) {
196
+ trans.setOutcome(constants.OUTCOME_FAILURE);
197
+ trans.result = 'HTTP 5xx';
198
+ trans.res = {
199
+ statusCode: 500,
200
+ };
201
+ return;
202
+ }
203
+
204
+ // Attempt to get what the Azure Functions system will use for the HTTP response
205
+ // data. This is a pain because Azure Functions supports a number of different
206
+ // ways the user can return a response. Part of the handling for this is:
207
+ // https://github.com/Azure/azure-functions-nodejs-library/blob/v3.5.0/src/InvocationModel.ts#L77-L144
208
+ const funcInfo = hookCtx.hookData.funcInfo;
209
+ const result = hookCtx.result;
210
+ const context = hookCtx.invocationContext;
211
+ let httpRes;
212
+ if (funcInfo.hasReturnBinding) {
213
+ httpRes = hookCtx.result;
214
+ } else {
215
+ if (
216
+ result &&
217
+ typeof result === 'object' &&
218
+ result[funcInfo.httpOutputName] !== undefined
219
+ ) {
220
+ httpRes = result[funcInfo.httpOutputName];
221
+ } else if (
222
+ context.bindings &&
223
+ context.bindings[funcInfo.httpOutputName] !== undefined
224
+ ) {
225
+ httpRes = context.bindings[funcInfo.httpOutputName];
226
+ } else if (context.res !== undefined) {
227
+ httpRes = context.res;
228
+ }
229
+ }
230
+
231
+ // Azure Functions requires that the HTTP output response value be an 'object',
232
+ // otherwise it errors out the response (statusCode=500) and logs an error:
233
+ // Stack: Error: The HTTP response must be an 'object' type that can include properties such as 'body', 'status', and 'headers'. Learn more: https://go.microsoft.com/fwlink/?linkid=2112563
234
+ if (typeof httpRes !== 'object') {
235
+ trans.setOutcome(constants.OUTCOME_FAILURE);
236
+ trans.result = 'HTTP 5xx';
237
+ trans.res = {
238
+ statusCode: 500,
239
+ };
240
+ return;
241
+ }
242
+
243
+ let statusCode = Number(httpRes.status);
244
+ if (!Number.isInteger(statusCode)) {
245
+ // While https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger
246
+ // suggests the default may be "HTTP 204 No Content", my observation is that
247
+ // 200 is the actual default.
248
+ statusCode = 200;
249
+ }
250
+
251
+ if (statusCode < 500) {
252
+ trans.setOutcome(constants.OUTCOME_SUCCESS);
253
+ } else {
254
+ trans.setOutcome(constants.OUTCOME_FAILURE);
255
+ }
256
+ trans.result = 'HTTP ' + statusCode.toString()[0] + 'xx';
257
+ trans.res = {
258
+ statusCode,
259
+ body: httpRes.body,
260
+ };
261
+ if (httpRes.headers && typeof httpRes.headers === 'object') {
262
+ trans.res.headers = httpRes.headers;
263
+ }
264
+ }
265
+
266
+ // The Azure account id is also called the "subscription GUID".
267
+ // https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings#app-environment
268
+ function getAzureAccountId() {
269
+ return (
270
+ process.env.WEBSITE_OWNER_NAME &&
271
+ process.env.WEBSITE_OWNER_NAME.split('+', 1)[0]
272
+ );
273
+ }
274
+
275
+ // ---- exports
276
+
277
+ const isAzureFunctionsEnvironment = !!process.env.FUNCTIONS_WORKER_RUNTIME;
278
+
279
+ // Gather APM metadata for this Azure Function instance per
280
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-azure-functions.md#metadata
281
+ function getAzureFunctionsExtraMetadata() {
282
+ const metadata = {
283
+ service: {
284
+ framework: {
285
+ // Passing this service.framework.name to Client#setExtraMetadata()
286
+ // ensures that it "wins" over a framework name from
287
+ // `agent.setFramework()`, because in the client `_extraMetadata`
288
+ // wins over `_conf.frameworkName`.
289
+ name: 'Azure Functions',
290
+ version: process.env.FUNCTIONS_EXTENSION_VERSION,
291
+ },
292
+ runtime: {
293
+ name: process.env.FUNCTIONS_WORKER_RUNTIME,
294
+ },
295
+ node: {
296
+ configured_name: process.env.WEBSITE_INSTANCE_ID,
297
+ },
298
+ },
299
+ // https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#azure-functions
300
+ cloud: {
301
+ provider: 'azure',
302
+ region: process.env.REGION_NAME,
303
+ service: {
304
+ name: 'functions',
305
+ },
306
+ },
307
+ };
308
+ const accountId = getAzureAccountId();
309
+ if (accountId) {
310
+ metadata.cloud.account = { id: accountId };
311
+ }
312
+ if (process.env.WEBSITE_SITE_NAME) {
313
+ metadata.cloud.instance = { name: process.env.WEBSITE_SITE_NAME };
314
+ }
315
+ if (process.env.WEBSITE_RESOURCE_GROUP) {
316
+ metadata.cloud.project = { name: process.env.WEBSITE_RESOURCE_GROUP };
317
+ }
318
+ return metadata;
319
+ }
320
+
321
+ function instrument(agent) {
322
+ if (isInstrumented) {
323
+ return;
324
+ }
325
+ isInstrumented = true;
326
+
327
+ const ins = agent._instrumentation;
328
+ const log = agent.logger;
329
+ let d;
330
+
331
+ let core;
332
+ try {
333
+ core = require('@azure/functions-core');
334
+ } catch (err) {
335
+ log.warn(
336
+ { err },
337
+ 'could not import "@azure/functions-core": skipping Azure Functions instrumentation',
338
+ );
339
+ return;
340
+ }
341
+
342
+ // Note: We *could* hook into 'appTerminate' to attempt a quick flush of the
343
+ // current intake request. However, I have not seen a need for it yet.
344
+ // d = core.registerHook('appTerminate', async (hookCtx) => {
345
+ // log.trace('azure-functions: appTerminate')
346
+ // // flush here ...
347
+ // })
348
+ // hookDisposables.push(d)
349
+
350
+ // See examples at https://github.com/Azure/azure-functions-nodejs-worker/issues/522
351
+ d = core.registerHook('preInvocation', (hookCtx) => {
352
+ if (!hookCtx.invocationContext) {
353
+ // Doesn't look like `require('@azure/functions-core').PreInvocationContext`. Abort.
354
+ return;
355
+ }
356
+
357
+ const context = hookCtx.invocationContext;
358
+ const invocationId = context.invocationId;
359
+ log.trace({ invocationId }, 'azure-functions: preInvocation');
360
+
361
+ const isColdStart = isFirstRun;
362
+ if (isFirstRun) {
363
+ isFirstRun = false;
364
+ }
365
+
366
+ // In programming model v3 the InvocationContext includes
367
+ // `bindingDefinitions` and `executionContext`. In v4 the structure is a
368
+ // little different.
369
+ let bindingDefinitions = context.bindingDefinitions;
370
+ if (!bindingDefinitions) {
371
+ bindingDefinitions = [];
372
+ // Input bindings
373
+ bindingDefinitions.push({
374
+ name: context?.options?.trigger?.name,
375
+ type: context?.options?.trigger?.type,
376
+ direction: context?.options?.trigger?.direction,
377
+ });
378
+ // Output bindings
379
+ if (context?.options?.return) {
380
+ bindingDefinitions.push(context?.options?.return);
381
+ }
382
+ }
383
+ let executionContext = context.executionContext;
384
+ if (!executionContext) {
385
+ executionContext = {
386
+ functionDirectory: '',
387
+ functionName: context.functionName,
388
+ };
389
+ }
390
+
391
+ const funcInfo = (hookCtx.hookData.funcInfo = new FunctionInfo(
392
+ bindingDefinitions,
393
+ executionContext,
394
+ log,
395
+ ));
396
+ const triggerType = funcInfo.triggerType;
397
+
398
+ // `InvocationContext.traceContext` is broken: it results in sampled=false
399
+ // (i.e. traces are discarded) and/or broken traces because it creates an
400
+ // internal Span ID in the trace that cannot be ingested.
401
+ // See this for full explanation:
402
+ // https://github.com/elastic/apm-agent-nodejs/pull/4426#issuecomment-2596922653
403
+ let traceparent;
404
+ let tracestate;
405
+ if (triggerType === TRIGGER_HTTP && context?.req?.headers?.traceparent) {
406
+ traceparent = context.req.headers.traceparent;
407
+ tracestate = context.req.headers.tracestate;
408
+ log.trace(
409
+ { traceparent, tracestate },
410
+ 'azure-functions: get trace-context from HTTP trigger request headers',
411
+ );
412
+ }
413
+
414
+ const trans = (hookCtx.hookData.trans = ins.startTransaction(
415
+ // This is the default name. Trigger-specific values are added below.
416
+ executionContext.functionName,
417
+ TRANS_TYPE_FROM_TRIGGER_TYPE[triggerType],
418
+ {
419
+ childOf: traceparent,
420
+ tracestate,
421
+ },
422
+ ));
423
+
424
+ // Expected env vars are documented at:
425
+ // https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings
426
+ const accountId = getAzureAccountId();
427
+ const resourceGroup = process.env.WEBSITE_RESOURCE_GROUP;
428
+ const fnAppName = process.env.WEBSITE_SITE_NAME;
429
+ const fnName = executionContext.functionName;
430
+ const faasData = {
431
+ trigger: {
432
+ type: FAAS_TRIGGER_TYPE_FROM_TRIGGER_TYPE[triggerType],
433
+ },
434
+ execution: invocationId,
435
+ coldstart: isColdStart,
436
+ };
437
+ if (accountId && resourceGroup && fnAppName) {
438
+ faasData.id = `/subscriptions/${accountId}/resourceGroups/${resourceGroup}/providers/Microsoft.Web/sites/${fnAppName}/functions/${fnName}`;
439
+ }
440
+ if (fnAppName && fnName) {
441
+ faasData.name = `${fnAppName}/${fnName}`;
442
+ }
443
+ trans.setFaas(faasData);
444
+
445
+ if (triggerType === TRIGGER_HTTP) {
446
+ // The request object is the first item in `hookCtx.inputs`. See:
447
+ // https://github.com/Azure/azure-functions-nodejs-worker/blob/v3.5.2/src/eventHandlers/InvocationHandler.ts#L127
448
+ const req = hookCtx.inputs[0];
449
+ if (req) {
450
+ trans.req = req; // Used for setting `trans.context.request` by `getContextFromRequest()`.
451
+ if (agent._conf.usePathAsTransactionName && req.url) {
452
+ trans.setDefaultName(`${req.method} ${new URL(req.url).pathname}`);
453
+ } else {
454
+ const route = funcInfo.routePrefix
455
+ ? `/${funcInfo.routePrefix}/${funcInfo.httpRoute}`
456
+ : `/${funcInfo.httpRoute}`;
457
+ trans.setDefaultName(`${req.method} ${route}`);
458
+ }
459
+ }
460
+ }
461
+ });
462
+ hookDisposables.push(d);
463
+
464
+ d = core.registerHook('postInvocation', (hookCtx) => {
465
+ if (!hookCtx.invocationContext) {
466
+ // Doesn't look like `require('@azure/functions-core').PreInvocationContext`. Abort.
467
+ return;
468
+ }
469
+ const invocationId = hookCtx.invocationContext.invocationId;
470
+ log.trace({ invocationId }, 'azure-functions: postInvocation');
471
+
472
+ const trans = hookCtx.hookData.trans;
473
+ if (!trans) {
474
+ return;
475
+ }
476
+
477
+ const funcInfo = hookCtx.hookData.funcInfo;
478
+ if (funcInfo.triggerType === TRIGGER_HTTP) {
479
+ setTransDataFromHttpTriggerResult(trans, hookCtx);
480
+ } else if (hookCtx.error) {
481
+ trans.result = constants.RESULT_FAILURE;
482
+ trans.setOutcome(constants.OUTCOME_FAILURE);
483
+ } else {
484
+ trans.result = constants.RESULT_SUCCESS;
485
+ trans.setOutcome(constants.OUTCOME_SUCCESS);
486
+ }
487
+
488
+ if (hookCtx.error) {
489
+ // Capture the error before trans.end() so it associates with the
490
+ // current trans. `skipOutcome` to avoid setting outcome on a possible
491
+ // currentSpan, because this error applies to the transaction, not any
492
+ // sub-span.
493
+ agent.captureError(hookCtx.error, { skipOutcome: true });
494
+ }
495
+
496
+ trans.end();
497
+ });
498
+ hookDisposables.push(d);
499
+ }
500
+
501
+ function uninstrument() {
502
+ if (!isInstrumented) {
503
+ return;
504
+ }
505
+ isInstrumented = false;
506
+
507
+ // Unregister `core.registerHook()` calls from above.
508
+ hookDisposables.forEach((d) => {
509
+ d.dispose();
510
+ });
511
+ hookDisposables = [];
512
+ }
513
+
514
+ module.exports = {
515
+ isAzureFunctionsEnvironment,
516
+ getAzureFunctionsExtraMetadata,
517
+ instrument,
518
+ uninstrument,
519
+ };
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and other contributors where applicable.
3
+ * Licensed under the BSD 2-Clause License; you may not use this file except in
4
+ * compliance with the BSD 2-Clause License.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ var { parseUrl } = require('../parsers');
10
+
11
+ // Get the port number including the default port for a protocols
12
+ function getPortNumber(port, protocol) {
13
+ if (port === '') {
14
+ port = protocol === 'http:' ? '80' : protocol === 'https:' ? '443' : '';
15
+ }
16
+ return port;
17
+ }
18
+
19
+ exports.getHTTPDestination = function (url) {
20
+ const { port, protocol, hostname } = parseUrl(url);
21
+ const portNumber = getPortNumber(port, protocol);
22
+
23
+ // If hostname begins with [ and ends with ], assume that it's an IPv6 address.
24
+ // since address and port are recorded separately, we are recording the
25
+ // info in canonical form without square brackets
26
+ const ipv6Hostname =
27
+ hostname[0] === '[' && hostname[hostname.length - 1] === ']';
28
+
29
+ const address = ipv6Hostname ? hostname.slice(1, -1) : hostname;
30
+
31
+ return {
32
+ address,
33
+ port: Number(portNumber),
34
+ };
35
+ };
36
+
37
+ exports.getDBDestination = function (host, port) {
38
+ const destination = {};
39
+ let haveValues = false;
40
+
41
+ if (host) {
42
+ destination.address = host;
43
+ haveValues = true;
44
+ }
45
+ port = Number(port);
46
+ if (port) {
47
+ destination.port = port;
48
+ haveValues = true;
49
+ }
50
+
51
+ if (haveValues) {
52
+ return destination;
53
+ } else {
54
+ return null;
55
+ }
56
+ };
@@ -0,0 +1,112 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and other contributors where applicable.
3
+ * Licensed under the BSD 2-Clause License; you may not use this file except in
4
+ * compliance with the BSD 2-Clause License.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const MAX_DROPPED_SPANS_STATS = 128;
10
+
11
+ class DroppedSpansStats {
12
+ constructor() {
13
+ this.statsMap = new Map();
14
+ }
15
+
16
+ /**
17
+ * Record this span in dropped spans stats.
18
+ *
19
+ * @param {Span} span
20
+ * @returns {boolean} True iff this span was added to stats. This return value
21
+ * is only used for testing.
22
+ */
23
+ captureDroppedSpan(span) {
24
+ if (!span) {
25
+ return false;
26
+ }
27
+
28
+ const serviceTargetType = span._serviceTarget && span._serviceTarget.type;
29
+ const serviceTargetName = span._serviceTarget && span._serviceTarget.name;
30
+ const resource =
31
+ span._destination &&
32
+ span._destination.service &&
33
+ span._destination.service.resource;
34
+ if (
35
+ !span._exitSpan ||
36
+ !(serviceTargetType || serviceTargetName) ||
37
+ !resource
38
+ ) {
39
+ return false;
40
+ }
41
+
42
+ const stats = this._getOrCreateStats(
43
+ serviceTargetType,
44
+ serviceTargetName,
45
+ resource,
46
+ span.outcome,
47
+ );
48
+ if (!stats) {
49
+ return false;
50
+ }
51
+ stats.duration.count++;
52
+ stats.duration.sum.us += span._duration * 1000;
53
+ return true;
54
+ }
55
+
56
+ _getOrCreateStats(serviceTargetType, serviceTargetName, resource, outcome) {
57
+ const key = [serviceTargetType, serviceTargetName, resource, outcome].join(
58
+ '',
59
+ );
60
+ let stats = this.statsMap.get(key);
61
+ if (stats) {
62
+ return stats;
63
+ }
64
+
65
+ if (this.statsMap.size >= MAX_DROPPED_SPANS_STATS) {
66
+ return;
67
+ }
68
+
69
+ stats = {
70
+ duration: {
71
+ count: 0,
72
+ sum: {
73
+ us: 0,
74
+ },
75
+ },
76
+ destination_service_resource: resource,
77
+ outcome,
78
+ };
79
+ if (serviceTargetType) {
80
+ stats.service_target_type = serviceTargetType;
81
+ }
82
+ if (serviceTargetName) {
83
+ stats.service_target_name = serviceTargetName;
84
+ }
85
+ this.statsMap.set(key, stats);
86
+ return stats;
87
+ }
88
+
89
+ encode() {
90
+ // `duration.sum.us` is an integer in the intake API, but is stored as
91
+ // a float. We assume this `.encode()` is typically only called when the
92
+ // transaction is ended, so the in-place loss of the fractional value is
93
+ // acceptable.
94
+ const result = [];
95
+ for (const stats of this.statsMap.values()) {
96
+ stats.duration.sum.us = Math.round(stats.duration.sum.us);
97
+ result.push(stats);
98
+ }
99
+ return result;
100
+ }
101
+
102
+ size() {
103
+ return this.statsMap.size;
104
+ }
105
+ }
106
+
107
+ module.exports = {
108
+ DroppedSpansStats,
109
+
110
+ // Exported for testing-only.
111
+ MAX_DROPPED_SPANS_STATS,
112
+ };