@sebspark/opentelemetry-instrumentation-opensearch 0.4.1 → 0.4.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sebspark/opentelemetry-instrumentation-opensearch
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 176ea40: Response attributes are read with type guards instead of type assertions. Values with an unexpected type, for example a non-numeric `took`, are now omitted instead of being recorded as-is. Non-JSON response bodies are ignored.
8
+
3
9
  ## 0.4.1
4
10
 
5
11
  ### Patch Changes
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import { suppressTracing } from "@opentelemetry/core";
6
6
  import { ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_DB_QUERY_TEXT, ATTR_DB_SYSTEM_NAME, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT } from "@opentelemetry/semantic-conventions";
7
7
  // package.json
8
8
  var name = "@sebspark/opentelemetry-instrumentation-opensearch";
9
- var version = "0.4.1";
9
+ var version = "0.4.2";
10
10
  // src/parse-path.ts
11
11
  const METHOD_OPERATIONS = {
12
12
  PUT: "indices.create",
@@ -34,6 +34,19 @@ const parsePath = (path, method) => {
34
34
  };
35
35
  // src/response-attributes.ts
36
36
  const ATTR_DB_OPENSEARCH_DURATION_MS = "db.opensearch.duration_ms";
37
+ const isRecord = (value) => typeof value === "object" && value !== null;
38
+ const setNumber = (attrs, key, value) => {
39
+ if (typeof value === "number") attrs[key] = value;
40
+ };
41
+ const setBoolean = (attrs, key, value) => {
42
+ if (typeof value === "boolean") attrs[key] = value;
43
+ };
44
+ /**
45
+ * Extracts span attributes from an OpenSearch response.
46
+ *
47
+ * The body is that of any API, so search fields are read only when present
48
+ * with the expected type.
49
+ */
37
50
  const extractResponseAttributes = (res) => {
38
51
  const attrs = {};
39
52
  const { hostname, port } = res.meta.connection.url;
@@ -41,28 +54,28 @@ const extractResponseAttributes = (res) => {
41
54
  if (port) attrs[ATTR_SERVER_PORT] = Number(port);
42
55
  if (res.statusCode != null) attrs[ATTR_HTTP_RESPONSE_STATUS_CODE] = res.statusCode;
43
56
  const body = res.body;
44
- if (body == null) return attrs;
45
- if (body.took != null) attrs["db.opensearch.took"] = body.took;
46
- if (body.timed_out != null) attrs["db.opensearch.timed_out"] = body.timed_out;
47
- if (body.terminated_early != null) attrs["db.opensearch.terminated_early"] = body.terminated_early;
57
+ if (!isRecord(body)) return attrs;
58
+ setNumber(attrs, "db.opensearch.took", body.took);
59
+ setBoolean(attrs, "db.opensearch.timed_out", body.timed_out);
60
+ setBoolean(attrs, "db.opensearch.terminated_early", body.terminated_early);
48
61
  const shards = body._shards;
49
- if (shards != null) {
50
- attrs["db.opensearch.shards.total"] = shards.total;
51
- attrs["db.opensearch.shards.successful"] = shards.successful;
52
- attrs["db.opensearch.shards.failed"] = shards.failed;
53
- if (shards.skipped != null) attrs["db.opensearch.shards.skipped"] = shards.skipped;
62
+ if (isRecord(shards)) {
63
+ setNumber(attrs, "db.opensearch.shards.total", shards.total);
64
+ setNumber(attrs, "db.opensearch.shards.successful", shards.successful);
65
+ setNumber(attrs, "db.opensearch.shards.failed", shards.failed);
66
+ setNumber(attrs, "db.opensearch.shards.skipped", shards.skipped);
54
67
  }
55
68
  const phaseTook = body.phase_took;
56
- if (phaseTook != null) {
57
- attrs["db.opensearch.phase_took.can_match"] = phaseTook.can_match;
58
- attrs["db.opensearch.phase_took.dfs_pre_query"] = phaseTook.dfs_pre_query;
59
- attrs["db.opensearch.phase_took.dfs_query"] = phaseTook.dfs_query;
60
- attrs["db.opensearch.phase_took.expand"] = phaseTook.expand;
61
- attrs["db.opensearch.phase_took.fetch"] = phaseTook.fetch;
62
- attrs["db.opensearch.phase_took.query"] = phaseTook.query;
63
- }
64
- const total = body.hits?.total;
65
- if (total != null && typeof total === "object") attrs["db.opensearch.hits.total"] = total.value;
69
+ if (isRecord(phaseTook)) for (const phase of [
70
+ "can_match",
71
+ "dfs_pre_query",
72
+ "dfs_query",
73
+ "expand",
74
+ "fetch",
75
+ "query"
76
+ ]) setNumber(attrs, `db.opensearch.phase_took.${phase}`, phaseTook[phase]);
77
+ const hits = body.hits;
78
+ if (isRecord(hits) && isRecord(hits.total)) setNumber(attrs, "db.opensearch.hits.total", hits.total.value);
66
79
  return attrs;
67
80
  };
68
81
  // src/serialize-body.ts
@@ -74,6 +87,8 @@ const serializeBody = (params, config) => {
74
87
  };
75
88
  // src/patch-request.ts
76
89
  const createPatchedRequest = (getTracer, config = {}, original, clientVersion) => {
90
+ const originalWithPromise = original;
91
+ const originalWithCallback = original;
77
92
  function patchedRequest(params, options, callback) {
78
93
  const { index, operation } = parsePath(params.path, params.method);
79
94
  const queryText = serializeBody(params, config);
@@ -103,13 +118,13 @@ const createPatchedRequest = (getTracer, config = {}, original, clientVersion) =
103
118
  };
104
119
  const callCtx = config.suppressInternalInstrumentation ? suppressTracing(context.active()) : trace.setSpan(context.active(), span);
105
120
  if (typeof callback === "function") {
106
- const originalWithCallback = original.bind(this);
107
- return context.with(callCtx, () => originalWithCallback(params, options, (err, res) => {
121
+ const request = originalWithCallback.bind(this);
122
+ return context.with(callCtx, () => request(params, options, (err, res) => {
108
123
  err ? onError(err) : onSuccess(res);
109
124
  callback(err, res);
110
125
  }));
111
126
  }
112
- const result = context.with(callCtx, () => original.call(this, params, options));
127
+ const result = context.with(callCtx, () => originalWithPromise.call(this, params, options));
113
128
  const settled = result.then((res) => {
114
129
  onSuccess(res);
115
130
  return res;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["pkg.name","pkg.version"],"sources":["../package.json","../src/parse-path.ts","../src/response-attributes.ts","../src/serialize-body.ts","../src/patch-request.ts","../src/index.ts"],"sourcesContent":["","const METHOD_OPERATIONS: Record<string, string> = {\n PUT: 'indices.create',\n DELETE: 'indices.delete',\n}\n\nconst OPERATION_ALIASES: Record<string, string> = {\n doc: 'index',\n}\n\nexport const parsePath = (\n path: string,\n method?: string\n): { index?: string; operation?: string } => {\n const decoded = decodeURIComponent(path)\n const match = decoded.match(/^\\/(?:([^/_][^/]*)\\/)?_([^/]+)/)\n if (match) {\n const operation = OPERATION_ALIASES[match[2]] ?? match[2]\n return { index: match[1], operation }\n }\n\n if (method) {\n const operation = METHOD_OPERATIONS[method.toUpperCase()]\n if (operation) {\n const index = decoded.replace(/^\\//, '') || undefined\n return { index, operation }\n }\n }\n\n return {}\n}\n","import type { Types } from '@opensearch-project/opensearch'\nimport type { ApiResponse } from '@opensearch-project/opensearch/lib/Transport.js'\nimport {\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n} from '@opentelemetry/semantic-conventions'\n\nexport const ATTR_DB_OPENSEARCH_DURATION_MS = 'db.opensearch.duration_ms'\n\nexport const extractResponseAttributes = (\n res: ApiResponse\n): Record<string, string | number | boolean> => {\n const attrs: Record<string, string | number | boolean> = {}\n\n const { hostname, port } = res.meta.connection.url\n attrs[ATTR_SERVER_ADDRESS] = hostname\n if (port) attrs[ATTR_SERVER_PORT] = Number(port)\n if (res.statusCode != null)\n attrs[ATTR_HTTP_RESPONSE_STATUS_CODE] = res.statusCode\n\n const body = res.body as Types.Core_Search.ResponseBody | null | undefined\n if (body == null) return attrs\n\n if (body.took != null) attrs['db.opensearch.took'] = body.took\n if (body.timed_out != null) attrs['db.opensearch.timed_out'] = body.timed_out\n if (body.terminated_early != null)\n attrs['db.opensearch.terminated_early'] = body.terminated_early\n\n const shards = body._shards\n if (shards != null) {\n attrs['db.opensearch.shards.total'] = shards.total\n attrs['db.opensearch.shards.successful'] = shards.successful\n attrs['db.opensearch.shards.failed'] = shards.failed\n if (shards.skipped != null)\n attrs['db.opensearch.shards.skipped'] = shards.skipped\n }\n\n const phaseTook = body.phase_took\n if (phaseTook != null) {\n attrs['db.opensearch.phase_took.can_match'] = phaseTook.can_match\n attrs['db.opensearch.phase_took.dfs_pre_query'] = phaseTook.dfs_pre_query\n attrs['db.opensearch.phase_took.dfs_query'] = phaseTook.dfs_query\n attrs['db.opensearch.phase_took.expand'] = phaseTook.expand\n attrs['db.opensearch.phase_took.fetch'] = phaseTook.fetch\n attrs['db.opensearch.phase_took.query'] = phaseTook.query\n }\n\n const total = body.hits?.total\n if (total != null && typeof total === 'object') {\n attrs['db.opensearch.hits.total'] = total.value\n }\n\n return attrs\n}\n","import type { TransportRequestParams } from '@opensearch-project/opensearch/lib/Transport.js'\nimport type { OpenSearchInstrumentationConfig } from './types'\n\nexport const serializeBody = (\n params: TransportRequestParams,\n config: Pick<OpenSearchInstrumentationConfig, 'dbStatementSerializer'>\n): string | undefined => {\n if (!params.body) return undefined\n if (config.dbStatementSerializer === false) return undefined\n if (typeof config.dbStatementSerializer === 'function') {\n return config.dbStatementSerializer(params)\n }\n return typeof params.body === 'string'\n ? params.body\n : JSON.stringify(params.body)\n}\n","import type Transport from '@opensearch-project/opensearch/lib/Transport.js'\nimport type {\n ApiError,\n ApiResponse,\n TransportRequestCallback,\n TransportRequestOptions,\n TransportRequestParams,\n TransportRequestPromise,\n} from '@opensearch-project/opensearch/lib/Transport.js'\nimport {\n context,\n SpanKind,\n SpanStatusCode,\n type Tracer,\n trace,\n} from '@opentelemetry/api'\nimport { suppressTracing } from '@opentelemetry/core'\nimport {\n ATTR_DB_COLLECTION_NAME,\n ATTR_DB_OPERATION_NAME,\n ATTR_DB_QUERY_TEXT,\n ATTR_DB_SYSTEM_NAME,\n} from '@opentelemetry/semantic-conventions'\nimport { parsePath } from './parse-path'\nimport {\n ATTR_DB_OPENSEARCH_DURATION_MS,\n extractResponseAttributes,\n} from './response-attributes'\nimport { serializeBody } from './serialize-body'\nimport type { OpenSearchInstrumentationConfig } from './types'\n\nexport type OriginalRequest = (\n this: Transport,\n params: TransportRequestParams,\n options?: TransportRequestOptions\n) => TransportRequestPromise<ApiResponse>\n\ntype OriginalRequestWithCallback = (\n this: Transport,\n params: TransportRequestParams,\n options: TransportRequestOptions | undefined,\n callback: (err: ApiError, result: ApiResponse) => void\n) => TransportRequestCallback\n\nexport const createPatchedRequest = (\n getTracer: () => Tracer,\n config: OpenSearchInstrumentationConfig = {},\n original: OriginalRequest,\n clientVersion: string\n) => {\n function patchedRequest(\n params: TransportRequestParams,\n options?: TransportRequestOptions\n ): TransportRequestPromise<ApiResponse>\n function patchedRequest(\n params: TransportRequestParams,\n options: TransportRequestOptions | undefined,\n callback: (err: ApiError, result: ApiResponse) => void\n ): TransportRequestCallback\n function patchedRequest(\n this: Transport,\n params: TransportRequestParams,\n options?: TransportRequestOptions,\n callback?: (err: ApiError, result: ApiResponse) => void\n ): TransportRequestPromise<ApiResponse> | TransportRequestCallback {\n const { index, operation } = parsePath(params.path, params.method)\n const queryText = serializeBody(params, config)\n\n const span = getTracer().startSpan(\n [operation, index].filter(Boolean).join(' ') || params.path,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n [ATTR_DB_SYSTEM_NAME]: 'opensearch',\n ...(operation && { [ATTR_DB_OPERATION_NAME]: operation }),\n ...(index && { [ATTR_DB_COLLECTION_NAME]: index }),\n ...(queryText && { [ATTR_DB_QUERY_TEXT]: queryText }),\n },\n }\n )\n config.requestHook?.(span, params)\n\n const startTime = Date.now()\n\n const onSuccess = (res: ApiResponse) => {\n span.setAttribute(ATTR_DB_OPENSEARCH_DURATION_MS, Date.now() - startTime)\n for (const [key, value] of Object.entries(\n extractResponseAttributes(res)\n )) {\n span.setAttribute(key, value)\n }\n if (config.moduleVersionAttributeName) {\n span.setAttribute(config.moduleVersionAttributeName, clientVersion)\n }\n config.responseHook?.(span, res)\n span.end()\n }\n\n const onError = (err: unknown) => {\n span.setAttribute(ATTR_DB_OPENSEARCH_DURATION_MS, Date.now() - startTime)\n if (err instanceof Error) span.recordException(err)\n span.setStatus({ code: SpanStatusCode.ERROR })\n span.end()\n }\n\n const callCtx = config.suppressInternalInstrumentation\n ? suppressTracing(context.active())\n : trace.setSpan(context.active(), span)\n\n if (typeof callback === 'function') {\n const originalWithCallback = (\n original as OriginalRequestWithCallback\n ).bind(this)\n return context.with(callCtx, () =>\n originalWithCallback(params, options, (err, res) => {\n err ? onError(err) : onSuccess(res)\n callback(err, res)\n })\n )\n }\n\n const result = context.with(callCtx, () =>\n original.call(this, params, options)\n )\n const settled = result.then(\n (res: ApiResponse) => {\n onSuccess(res)\n return res\n },\n (err: unknown) => {\n onError(err)\n throw err\n }\n )\n return Object.assign(settled, { abort: result.abort })\n }\n\n return patchedRequest\n}\n","import Transport from '@opensearch-project/opensearch/lib/Transport.js'\nimport opensearchPkg from '@opensearch-project/opensearch/package.json' with {\n type: 'json',\n}\nimport { InstrumentationBase } from '@opentelemetry/instrumentation'\nimport pkg from '../package.json' with { type: 'json' }\nimport { createPatchedRequest, type OriginalRequest } from './patch-request'\nimport type { OpenSearchInstrumentationConfig } from './types'\n\nexport type { OpenSearchInstrumentationConfig } from './types'\n\nconst PACKAGE_NAME: string = pkg.name\nconst PACKAGE_VERSION: string = pkg.version\nconst OPENSEARCH_CLIENT_VERSION: string = opensearchPkg.version\n\nexport class OpenSearchInstrumentation extends InstrumentationBase {\n private readonly osConfig: OpenSearchInstrumentationConfig\n private _original: OriginalRequest | undefined\n\n constructor(config: OpenSearchInstrumentationConfig = {}) {\n super(PACKAGE_NAME, PACKAGE_VERSION, { ...config, enabled: false })\n this.osConfig = config\n if (config.enabled !== false) {\n this.enable()\n }\n }\n\n protected init() {}\n\n override enable() {\n if (this._original) return\n this._original = Transport.prototype.request as OriginalRequest\n Transport.prototype.request = createPatchedRequest(\n () => this.tracer,\n this.osConfig,\n this._original,\n OPENSEARCH_CLIENT_VERSION\n )\n }\n\n override disable() {\n if (this._original) {\n Transport.prototype.request = this._original\n this._original = undefined\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["pkg.name","pkg.version"],"sources":["../package.json","../src/parse-path.ts","../src/response-attributes.ts","../src/serialize-body.ts","../src/patch-request.ts","../src/index.ts"],"sourcesContent":["","const METHOD_OPERATIONS: Record<string, string> = {\n PUT: 'indices.create',\n DELETE: 'indices.delete',\n}\n\nconst OPERATION_ALIASES: Record<string, string> = {\n doc: 'index',\n}\n\nexport const parsePath = (\n path: string,\n method?: string\n): { index?: string; operation?: string } => {\n const decoded = decodeURIComponent(path)\n const match = decoded.match(/^\\/(?:([^/_][^/]*)\\/)?_([^/]+)/)\n if (match) {\n const operation = OPERATION_ALIASES[match[2]] ?? match[2]\n return { index: match[1], operation }\n }\n\n if (method) {\n const operation = METHOD_OPERATIONS[method.toUpperCase()]\n if (operation) {\n const index = decoded.replace(/^\\//, '') || undefined\n return { index, operation }\n }\n }\n\n return {}\n}\n","import type { ApiResponse } from '@opensearch-project/opensearch/lib/Transport.js'\nimport {\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n} from '@opentelemetry/semantic-conventions'\n\nexport const ATTR_DB_OPENSEARCH_DURATION_MS = 'db.opensearch.duration_ms'\n\ntype Attributes = Record<string, string | number | boolean>\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null\n\nconst setNumber = (attrs: Attributes, key: string, value: unknown) => {\n if (typeof value === 'number') attrs[key] = value\n}\n\nconst setBoolean = (attrs: Attributes, key: string, value: unknown) => {\n if (typeof value === 'boolean') attrs[key] = value\n}\n\n/**\n * Extracts span attributes from an OpenSearch response.\n *\n * The body is that of any API, so search fields are read only when present\n * with the expected type.\n */\nexport const extractResponseAttributes = (res: ApiResponse): Attributes => {\n const attrs: Attributes = {}\n\n const { hostname, port } = res.meta.connection.url\n attrs[ATTR_SERVER_ADDRESS] = hostname\n if (port) attrs[ATTR_SERVER_PORT] = Number(port)\n if (res.statusCode != null)\n attrs[ATTR_HTTP_RESPONSE_STATUS_CODE] = res.statusCode\n\n const body: unknown = res.body\n if (!isRecord(body)) return attrs\n\n setNumber(attrs, 'db.opensearch.took', body.took)\n setBoolean(attrs, 'db.opensearch.timed_out', body.timed_out)\n setBoolean(attrs, 'db.opensearch.terminated_early', body.terminated_early)\n\n const shards = body._shards\n if (isRecord(shards)) {\n setNumber(attrs, 'db.opensearch.shards.total', shards.total)\n setNumber(attrs, 'db.opensearch.shards.successful', shards.successful)\n setNumber(attrs, 'db.opensearch.shards.failed', shards.failed)\n setNumber(attrs, 'db.opensearch.shards.skipped', shards.skipped)\n }\n\n const phaseTook = body.phase_took\n if (isRecord(phaseTook)) {\n for (const phase of [\n 'can_match',\n 'dfs_pre_query',\n 'dfs_query',\n 'expand',\n 'fetch',\n 'query',\n ]) {\n setNumber(attrs, `db.opensearch.phase_took.${phase}`, phaseTook[phase])\n }\n }\n\n const hits = body.hits\n if (isRecord(hits) && isRecord(hits.total)) {\n setNumber(attrs, 'db.opensearch.hits.total', hits.total.value)\n }\n\n return attrs\n}\n","import type { TransportRequestParams } from '@opensearch-project/opensearch/lib/Transport.js'\nimport type { OpenSearchInstrumentationConfig } from './types'\n\nexport const serializeBody = (\n params: TransportRequestParams,\n config: Pick<OpenSearchInstrumentationConfig, 'dbStatementSerializer'>\n): string | undefined => {\n if (!params.body) return undefined\n if (config.dbStatementSerializer === false) return undefined\n if (typeof config.dbStatementSerializer === 'function') {\n return config.dbStatementSerializer(params)\n }\n return typeof params.body === 'string'\n ? params.body\n : JSON.stringify(params.body)\n}\n","import type Transport from '@opensearch-project/opensearch/lib/Transport.js'\nimport type {\n ApiError,\n ApiResponse,\n TransportRequestCallback,\n TransportRequestOptions,\n TransportRequestParams,\n TransportRequestPromise,\n} from '@opensearch-project/opensearch/lib/Transport.js'\nimport {\n context,\n SpanKind,\n SpanStatusCode,\n type Tracer,\n trace,\n} from '@opentelemetry/api'\nimport { suppressTracing } from '@opentelemetry/core'\nimport {\n ATTR_DB_COLLECTION_NAME,\n ATTR_DB_OPERATION_NAME,\n ATTR_DB_QUERY_TEXT,\n ATTR_DB_SYSTEM_NAME,\n} from '@opentelemetry/semantic-conventions'\nimport { parsePath } from './parse-path'\nimport {\n ATTR_DB_OPENSEARCH_DURATION_MS,\n extractResponseAttributes,\n} from './response-attributes'\nimport { serializeBody } from './serialize-body'\nimport type { OpenSearchInstrumentationConfig } from './types'\n\nexport type OriginalRequest = Transport['request']\n\ntype OriginalRequestWithPromise = (\n this: Transport,\n params: TransportRequestParams,\n options?: TransportRequestOptions\n) => TransportRequestPromise<ApiResponse>\n\ntype OriginalRequestWithCallback = (\n this: Transport,\n params: TransportRequestParams,\n options: TransportRequestOptions | undefined,\n callback: (err: ApiError, result: ApiResponse) => void\n) => TransportRequestCallback\n\nexport const createPatchedRequest = (\n getTracer: () => Tracer,\n config: OpenSearchInstrumentationConfig = {},\n original: OriginalRequest,\n clientVersion: string\n) => {\n const originalWithPromise: OriginalRequestWithPromise = original\n const originalWithCallback: OriginalRequestWithCallback = original\n\n function patchedRequest(\n params: TransportRequestParams,\n options?: TransportRequestOptions\n ): TransportRequestPromise<ApiResponse>\n function patchedRequest(\n params: TransportRequestParams,\n options: TransportRequestOptions | undefined,\n callback: (err: ApiError, result: ApiResponse) => void\n ): TransportRequestCallback\n function patchedRequest(\n this: Transport,\n params: TransportRequestParams,\n options?: TransportRequestOptions,\n callback?: (err: ApiError, result: ApiResponse) => void\n ): TransportRequestPromise<ApiResponse> | TransportRequestCallback {\n const { index, operation } = parsePath(params.path, params.method)\n const queryText = serializeBody(params, config)\n\n const span = getTracer().startSpan(\n [operation, index].filter(Boolean).join(' ') || params.path,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n [ATTR_DB_SYSTEM_NAME]: 'opensearch',\n ...(operation && { [ATTR_DB_OPERATION_NAME]: operation }),\n ...(index && { [ATTR_DB_COLLECTION_NAME]: index }),\n ...(queryText && { [ATTR_DB_QUERY_TEXT]: queryText }),\n },\n }\n )\n config.requestHook?.(span, params)\n\n const startTime = Date.now()\n\n const onSuccess = (res: ApiResponse) => {\n span.setAttribute(ATTR_DB_OPENSEARCH_DURATION_MS, Date.now() - startTime)\n for (const [key, value] of Object.entries(\n extractResponseAttributes(res)\n )) {\n span.setAttribute(key, value)\n }\n if (config.moduleVersionAttributeName) {\n span.setAttribute(config.moduleVersionAttributeName, clientVersion)\n }\n config.responseHook?.(span, res)\n span.end()\n }\n\n const onError = (err: unknown) => {\n span.setAttribute(ATTR_DB_OPENSEARCH_DURATION_MS, Date.now() - startTime)\n if (err instanceof Error) span.recordException(err)\n span.setStatus({ code: SpanStatusCode.ERROR })\n span.end()\n }\n\n const callCtx = config.suppressInternalInstrumentation\n ? suppressTracing(context.active())\n : trace.setSpan(context.active(), span)\n\n if (typeof callback === 'function') {\n const request = originalWithCallback.bind(this)\n return context.with(callCtx, () =>\n request(params, options, (err, res) => {\n err ? onError(err) : onSuccess(res)\n callback(err, res)\n })\n )\n }\n\n const result = context.with(callCtx, () =>\n originalWithPromise.call(this, params, options)\n )\n const settled = result.then(\n (res: ApiResponse) => {\n onSuccess(res)\n return res\n },\n (err: unknown) => {\n onError(err)\n throw err\n }\n )\n return Object.assign(settled, { abort: result.abort })\n }\n\n return patchedRequest\n}\n","import Transport from '@opensearch-project/opensearch/lib/Transport.js'\nimport opensearchPkg from '@opensearch-project/opensearch/package.json' with {\n type: 'json',\n}\nimport { InstrumentationBase } from '@opentelemetry/instrumentation'\nimport pkg from '../package.json' with { type: 'json' }\nimport { createPatchedRequest, type OriginalRequest } from './patch-request'\nimport type { OpenSearchInstrumentationConfig } from './types'\n\nexport type { OpenSearchInstrumentationConfig } from './types'\n\nconst PACKAGE_NAME: string = pkg.name\nconst PACKAGE_VERSION: string = pkg.version\nconst OPENSEARCH_CLIENT_VERSION: string = opensearchPkg.version\n\nexport class OpenSearchInstrumentation extends InstrumentationBase {\n private readonly osConfig: OpenSearchInstrumentationConfig\n private _original: OriginalRequest | undefined\n\n constructor(config: OpenSearchInstrumentationConfig = {}) {\n super(PACKAGE_NAME, PACKAGE_VERSION, { ...config, enabled: false })\n this.osConfig = config\n if (config.enabled !== false) {\n this.enable()\n }\n }\n\n protected init() {}\n\n override enable() {\n if (this._original) return\n this._original = Transport.prototype.request\n Transport.prototype.request = createPatchedRequest(\n () => this.tracer,\n this.osConfig,\n this._original,\n OPENSEARCH_CLIENT_VERSION\n )\n }\n\n override disable() {\n if (this._original) {\n Transport.prototype.request = this._original\n this._original = undefined\n }\n }\n}\n"],"mappings":";;;;;;;;;;ACAA,MAAM,oBAA4C;CAChD,KAAK;CACL,QAAQ;AACV;AAEA,MAAM,oBAA4C,EAChD,KAAK,QACP;AAEA,MAAa,aACX,MACA,WAC2C;CAC3C,MAAM,UAAU,mBAAmB,IAAI;CACvC,MAAM,QAAQ,QAAQ,MAAM,gCAAgC;CAC5D,IAAI,OAAO;EACT,MAAM,YAAY,kBAAkB,MAAM,OAAO,MAAM;EACvD,OAAO;GAAE,OAAO,MAAM;GAAI;EAAU;CACtC;CAEA,IAAI,QAAQ;EACV,MAAM,YAAY,kBAAkB,OAAO,YAAY;EACvD,IAAI,WAEF,OAAO;GAAE,OADK,QAAQ,QAAQ,OAAO,EAAE,KAAK,KAAA;GAC5B;EAAU;CAE9B;CAEA,OAAO,CAAC;AACV;;ACtBA,MAAa,iCAAiC;AAI9C,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU;AAEzC,MAAM,aAAa,OAAmB,KAAa,UAAmB;CACpE,IAAI,OAAO,UAAU,UAAU,MAAM,OAAO;AAC9C;AAEA,MAAM,cAAc,OAAmB,KAAa,UAAmB;CACrE,IAAI,OAAO,UAAU,WAAW,MAAM,OAAO;AAC/C;;;;;;;AAQA,MAAa,6BAA6B,QAAiC;CACzE,MAAM,QAAoB,CAAC;CAE3B,MAAM,EAAE,UAAU,SAAS,IAAI,KAAK,WAAW;CAC/C,MAAM,uBAAuB;CAC7B,IAAI,MAAM,MAAM,oBAAoB,OAAO,IAAI;CAC/C,IAAI,IAAI,cAAc,MACpB,MAAM,kCAAkC,IAAI;CAE9C,MAAM,OAAgB,IAAI;CAC1B,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO;CAE5B,UAAU,OAAO,sBAAsB,KAAK,IAAI;CAChD,WAAW,OAAO,2BAA2B,KAAK,SAAS;CAC3D,WAAW,OAAO,kCAAkC,KAAK,gBAAgB;CAEzE,MAAM,SAAS,KAAK;CACpB,IAAI,SAAS,MAAM,GAAG;EACpB,UAAU,OAAO,8BAA8B,OAAO,KAAK;EAC3D,UAAU,OAAO,mCAAmC,OAAO,UAAU;EACrE,UAAU,OAAO,+BAA+B,OAAO,MAAM;EAC7D,UAAU,OAAO,gCAAgC,OAAO,OAAO;CACjE;CAEA,MAAM,YAAY,KAAK;CACvB,IAAI,SAAS,SAAS,GACpB,KAAK,MAAM,SAAS;EAClB;EACA;EACA;EACA;EACA;EACA;CACF,GACE,UAAU,OAAO,4BAA4B,SAAS,UAAU,MAAM;CAI1E,MAAM,OAAO,KAAK;CAClB,IAAI,SAAS,IAAI,KAAK,SAAS,KAAK,KAAK,GACvC,UAAU,OAAO,4BAA4B,KAAK,MAAM,KAAK;CAG/D,OAAO;AACT;;ACrEA,MAAa,iBACX,QACA,WACuB;CACvB,IAAI,CAAC,OAAO,MAAM,OAAO,KAAA;CACzB,IAAI,OAAO,0BAA0B,OAAO,OAAO,KAAA;CACnD,IAAI,OAAO,OAAO,0BAA0B,YAC1C,OAAO,OAAO,sBAAsB,MAAM;CAE5C,OAAO,OAAO,OAAO,SAAS,WAC1B,OAAO,OACP,KAAK,UAAU,OAAO,IAAI;AAChC;;AC+BA,MAAa,wBACX,WACA,SAA0C,CAAC,GAC3C,UACA,kBACG;CACH,MAAM,sBAAkD;CACxD,MAAM,uBAAoD;CAW1D,SAAS,eAEP,QACA,SACA,UACiE;EACjE,MAAM,EAAE,OAAO,cAAc,UAAU,OAAO,MAAM,OAAO,MAAM;EACjE,MAAM,YAAY,cAAc,QAAQ,MAAM;EAE9C,MAAM,OAAO,UAAU,CAAC,CAAC,UACvB,CAAC,WAAW,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,OAAO,MACvD;GACE,MAAM,SAAS;GACf,YAAY;KACT,sBAAsB;IACvB,GAAI,aAAa,GAAG,yBAAyB,UAAU;IACvD,GAAI,SAAS,GAAG,0BAA0B,MAAM;IAChD,GAAI,aAAa,GAAG,qBAAqB,UAAU;GACrD;EACF,CACF;EACA,OAAO,cAAc,MAAM,MAAM;EAEjC,MAAM,YAAY,KAAK,IAAI;EAE3B,MAAM,aAAa,QAAqB;GACtC,KAAK,aAAa,gCAAgC,KAAK,IAAI,IAAI,SAAS;GACxE,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,0BAA0B,GAAG,CAC/B,GACE,KAAK,aAAa,KAAK,KAAK;GAE9B,IAAI,OAAO,4BACT,KAAK,aAAa,OAAO,4BAA4B,aAAa;GAEpE,OAAO,eAAe,MAAM,GAAG;GAC/B,KAAK,IAAI;EACX;EAEA,MAAM,WAAW,QAAiB;GAChC,KAAK,aAAa,gCAAgC,KAAK,IAAI,IAAI,SAAS;GACxE,IAAI,eAAe,OAAO,KAAK,gBAAgB,GAAG;GAClD,KAAK,UAAU,EAAE,MAAM,eAAe,MAAM,CAAC;GAC7C,KAAK,IAAI;EACX;EAEA,MAAM,UAAU,OAAO,kCACnB,gBAAgB,QAAQ,OAAO,CAAC,IAChC,MAAM,QAAQ,QAAQ,OAAO,GAAG,IAAI;EAExC,IAAI,OAAO,aAAa,YAAY;GAClC,MAAM,UAAU,qBAAqB,KAAK,IAAI;GAC9C,OAAO,QAAQ,KAAK,eAClB,QAAQ,QAAQ,UAAU,KAAK,QAAQ;IACrC,MAAM,QAAQ,GAAG,IAAI,UAAU,GAAG;IAClC,SAAS,KAAK,GAAG;GACnB,CAAC,CACH;EACF;EAEA,MAAM,SAAS,QAAQ,KAAK,eAC1B,oBAAoB,KAAK,MAAM,QAAQ,OAAO,CAChD;EACA,MAAM,UAAU,OAAO,MACpB,QAAqB;GACpB,UAAU,GAAG;GACb,OAAO;EACT,IACC,QAAiB;GAChB,QAAQ,GAAG;GACX,MAAM;EACR,CACF;EACA,OAAO,OAAO,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM,CAAC;CACvD;CAEA,OAAO;AACT;;AClIA,MAAM,eAAuBA;AAC7B,MAAM,kBAA0BC;AAChC,MAAM,4BAAoC,cAAc;AAExD,IAAa,4BAAb,cAA+C,oBAAoB;CACjE;CACA;CAEA,YAAY,SAA0C,CAAC,GAAG;EACxD,MAAM,cAAc,iBAAiB;GAAE,GAAG;GAAQ,SAAS;EAAM,CAAC;EAClE,KAAK,WAAW;EAChB,IAAI,OAAO,YAAY,OACrB,KAAK,OAAO;CAEhB;CAEA,OAAiB,CAAC;CAElB,SAAkB;EAChB,IAAI,KAAK,WAAW;EACpB,KAAK,YAAY,UAAU,UAAU;EACrC,UAAU,UAAU,UAAU,2BACtB,KAAK,QACX,KAAK,UACL,KAAK,WACL,yBACF;CACF;CAEA,UAAmB;EACjB,IAAI,KAAK,WAAW;GAClB,UAAU,UAAU,UAAU,KAAK;GACnC,KAAK,YAAY,KAAA;EACnB;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebspark/opentelemetry-instrumentation-opensearch",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -20,7 +20,7 @@
20
20
  "lint": "biome check .",
21
21
  "test": "vitest run --passWithNoTests --coverage --fsModuleCachePath node_modules/.vitest-cache",
22
22
  "test:e2e": "vitest --config vitest.config.e2e.ts --run --fsModuleCachePath node_modules/.vitest-cache",
23
- "typecheck": "vitest --typecheck.only --passWithNoTests --run"
23
+ "typecheck": "tsc --noEmit -p . && vitest --typecheck.only --passWithNoTests --run"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@opensearch-project/opensearch": ">=2",