-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: embed Aspecto instrumentations (#754)
* feat: add testing harness and kafkajs instrumentation port * feat: port elasticsearch instrumentation * feat: add typeorm port * chore: readd fsevents to package-lock * feat: port sequelize instrumentation * chore: fix deps and dev imports * feat: remove version files, fix compilation, remove use of arguments * chore: setup python in ci steps * chore: ci debug * chore: set up older python version * chore: upgrade python version * chore: increase python version * chore: try python 3 via apt * downgrade ubuntu * python 3 please * set up python after node * dont set up python * ignore optional deps * try apt * downgrade ubuntu * try apt get * use 14-buster * platform check python versions * check python versions on centos * remove python version checks on centos * test without sqlite3 * mock sqlite for tests * revert gh ci changes * debug windows tests * mocha debug * rever debug changes * skip some elasticsearch tests on windows * add NOTICE * combine elastic attributes into a single file * readd node 14.0.0 * remove timeout from isntrumentations tests
- Loading branch information
Showing
38 changed files
with
7,787 additions
and
2,247 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Splunk's Distribution of OpenTelemetry for Node.js | ||
Copyright 2023 Splunk Inc. | ||
|
||
Parts of the library, such as elasticsearch, kafkajs, sequelize and typeorm instrumentations, | ||
are copied and derived from instrumentations (https://github.com/aspecto-io/opentelemetry-ext-js) created by Aspecto | ||
(https://www.aspecto.io/). |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
231 changes: 231 additions & 0 deletions
231
src/instrumentations/external/elasticsearch/elasticsearch.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
/* | ||
* Copyright Splunk Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
import { diag, context, trace, Span } from '@opentelemetry/api'; | ||
import { suppressTracing } from '@opentelemetry/core'; | ||
import type * as elasticsearch from '@elastic/elasticsearch'; | ||
import { ElasticsearchInstrumentationConfig } from './types'; | ||
import { | ||
InstrumentationBase, | ||
InstrumentationModuleDefinition, | ||
InstrumentationNodeModuleDefinition, | ||
InstrumentationNodeModuleFile, | ||
} from '@opentelemetry/instrumentation'; | ||
import { VERSION } from '../../../version'; | ||
import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; | ||
import { | ||
startSpan, | ||
onError, | ||
onResponse, | ||
defaultDbStatementSerializer, | ||
normalizeArguments, | ||
getIndexName, | ||
} from './utils'; | ||
import { ELASTICSEARCH_API_FILES } from './helpers'; | ||
|
||
enum AttributeNames { | ||
ELASTICSEARCH_INDICES = 'elasticsearch.request.indices', | ||
} | ||
|
||
export class ElasticsearchInstrumentation extends InstrumentationBase< | ||
typeof elasticsearch | ||
> { | ||
static readonly component = '@elastic/elasticsearch'; | ||
|
||
protected override _config: ElasticsearchInstrumentationConfig = {}; | ||
private _isEnabled = false; | ||
private moduleVersion?: string; | ||
|
||
constructor(config: ElasticsearchInstrumentationConfig = {}) { | ||
super( | ||
'splunk-opentelemetry-instrumentation-elasticsearch', | ||
VERSION, | ||
Object.assign({}, config) | ||
); | ||
} | ||
|
||
override setConfig(config: ElasticsearchInstrumentationConfig = {}) { | ||
this._config = Object.assign({}, config); | ||
} | ||
|
||
protected init(): InstrumentationModuleDefinition<typeof elasticsearch> { | ||
const apiModuleFiles = ELASTICSEARCH_API_FILES.map( | ||
({ path, operationClassName }) => | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
new InstrumentationNodeModuleFile<any>( | ||
`@elastic/elasticsearch/api/${path}`, | ||
['>=5 <8'], | ||
(moduleExports, moduleVersion) => { | ||
diag.debug( | ||
`elasticsearch instrumentation: patch elasticsearch ${operationClassName}.` | ||
); | ||
this.moduleVersion = moduleVersion; | ||
this._isEnabled = true; | ||
|
||
const modulePrototypeKeys = Object.keys(moduleExports.prototype); | ||
if (modulePrototypeKeys.length > 0) { | ||
modulePrototypeKeys.forEach((functionName) => { | ||
this._wrap( | ||
moduleExports.prototype, | ||
functionName, | ||
this.wrappedApiRequest(operationClassName, functionName) | ||
); | ||
}); | ||
return moduleExports; | ||
} | ||
|
||
// For versions <= 7.9.0 | ||
const instrumentation = this; | ||
return function (opts: unknown) { | ||
const module = moduleExports(opts); | ||
instrumentation.patchObject(operationClassName, module); | ||
return module; | ||
}; | ||
}, | ||
(moduleExports) => { | ||
diag.debug(`elasticsearch instrumentation: unpatch elasticsearch.`); | ||
this._isEnabled = false; | ||
|
||
const modulePrototypeKeys = Object.keys(moduleExports.prototype); | ||
if (modulePrototypeKeys.length > 0) { | ||
modulePrototypeKeys.forEach((functionName) => { | ||
this._unwrap(moduleExports.prototype, functionName); | ||
}); | ||
} else { | ||
// Unable to unwrap function for versions <= 7.9.0. Using _isEnabled flag instead. | ||
} | ||
} | ||
) | ||
); | ||
|
||
const module = new InstrumentationNodeModuleDefinition< | ||
typeof elasticsearch | ||
>( | ||
ElasticsearchInstrumentation.component, | ||
['*'], | ||
undefined, | ||
undefined, | ||
apiModuleFiles | ||
); | ||
|
||
return module; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
private patchObject(operationClassName: string, object: any) { | ||
Object.keys(object).forEach((functionName) => { | ||
if (typeof object[functionName] === 'object') { | ||
this.patchObject(functionName, object[functionName]); | ||
} else { | ||
this._wrap( | ||
object, | ||
functionName, | ||
this.wrappedApiRequest(operationClassName, functionName) | ||
); | ||
} | ||
}); | ||
} | ||
|
||
private wrappedApiRequest(apiClassName: string, functionName: string) { | ||
return (original: Function) => { | ||
const instrumentation = this; | ||
return function (this: unknown, ...args: unknown[]) { | ||
if (!instrumentation._isEnabled) { | ||
return original.apply(this, args); | ||
} | ||
|
||
const [params, options, originalCallback] = normalizeArguments( | ||
args[0], | ||
args[1], | ||
args[2] | ||
); | ||
const operation = `${apiClassName}.${functionName}`; | ||
const span = startSpan({ | ||
tracer: instrumentation.tracer, | ||
attributes: { | ||
[SemanticAttributes.DB_OPERATION]: operation, | ||
[AttributeNames.ELASTICSEARCH_INDICES]: getIndexName(params), | ||
[SemanticAttributes.DB_STATEMENT]: ( | ||
instrumentation._config.dbStatementSerializer || | ||
defaultDbStatementSerializer | ||
)(operation, params, options), | ||
}, | ||
}); | ||
instrumentation._addModuleVersionIfNeeded(span); | ||
|
||
if (originalCallback) { | ||
const wrappedCallback = function ( | ||
this: unknown, | ||
err: Error, | ||
result: elasticsearch.ApiResponse | ||
) { | ||
if (err) { | ||
onError(span, err); | ||
} else { | ||
onResponse(span, result, instrumentation._config.responseHook); | ||
} | ||
|
||
return originalCallback.call(this, err, result); | ||
}; | ||
|
||
return instrumentation._callOriginalFunction(span, () => | ||
original.call(this, params, options, wrappedCallback) | ||
); | ||
} else { | ||
const promise = instrumentation._callOriginalFunction(span, () => | ||
original.apply(this, args) | ||
); | ||
promise.then( | ||
(result: elasticsearch.ApiResponse) => { | ||
onResponse(span, result, instrumentation._config.responseHook); | ||
return result; | ||
}, | ||
(err: Error) => { | ||
onError(span, err); | ||
return err; | ||
} | ||
); | ||
|
||
return promise; | ||
} | ||
}; | ||
}; | ||
} | ||
|
||
private _callOriginalFunction<T>( | ||
span: Span, | ||
originalFunction: (...args: unknown[]) => T | ||
): T { | ||
if (this._config?.suppressInternalInstrumentation) { | ||
return context.with(suppressTracing(context.active()), originalFunction); | ||
} else { | ||
const activeContextWithSpan = trace.setSpan(context.active(), span); | ||
return context.with(activeContextWithSpan, originalFunction); | ||
} | ||
} | ||
|
||
private _addModuleVersionIfNeeded(span: Span) { | ||
if (this.moduleVersion === undefined) { | ||
return; | ||
} | ||
|
||
if (this._config.moduleVersionAttributeName) { | ||
span.setAttribute( | ||
this._config.moduleVersionAttributeName, | ||
this.moduleVersion | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright Splunk Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
export const ELASTICSEARCH_API_FILES = [ | ||
{ path: 'index.js', operationClassName: 'client' }, | ||
{ path: 'api/async_search.js', operationClassName: 'asyncSearch' }, | ||
{ path: 'api/autoscaling.js', operationClassName: 'autoscaling' }, | ||
{ path: 'api/cat.js', operationClassName: 'cat' }, | ||
{ path: 'api/ccr.js', operationClassName: 'ccr' }, | ||
{ path: 'api/cluster.js', operationClassName: 'cluster' }, | ||
{ path: 'api/dangling_indices.js', operationClassName: 'dangling_indices' }, | ||
{ path: 'api/enrich.js', operationClassName: 'enrich' }, | ||
{ path: 'api/eql.js', operationClassName: 'eql' }, | ||
{ path: 'api/graph.js', operationClassName: 'graph' }, | ||
{ path: 'api/ilm.js', operationClassName: 'ilm' }, | ||
{ path: 'api/indices.js', operationClassName: 'indices' }, | ||
{ path: 'api/ingest.js', operationClassName: 'ingest' }, | ||
{ path: 'api/license.js', operationClassName: 'license' }, | ||
{ path: 'api/logstash.js', operationClassName: 'logstash' }, | ||
{ path: 'api/migration.js', operationClassName: 'migration' }, | ||
{ path: 'api/ml.js', operationClassName: 'ml' }, | ||
{ path: 'api/monitoring.js', operationClassName: 'monitoring' }, | ||
{ path: 'api/nodes.js', operationClassName: 'nodes' }, | ||
{ path: 'api/rollup.js', operationClassName: 'rollup' }, | ||
{ | ||
path: 'api/searchable_snapshots.js', | ||
operationClassName: 'searchable_snapshots', | ||
}, | ||
{ path: 'api/security.js', operationClassName: 'security' }, | ||
{ path: 'api/slm.js', operationClassName: 'slm' }, | ||
{ path: 'api/snapshot.js', operationClassName: 'snapshot' }, | ||
{ path: 'api/sql.js', operationClassName: 'sql' }, | ||
{ path: 'api/ssl.js', operationClassName: 'ssl' }, | ||
{ path: 'api/tasks.js', operationClassName: 'tasks' }, | ||
{ path: 'api/text_structure.js', operationClassName: 'text_structure' }, | ||
{ path: 'api/transform.js', operationClassName: 'transform' }, | ||
{ path: 'api/watcher.js', operationClassName: 'watcher' }, | ||
{ path: 'api/xpack.js', operationClassName: 'xpack' }, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright Splunk Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
export * from './elasticsearch'; | ||
export * from './types'; |
Oops, something went wrong.