Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oracledb): Add support for Oracle DB instrumentation #2612

Open
wants to merge 45 commits into
base: main
Choose a base branch
from

Conversation

sudarshan12s
Copy link

@sudarshan12s sudarshan12s commented Dec 20, 2024

Add Oracledb instrumentation

Which problem is this PR solving?

Adding instrumentation support for node-oracledb applications connecting to Oracle database.

Short description of the changes

From node-oracledb version 6.7 onwards, oracledb module exports a class,
TraceHandlerBase which simulates interface kind of functionality with abstract methods to be overwritten by the derived class.

The current OT module derives this class TraceHandlerBase and implements abstract methods; It uses setTraceInstance method of oracledb module to register this new derived class OracleTelemetryTraceHandler
These methods are invoked by oracledb module providing the traceContext which is used to dump the data in to the span after converting to standard span attributes.

Following are the abstract methods implemented:

  • onEnterFn -> It gets invoked by oracledb module when application invokes getConnection to create a connection to
    database or execute method to run a sql. It is invoked before invoking the public API calls made by application.
  • onExitFn -> It gets invoked after the public API call is completed with success/failure.
  • onBeginRoundTrip -> It gets invoked before round trip to database which was triggered as part of public API.
  • onEndRoundTrip -> It gets invoked after round trip to database completes which was triggered as part of public API..

Add Oracledb instrumentation
@sudarshan12s sudarshan12s requested a review from a team as a code owner December 20, 2024 17:52
Copy link

linux-foundation-easycla bot commented Dec 20, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

Copy link
Member

@blumamir blumamir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did another round of review and added few more comments.

Currently marking as "RequestChanges" due to the following 2 issues:

  1. the copyright to oracle should be addressed (not sure in what way) @open-telemetry/javascript-maintainers
  2. the instrumentation must not import from the instrumented package.

Thanks again


// Register the instance with oracledb.
newmoduleExports.traceHandler.setTraceInstance(obj);
this._tmHandler = obj;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the patch function can be called more than once per instrumentation instance.
It will happen if the instrumented package is installed multiple times in the node_modules (for example - with 2 different versions).

While it's not very likely for oracle db driver, it's an anti pattern for instrumentation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using non null check to see if its patched already, then unregister and register again. Please add if i missed anything.

