@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
131 lines (128 loc) • 5.98 kB
JavaScript
import { MESSAGING_DESTINATION_NAME, MESSAGING_OPERATION_NAME, MESSAGING_MESSAGE_ID, MESSAGING_MESSAGE_RETRY_COUNT, MESSAGING_BATCH_MESSAGE_COUNT, MESSAGING_CLIENT_ID, MESSAGING_BULLMQ_JOB_STATE, MESSAGING_BULLMQ_FLOW_NAME, MESSAGING_BULLMQ_JOB_ATTEMPT_FINISHED_TIMESTAMP, MESSAGING_BULLMQ_JOB_BULK_NAMES, MESSAGING_BULLMQ_JOB_DEDUPLICATION_KEY, MESSAGING_BULLMQ_JOB_IDS, MESSAGING_BULLMQ_JOB_KEY, MESSAGING_BULLMQ_JOB_NAME, MESSAGING_BULLMQ_JOB_PROCESSED_TIMESTAMP, MESSAGING_BULLMQ_JOB_SCHEDULER_ID, MESSAGING_BULLMQ_JOB_TYPE, MESSAGING_BULLMQ_QUEUE_CLEAN_COUNT, MESSAGING_BULLMQ_QUEUE_CLEAN_LIMIT, MESSAGING_BULLMQ_QUEUE_DRAIN_DELAY, MESSAGING_BULLMQ_QUEUE_EVENT_MAX_LENGTH, MESSAGING_BULLMQ_QUEUE_GRACE, MESSAGING_BULLMQ_QUEUE_JOBS_STATE, MESSAGING_BULLMQ_QUEUE_RATE_LIMIT, MESSAGING_BULLMQ_WORKER_DO_NOT_WAIT_ACTIVE, MESSAGING_BULLMQ_WORKER_FORCE_CLOSE, MESSAGING_BULLMQ_WORKER_JOBS_TO_EXTEND_LOCKS, MESSAGING_BULLMQ_WORKER_NAME, MESSAGING_BULLMQ_WORKER_RATE_LIMIT, MESSAGING_BULLMQ_WORKER_STALLED_JOBS } from '@sentry/conventions/attributes';
import { SPAN_STATUS_ERROR, captureException } from '@sentry/core';
const DROPPED_ATTRIBUTES = /* @__PURE__ */ new Set([
// BullMQ sets these to `JSON.stringify()` output. They are left out until Sentry attributes support objects.
"bullmq.job.options",
"bullmq.job.progress",
"bullmq.queue.options",
"bullmq.worker.options",
// BullMQ deprecated this for `bullmq.job.attempt_finished_timestamp`, which it sets on the same spans.
"bullmq.job.finished.timestamp"
]);
const MESSAGING_BULLMQ_KEYS = /* @__PURE__ */ new Map([
["bullmq.flow.name", MESSAGING_BULLMQ_FLOW_NAME],
["bullmq.job.attempt_finished_timestamp", MESSAGING_BULLMQ_JOB_ATTEMPT_FINISHED_TIMESTAMP],
["bullmq.job.bulk.names", MESSAGING_BULLMQ_JOB_BULK_NAMES],
["bullmq.job.deduplication.key", MESSAGING_BULLMQ_JOB_DEDUPLICATION_KEY],
["bullmq.job.ids", MESSAGING_BULLMQ_JOB_IDS],
["bullmq.job.key", MESSAGING_BULLMQ_JOB_KEY],
["bullmq.job.name", MESSAGING_BULLMQ_JOB_NAME],
["bullmq.job.processed.timestamp", MESSAGING_BULLMQ_JOB_PROCESSED_TIMESTAMP],
["bullmq.job.scheduler.id", MESSAGING_BULLMQ_JOB_SCHEDULER_ID],
["bullmq.job.type", MESSAGING_BULLMQ_JOB_TYPE],
["bullmq.queue.clean.count", MESSAGING_BULLMQ_QUEUE_CLEAN_COUNT],
["bullmq.queue.clean.limit", MESSAGING_BULLMQ_QUEUE_CLEAN_LIMIT],
["bullmq.queue.drain.delay", MESSAGING_BULLMQ_QUEUE_DRAIN_DELAY],
["bullmq.queue.event.max.length", MESSAGING_BULLMQ_QUEUE_EVENT_MAX_LENGTH],
["bullmq.queue.grace", MESSAGING_BULLMQ_QUEUE_GRACE],
["bullmq.queue.jobs.state", MESSAGING_BULLMQ_QUEUE_JOBS_STATE],
["bullmq.queue.rate.limit", MESSAGING_BULLMQ_QUEUE_RATE_LIMIT],
["bullmq.worker.do.not.wait.active", MESSAGING_BULLMQ_WORKER_DO_NOT_WAIT_ACTIVE],
["bullmq.worker.force.close", MESSAGING_BULLMQ_WORKER_FORCE_CLOSE],
["bullmq.worker.jobs.to.extend.locks", MESSAGING_BULLMQ_WORKER_JOBS_TO_EXTEND_LOCKS],
["bullmq.worker.name", MESSAGING_BULLMQ_WORKER_NAME],
["bullmq.worker.rate.limit", MESSAGING_BULLMQ_WORKER_RATE_LIMIT],
["bullmq.worker.stalled.jobs", MESSAGING_BULLMQ_WORKER_STALLED_JOBS]
]);
function toMessagingKey(key) {
return MESSAGING_BULLMQ_KEYS.get(key) ?? (key.startsWith("bullmq.") ? `messaging.${key}` : key);
}
function toSentryAttributes(attributes) {
const {
"bullmq.queue.name": queueName,
"bullmq.queue.operation": operation,
"bullmq.job.id": jobId,
"bullmq.job.attempts.made": attemptsMade,
"bullmq.job.bulk.count": bulkCount,
"bullmq.worker.id": workerId,
"bullmq.job.state": jobState,
"bullmq.job.status": jobStatus,
...rest
} = attributes;
const result = {};
for (const [key, value] of Object.entries(rest)) {
if (!DROPPED_ATTRIBUTES.has(key)) {
result[toMessagingKey(key)] = value;
}
}
if (typeof queueName === "string") {
result[MESSAGING_DESTINATION_NAME] = queueName;
}
if (typeof operation === "string") {
result[MESSAGING_OPERATION_NAME] = operation;
}
if (jobId !== void 0) {
result[MESSAGING_MESSAGE_ID] = String(jobId);
}
if (typeof attemptsMade === "number") {
result[MESSAGING_MESSAGE_RETRY_COUNT] = Math.max(attemptsMade - 1, 0);
}
if (typeof bulkCount === "number") {
result[MESSAGING_BATCH_MESSAGE_COUNT] = bulkCount;
}
if (typeof workerId === "string") {
result[MESSAGING_CLIENT_ID] = workerId;
}
const state = jobState ?? jobStatus;
if (typeof state === "string") {
result[MESSAGING_BULLMQ_JOB_STATE] = state;
}
return result;
}
class SentryBullMQSpan {
constructor(span, scope) {
this._span = span;
this._scope = scope;
}
setAttribute(key, value) {
this.setAttributes({ [key]: value });
}
setAttributes(attributes) {
this._span.setAttributes(toSentryAttributes(attributes));
}
addEvent(name, attributes) {
this._span.addEvent(name, attributes);
if (name === "job failed") {
this._span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" });
const reason = attributes?.["bullmq.job.failed.reason"];
const error = new Error(String(reason || "Unknown error"));
error.stack = void 0;
captureException(error, {
mechanism: {
handled: false,
type: "auto.queue.bullmq"
}
});
}
}
/**
* Does not capture to Sentry. BullMQ calls this for Redis, lock and serialization errors that it
* retries itself, often several times for one fault. Job failures are captured in `addEvent`.
*/
recordException(exception) {
const error = exception instanceof Error ? exception : new Error(typeof exception === "string" ? exception : exception.message || "Unknown error");
this._span.recordException(error);
}
setSpanOnContext(context) {
return {
...context,
span: this._span,
scope: this._scope
};
}
end() {
this._span.end();
}
}
export { SentryBullMQSpan, toMessagingKey, toSentryAttributes };
//# sourceMappingURL=span.js.map