Skip to content

Commit

Permalink
fix: Add support for setConfig in ioredis instrumentation (#689)
Browse files Browse the repository at this point in the history
* fix: add support for setConfig in ioredis instrumentation

* fix: use latest config in traceSendCommand

* fix: lint errors fixed

* fix: remove override setConfig

* refactor: ioredis instrumentation refactored
moved traceConnection to instrumentation.ts
refactored tests to use setConfig instead of new instrumentation
  • Loading branch information
mustafain117 authored Oct 15, 2021
1 parent fe85fca commit 314c890
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
* limitations under the License.
*/

import { diag } from '@opentelemetry/api';
import { diag, trace, context, SpanKind } from '@opentelemetry/api';
import type * as ioredisTypes from 'ioredis';
import {
InstrumentationBase,
InstrumentationNodeModuleDefinition,
isWrapped,
} from '@opentelemetry/instrumentation';
import { IORedisInstrumentationConfig } from './types';
import { traceConnection, traceSendCommand } from './utils';
import { IORedisInstrumentationConfig, IORedisCommand } from './types';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { safeExecuteInTheMiddle } from '@opentelemetry/instrumentation';
import { endSpan, defaultDbStatementSerializer } from './utils';
import { VERSION } from './version';

const DEFAULT_CONFIG: IORedisInstrumentationConfig = {
Expand Down Expand Up @@ -82,18 +84,127 @@ export class IORedisInstrumentation extends InstrumentationBase<
*/
private _patchSendCommand(moduleVersion?: string) {
return (original: Function) => {
return traceSendCommand(
this.tracer,
original,
this._config,
moduleVersion
);
return this.traceSendCommand(original, moduleVersion);
};
}

private _patchConnection() {
return (original: Function) => {
return traceConnection(this.tracer, original);
return this.traceConnection(original);
};
}

private traceSendCommand = (original: Function, moduleVersion?: string) => {
const instrumentation = this;
return function (this: ioredisTypes.Redis, cmd?: IORedisCommand) {
if (arguments.length < 1 || typeof cmd !== 'object') {
return original.apply(this, arguments);
}
const config =
instrumentation.getConfig() as IORedisInstrumentationConfig;
const dbStatementSerializer =
config?.dbStatementSerializer || defaultDbStatementSerializer;

const hasNoParentSpan = trace.getSpan(context.active()) === undefined;
if (config?.requireParentSpan === true && hasNoParentSpan) {
return original.apply(this, arguments);
}

const span = instrumentation.tracer.startSpan(cmd.name, {
kind: SpanKind.CLIENT,
attributes: {
[SemanticAttributes.DB_SYSTEM]: IORedisInstrumentation.DB_SYSTEM,
[SemanticAttributes.DB_STATEMENT]: dbStatementSerializer(
cmd.name,
cmd.args
),
},
});

if (config?.requestHook) {
safeExecuteInTheMiddle(
() =>
config?.requestHook!(span, {
moduleVersion,
cmdName: cmd.name,
cmdArgs: cmd.args,
}),
e => {
if (e) {
diag.error('ioredis instrumentation: request hook failed', e);
}
},
true
);
}

const { host, port } = this.options;

span.setAttributes({
[SemanticAttributes.NET_PEER_NAME]: host,
[SemanticAttributes.NET_PEER_PORT]: port,
[SemanticAttributes.NET_PEER_IP]: `redis://${host}:${port}`,
});

try {
const result = original.apply(this, arguments);

const origResolve = cmd.resolve;
/* eslint-disable @typescript-eslint/no-explicit-any */
cmd.resolve = function (result: any) {
safeExecuteInTheMiddle(
() => config?.responseHook?.(span, cmd.name, cmd.args, result),
e => {
if (e) {
diag.error('ioredis instrumentation: response hook failed', e);
}
},
true
);

endSpan(span, null);
origResolve(result);
};

const origReject = cmd.reject;
cmd.reject = function (err: Error) {
endSpan(span, err);
origReject(err);
};

return result;
} catch (error) {
endSpan(span, error);
throw error;
}
};
};

private traceConnection = (original: Function) => {
const instrumentation = this;
return function (this: ioredisTypes.Redis) {
const span = instrumentation.tracer.startSpan('connect', {
kind: SpanKind.CLIENT,
attributes: {
[SemanticAttributes.DB_SYSTEM]: IORedisInstrumentation.DB_SYSTEM,
[SemanticAttributes.DB_STATEMENT]: 'connect',
},
});
const { host, port } = this.options;

span.setAttributes({
[SemanticAttributes.NET_PEER_NAME]: host,
[SemanticAttributes.NET_PEER_PORT]: port,
[SemanticAttributes.NET_PEER_IP]: `redis://${host}:${port}`,
});
try {
const client = original.apply(this, arguments);
endSpan(span, null);
return client;
} catch (error) {
endSpan(span, error);
throw error;
}
};
};
}
142 changes: 7 additions & 135 deletions plugins/node/opentelemetry-instrumentation-ioredis/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,13 @@
* limitations under the License.
*/

import type * as ioredisTypes from 'ioredis';
import {
Tracer,
SpanKind,
Span,
SpanStatusCode,
trace,
context,
diag,
} from '@opentelemetry/api';
import {
IORedisCommand,
IORedisInstrumentationConfig,
DbStatementSerializer,
} from './types';
import { IORedisInstrumentation } from './';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { safeExecuteInTheMiddle } from '@opentelemetry/instrumentation';
import { Span, SpanStatusCode } from '@opentelemetry/api';
import { DbStatementSerializer } from './types';

const endSpan = (span: Span, err: NodeJS.ErrnoException | null | undefined) => {
export const endSpan = (
span: Span,
err: NodeJS.ErrnoException | null | undefined
) => {
if (err) {
span.recordException(err);
span.setStatus({
Expand All @@ -44,125 +31,10 @@ const endSpan = (span: Span, err: NodeJS.ErrnoException | null | undefined) => {
span.end();
};

export const traceConnection = (tracer: Tracer, original: Function) => {
return function (this: ioredisTypes.Redis) {
const span = tracer.startSpan('connect', {
kind: SpanKind.CLIENT,
attributes: {
[SemanticAttributes.DB_SYSTEM]: IORedisInstrumentation.DB_SYSTEM,
[SemanticAttributes.DB_STATEMENT]: 'connect',
},
});
const { host, port } = this.options;

span.setAttributes({
[SemanticAttributes.NET_PEER_NAME]: host,
[SemanticAttributes.NET_PEER_PORT]: port,
[SemanticAttributes.NET_PEER_IP]: `redis://${host}:${port}`,
});
try {
const client = original.apply(this, arguments);
endSpan(span, null);
return client;
} catch (error) {
endSpan(span, error);
throw error;
}
};
};

const defaultDbStatementSerializer: DbStatementSerializer = (
export const defaultDbStatementSerializer: DbStatementSerializer = (
cmdName,
cmdArgs
) =>
Array.isArray(cmdArgs) && cmdArgs.length
? `${cmdName} ${cmdArgs.join(' ')}`
: cmdName;

export const traceSendCommand = (
tracer: Tracer,
original: Function,
config?: IORedisInstrumentationConfig,
moduleVersion?: string
) => {
const dbStatementSerializer =
config?.dbStatementSerializer || defaultDbStatementSerializer;
return function (this: ioredisTypes.Redis, cmd?: IORedisCommand) {
if (arguments.length < 1 || typeof cmd !== 'object') {
return original.apply(this, arguments);
}

const hasNoParentSpan = trace.getSpan(context.active()) === undefined;
if (config?.requireParentSpan === true && hasNoParentSpan) {
return original.apply(this, arguments);
}

const span = tracer.startSpan(cmd.name, {
kind: SpanKind.CLIENT,
attributes: {
[SemanticAttributes.DB_SYSTEM]: IORedisInstrumentation.DB_SYSTEM,
[SemanticAttributes.DB_STATEMENT]: dbStatementSerializer(
cmd.name,
cmd.args
),
},
});

if (config?.requestHook) {
safeExecuteInTheMiddle(
() =>
config?.requestHook!(span, {
moduleVersion,
cmdName: cmd.name,
cmdArgs: cmd.args,
}),
e => {
if (e) {
diag.error('ioredis instrumentation: request hook failed', e);
}
},
true
);
}

const { host, port } = this.options;

span.setAttributes({
[SemanticAttributes.NET_PEER_NAME]: host,
[SemanticAttributes.NET_PEER_PORT]: port,
[SemanticAttributes.NET_PEER_IP]: `redis://${host}:${port}`,
});

try {
const result = original.apply(this, arguments);

const origResolve = cmd.resolve;
/* eslint-disable @typescript-eslint/no-explicit-any */
cmd.resolve = function (result: any) {
safeExecuteInTheMiddle(
() => config?.responseHook?.(span, cmd.name, cmd.args, result),
e => {
if (e) {
diag.error('ioredis instrumentation: response hook failed', e);
}
},
true
);

endSpan(span, null);
origResolve(result);
};

const origReject = cmd.reject;
cmd.reject = function (err: Error) {
endSpan(span, err);
origReject(err);
};

return result;
} catch (error) {
endSpan(span, error);
throw error;
}
};
};
Loading

0 comments on commit 314c890

Please sign in to comment.