if (this._tmHandler) {

'oracledb',
['>= 6.7 < 7'],
(moduleExports: typeof oracleDBTypes) => {
const newmoduleExports: any = moduleExports;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's not actually new, right?
the changes we apply here will modify the moduleExports itself.

I think all other instrumentation are updating the moduleExports var directly so it can be nice to align for consistency and to reflect that the original object is modified and not a copy.

same for the unpatch

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes removed newmoduleExports variable.

ATTR_DB_NAMESPACE,
ATTR_DB_OPERATION_NAME,
} from './semconv';
import * as oracledbTypes from 'oracledb';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when this instrumentation is added to the auto-instrumentation package, or any other otel distribution, this line will ALWAYS run.

If the user has the oracledb package in the node_modules, with an up-to-date version, things will work fine. When the instrumented package is not install, this line will crash the app as the import will fail.

read more here

Why do you need to extend a class and not implement an interface? unfortunately this cannot merge as is

if (this._instrumentConfig.enhancedDatabaseReporting && !roundTrip) {
const values = this._getValues(callConfig.values);
if (values) {
span.setAttribute(AttributeNames.ORACLE_BIND_VALUES, values);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

semantic conventions defined an attribute per value:
https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/db.md#db-operation-parameter

should we align this instrumentation?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try. But since it was more for debugging and its a sensitive information, I think its less priority.

enhancedDatabaseReporting?: boolean;

/**
* If true, db.statement will have sql in the spans generated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here - the query will be recorded and can potentially contain sensitive unparameterized data. can be nice to add a note here to let users know this option might be dangerous.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the following:


If true, db.statement will have sql which could potentially contain
    sensitive unparameterized data in the spans generated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is named utils but it's not hosting util functions.
consider renaming it to OracleTelemetryTraceHandler.ts

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to OracleTelemetryTraceHandler.ts


export interface OracleRequestHookInformation {
inputArgs: any;
connection: SpanConnectionConfig | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when will this be undefined?
or is it only for safety?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed undefined.

@blumamir
Copy link
Member

For becoming members of OTel org, @marcalff has agreed for first sponsor. Can someone from javascript-maintainers be the second sponsor.

Once this PR is finalized and ready to merge, I can be your second sponsor for the organization membership

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to OracleTelemetryTraceHandler.ts

enhancedDatabaseReporting?: boolean;

/**
* If true, db.statement will have sql in the spans generated.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the following:


If true, db.statement will have sql which could potentially contain
    sensitive unparameterized data in the spans generated.

'oracledb',
['>= 6.7 < 7'],
(moduleExports: typeof oracleDBTypes) => {
const newmoduleExports: any = moduleExports;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes removed newmoduleExports variable.


// Register the instance with oracledb.
newmoduleExports.traceHandler.setTraceInstance(obj);
this._tmHandler = obj;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using non null check to see if its patched already, then unregister and register again. Please add if i missed anything.

if (this._tmHandler) {


export interface OracleRequestHookInformation {
inputArgs: any;
connection: SpanConnectionConfig | undefined;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed undefined.

import type { traceHandler } from 'oracledb'; // Import only for type checking

// It returns the TraceHandlerBase class, if oracledb module is available.
function getTraceHandlerBaseClass(): TraceHandlerBaseConstructor | null {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the issue with requiring oracledb to be present in node_modules with this line:
import * as oracledbTypes from 'oracledb';

I made changes to import types instead:
import type { traceHandler } from 'oracledb';

Now since oracledb module exposes TraceHandlerBase as abstract class where some default implementations (enable) are available, I can not do implements TraceHandlerBase.
Added a function, getTraceHandlerBaseClass which gives the constructor for TraceHandlerBase dynamically during patch avoiding global require("oracledb") in generated js files.

The types added inside this file will be removed once this PR and release including this change is done.

Please confirm if i am missed anything or any suggestions.

Thanks for pointing it out with details.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the instance of oracledb that's being required by the instrumentation package here might differ from the one that is being patched. Given you have access to the exports from the patch method it would be a better idea to pass them here as a parameter and get the base class from the instance that's being instrumented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. I made the changes accordingly.

@sudarshan12s
Copy link
Author

Did another round of review and added few more comments.

Currently marking as "RequestChanges" due to the following 2 issues:

  1. the copyright to oracle should be addressed (not sure in what way) @open-telemetry/javascript-maintainers
  2. the instrumentation must not import from the instrumented package.

Thanks again

I have addressed the second point by including types , Please see.

@sudarshan12s
Copy link
Author

Did another round of review and added few more comments.
Currently marking as "RequestChanges" due to the following 2 issues:

  1. the copyright to oracle should be addressed (not sure in what way) @open-telemetry/javascript-maintainers
  2. the instrumentation must not import from the instrumented package.

Thanks again

I have addressed the second point by including types , Please see.

I have removed the copyrights from src/. It is only added in component_owners.yml

@sudarshan12s sudarshan12s requested a review from blumamir March 5, 2025 15:11
@dyladan
Copy link
Member

dyladan commented Mar 5, 2025

https://github.com/cncf/foundation/blob/main/copyright-notices.md#what-if-i-want-my-copyright-notice-included

Copy link
Member

@blumamir blumamir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

adding the relevant part from OpenTelemetry community regarding the copyright notice for future reference:

https://github.com/cncf/foundation/blob/main/copyright-notices.md#what-if-i-want-my-copyright-notice-included

obj: typeof oracleDBTypes
): any {
const traceHandlerBase = getTraceHandlerBaseClass(obj);
if (traceHandlerBase) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of creating an if with >300 lines of code, we can revert the check and exist fast:

Suggested change
if (traceHandlerBase) {
if (!traceHandlerBase) {
return undefined;
}
// else - all the code below, not nested and no indentation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment