@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
237 lines (207 loc) • 7.09 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const api = require('@opentelemetry/api');
const instrumentation = require('@opentelemetry/instrumentation');
const core = require('@sentry/core');
const constants = require('./constants.js');
const PACKAGE_NAME = '@sentry/instrumentation-hono';
const PACKAGE_VERSION = '0.0.1';
/**
* Hono instrumentation for OpenTelemetry
*/
class HonoInstrumentation extends instrumentation.InstrumentationBase {
constructor(config = {}) {
super(PACKAGE_NAME, PACKAGE_VERSION, config);
}
/**
* Initialize the instrumentation.
*/
init() {
return [
new instrumentation.InstrumentationNodeModuleDefinition('hono', ['>=4.0.0 <5'], moduleExports => this._patch(moduleExports)),
];
}
/**
* Patches the module exports to instrument Hono.
*/
_patch(moduleExports) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instrumentation = this;
class WrappedHono extends moduleExports.Hono {
constructor(...args) {
super(...args);
instrumentation._wrap(this, 'get', instrumentation._patchHandler());
instrumentation._wrap(this, 'post', instrumentation._patchHandler());
instrumentation._wrap(this, 'put', instrumentation._patchHandler());
instrumentation._wrap(this, 'delete', instrumentation._patchHandler());
instrumentation._wrap(this, 'options', instrumentation._patchHandler());
instrumentation._wrap(this, 'patch', instrumentation._patchHandler());
instrumentation._wrap(this, 'all', instrumentation._patchHandler());
instrumentation._wrap(this, 'on', instrumentation._patchOnHandler());
instrumentation._wrap(this, 'use', instrumentation._patchMiddlewareHandler());
}
}
try {
moduleExports.Hono = WrappedHono;
} catch {
// This is a workaround for environments where direct assignment is not allowed.
return { ...moduleExports, Hono: WrappedHono };
}
return moduleExports;
}
/**
* Patches the route handler to instrument it.
*/
_patchHandler() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instrumentation = this;
return function (original) {
return function wrappedHandler( ...args) {
if (typeof args[0] === 'string') {
const path = args[0];
if (args.length === 1) {
return original.apply(this, [path]);
}
const handlers = args.slice(1);
return original.apply(this, [
path,
...handlers.map(handler => instrumentation._wrapHandler(handler )),
]);
}
return original.apply(
this,
args.map(handler => instrumentation._wrapHandler(handler )),
);
};
};
}
/**
* Patches the 'on' handler to instrument it.
*/
_patchOnHandler() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instrumentation = this;
return function (original) {
return function wrappedHandler( ...args) {
const handlers = args.slice(2);
return original.apply(this, [
...args.slice(0, 2),
...handlers.map(handler => instrumentation._wrapHandler(handler )),
]);
};
};
}
/**
* Patches the middleware handler to instrument it.
*/
_patchMiddlewareHandler() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instrumentation = this;
return function (original) {
return function wrappedHandler( ...args) {
if (typeof args[0] === 'string') {
const path = args[0];
if (args.length === 1) {
return original.apply(this, [path]);
}
const handlers = args.slice(1);
return original.apply(this, [
path,
...handlers.map(handler => instrumentation._wrapHandler(handler )),
]);
}
return original.apply(
this,
args.map(handler => instrumentation._wrapHandler(handler )),
);
};
};
}
/**
* Wraps a handler or middleware handler to apply instrumentation.
*/
_wrapHandler(handler) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instrumentation = this;
return function ( c, next) {
if (!instrumentation.isEnabled()) {
return handler.apply(this, [c, next]);
}
const path = c.req.path;
const span = instrumentation.tracer.startSpan(path);
return api.context.with(api.trace.setSpan(api.context.active(), span), () => {
return instrumentation._safeExecute(
() => {
const result = handler.apply(this, [c, next]);
if (core.isThenable(result)) {
return result.then(result => {
const type = instrumentation._determineHandlerType(result);
span.setAttributes({
[constants.AttributeNames.HONO_TYPE]: type,
[constants.AttributeNames.HONO_NAME]: type === constants.HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous',
});
instrumentation.getConfig().responseHook?.(span);
return result;
});
} else {
const type = instrumentation._determineHandlerType(result);
span.setAttributes({
[constants.AttributeNames.HONO_TYPE]: type,
[constants.AttributeNames.HONO_NAME]: type === constants.HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous',
});
instrumentation.getConfig().responseHook?.(span);
return result;
}
},
() => span.end(),
error => {
instrumentation._handleError(span, error);
span.end();
},
);
});
};
}
/**
* Safely executes a function and handles errors.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_safeExecute(execute, onSuccess, onFailure) {
try {
const result = execute();
if (core.isThenable(result)) {
result.then(
() => onSuccess(),
(error) => onFailure(error),
);
} else {
onSuccess();
}
return result;
} catch (error) {
onFailure(error);
throw error;
}
}
/**
* Determines the handler type based on the result.
* @param result
* @private
*/
_determineHandlerType(result) {
return result === undefined ? constants.HonoTypes.MIDDLEWARE : constants.HonoTypes.REQUEST_HANDLER;
}
/**
* Handles errors by setting the span status and recording the exception.
*/
_handleError(span, error) {
if (error instanceof Error) {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
}
}
}
exports.HonoInstrumentation = HonoInstrumentation;
//# sourceMappingURL=instrumentation.js.map