Skip to content

Commit

Permalink
Merge branch 'main' into dns-fix-hostname-attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
svrnm authored Jun 1, 2021
2 parents 0203d9d + 8453034 commit bc7361d
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@
"@opentelemetry/tracing": "0.19.0",
"@types/mocha": "7.0.2",
"@types/node": "14.0.27",
"@types/semver": "^7.3.6",
"@types/semver": "7.3.6",
"codecov": "3.7.2",
"generic-pool": "^3.7.8",
"generic-pool": "3.7.8",
"gts": "3.1.0",
"mocha": "7.2.0",
"nyc": "15.1.0",
"rimraf": "3.0.2",
"semver": "^7.3.5",
"semver": "7.3.5",
"ts-mocha": "8.0.0",
"typescript": "4.1.3"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import {
import { RedisInstrumentationConfig } from './types';
import { VERSION } from './version';

const DEFAULT_CONFIG: RedisInstrumentationConfig = {
requireParentSpan: false,
};

export class RedisInstrumentation extends InstrumentationBase<
typeof redisTypes
> {
Expand All @@ -38,6 +42,10 @@ export class RedisInstrumentation extends InstrumentationBase<
super('@opentelemetry/instrumentation-redis', VERSION, _config);
}

setConfig(config: RedisInstrumentationConfig = {}) {
this._config = Object.assign({}, DEFAULT_CONFIG, config);
}

protected init() {
return [
new InstrumentationNodeModuleDefinition<typeof redisTypes>(
Expand Down Expand Up @@ -100,8 +108,9 @@ export class RedisInstrumentation extends InstrumentationBase<
*/
private _getPatchInternalSendCommand() {
const tracer = this.tracer;
const config = this._config;
return function internal_send_command(original: Function) {
return getTracedInternalSendCommand(tracer, original);
return getTracedInternalSendCommand(tracer, original, config);
};
}

Expand Down
42 changes: 41 additions & 1 deletion plugins/node/opentelemetry-instrumentation-redis/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { Span } from '@opentelemetry/api';
import { InstrumentationConfig } from '@opentelemetry/instrumentation';
import type * as redisTypes from 'redis';

Expand All @@ -36,4 +37,43 @@ export interface RedisPluginClientTypes {
address?: string;
}

export type RedisInstrumentationConfig = InstrumentationConfig;
/**
* Function that can be used to serialize db.statement tag
* @param cmdName - The name of the command (eg. set, get, mset)
* @param cmdArgs - Array of arguments passed to the command
*
* @returns serialized string that will be used as the db.statement attribute.
*/
export type DbStatementSerializer = (
cmdName: RedisCommand['command'],
cmdArgs: RedisCommand['args']
) => string;

/**
* Function that can be used to add custom attributes to span on response from redis server
* @param span - The span created for the redis command, on which attributes can be set
* @param cmdName - The name of the command (eg. set, get, mset)
* @param cmdArgs - Array of arguments passed to the command
* @param response - The response object which is returned to the user who called this command.
* Can be used to set custom attributes on the span.
* The type of the response varies depending on the specific command.
*/
export interface RedisResponseCustomAttributeFunction {
(
span: Span,
cmdName: RedisCommand['command'],
cmdArgs: RedisCommand['args'],
response: unknown
): void;
}

export interface RedisInstrumentationConfig extends InstrumentationConfig {
/** Custom serializer function for the db.statement tag */
dbStatementSerializer?: DbStatementSerializer;

/** Function for adding custom attributes on db response */
responseHook?: RedisResponseCustomAttributeFunction;

/** Require parent to create redis span, default when unset is false */
requireParentSpan?: boolean;
}
124 changes: 79 additions & 45 deletions plugins/node/opentelemetry-instrumentation-redis/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@ import {
SpanKind,
Span,
SpanStatusCode,
getSpan,
diag,
} from '@opentelemetry/api';
import { RedisCommand, RedisPluginClientTypes } from './types';
import {
DbStatementSerializer,
RedisCommand,
RedisInstrumentationConfig,
RedisPluginClientTypes,
} from './types';
import { EventEmitter } from 'events';
import { RedisInstrumentation } from './';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { safeExecuteInTheMiddle } from '@opentelemetry/instrumentation';

const endSpan = (span: Span, err?: Error | null) => {
if (err) {
Expand Down Expand Up @@ -64,63 +72,89 @@ export const getTracedCreateStreamTrace = (
};
};

const defaultDbStatementSerializer: DbStatementSerializer = cmdName => cmdName;

export const getTracedInternalSendCommand = (
tracer: Tracer,
original: Function
original: Function,
config?: RedisInstrumentationConfig
) => {
return function internal_send_command_trace(
this: RedisPluginClientTypes,
cmd?: RedisCommand
) {
// New versions of redis (2.4+) use a single options object
// instead of named arguments
if (arguments.length === 1 && typeof cmd === 'object') {
const span = tracer.startSpan(
`${RedisInstrumentation.COMPONENT}-${cmd.command}`,
{
kind: SpanKind.CLIENT,
attributes: {
[SemanticAttributes.DB_SYSTEM]: RedisInstrumentation.COMPONENT,
[SemanticAttributes.DB_STATEMENT]: cmd.command,
},
}
);
if (arguments.length !== 1 || typeof cmd !== 'object') {
// We don't know how to trace this call, so don't start/stop a span
return original.apply(this, arguments);
}

// Set attributes for not explicitly typed RedisPluginClientTypes
if (this.options) {
span.setAttributes({
[SemanticAttributes.NET_PEER_NAME]: this.options.host,
[SemanticAttributes.NET_PEER_PORT]: this.options.port,
});
}
if (this.address) {
span.setAttribute(
SemanticAttributes.NET_PEER_IP,
`redis://${this.address}`
);
}
const hasNoParentSpan = getSpan(context.active()) === undefined;
if (config?.requireParentSpan === true && hasNoParentSpan) {
return original.apply(this, arguments);
}

const originalCallback = arguments[0].callback;
if (originalCallback) {
(arguments[0] as RedisCommand).callback = function callback<T>(
this: unknown,
err: Error | null,
_reply: T
) {
endSpan(span, err);
return originalCallback.apply(this, arguments);
};
}
try {
// Span will be ended in callback
return original.apply(this, arguments);
} catch (rethrow) {
endSpan(span, rethrow);
throw rethrow; // rethrow after ending span
const dbStatementSerializer =
config?.dbStatementSerializer || defaultDbStatementSerializer;
const span = tracer.startSpan(
`${RedisInstrumentation.COMPONENT}-${cmd.command}`,
{
kind: SpanKind.CLIENT,
attributes: {
[SemanticAttributes.DB_SYSTEM]: RedisInstrumentation.COMPONENT,
[SemanticAttributes.DB_STATEMENT]: dbStatementSerializer(
cmd.command,
cmd.args
),
},
}
);

// Set attributes for not explicitly typed RedisPluginClientTypes
if (this.options) {
span.setAttributes({
[SemanticAttributes.NET_PEER_NAME]: this.options.host,
[SemanticAttributes.NET_PEER_PORT]: this.options.port,
});
}
if (this.address) {
span.setAttribute(
SemanticAttributes.NET_PEER_IP,
`redis://${this.address}`
);
}

// We don't know how to trace this call, so don't start/stop a span
return original.apply(this, arguments);
const originalCallback = arguments[0].callback;
if (originalCallback) {
(arguments[0] as RedisCommand).callback = function callback<T>(
this: unknown,
err: Error | null,
reply: T
) {
if (config?.responseHook) {
const responseHook = config.responseHook;
safeExecuteInTheMiddle(
() => {
responseHook(span, cmd.command, cmd.args, reply);
},
err => {
diag.error('Error executing responseHook', err);
},
true
);
}

endSpan(span, err);
return originalCallback.apply(this, arguments);
};
}
try {
// Span will be ended in callback
return original.apply(this, arguments);
} catch (rethrow) {
endSpan(span, rethrow);
throw rethrow; // rethrow after ending span
}
};
};
Loading

0 comments on commit bc7361d

Please sign in to comment.