@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
301 lines (297 loc) • 9.88 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const api = require('@opentelemetry/api');
const _enum = require('./enum.js');
const AttributeNames = require('./enums/AttributeNames.js');
const symbols = require('./symbols.js');
const OPERATION_VALUES = Object.values(_enum.AllowedOperationTypes);
const isPromise = (value) => {
return typeof value?.then === "function";
};
const isObjectLike = (value) => {
return typeof value == "object" && value !== null;
};
function addInputVariableAttribute(span, key, variable) {
if (Array.isArray(variable)) {
variable.forEach((value, idx) => {
addInputVariableAttribute(span, `${key}.${idx}`, value);
});
} else if (variable instanceof Object) {
Object.entries(variable).forEach(([nestedKey, value]) => {
addInputVariableAttribute(span, `${key}.${nestedKey}`, value);
});
} else {
span.setAttribute(`${AttributeNames.AttributeNames.VARIABLES}${String(key)}`, variable);
}
}
function addInputVariableAttributes(span, variableValues) {
Object.entries(variableValues).forEach(([key, value]) => {
addInputVariableAttribute(span, key, value);
});
}
function addSpanSource(span, loc, allowValues, start, end) {
const source = getSourceFromLocation(loc, allowValues, start, end);
span.setAttribute(AttributeNames.AttributeNames.SOURCE, source);
}
function createFieldIfNotExists(tracer, getConfig, contextValue, info, path) {
let field = getField(contextValue, path);
if (field) {
return { field, spanAdded: false };
}
const config = getConfig();
const parentSpan = config.flatResolveSpans ? getRootSpan(contextValue) : getParentFieldSpan(contextValue, path);
field = {
span: createResolverSpan(tracer, getConfig, contextValue, info, path, parentSpan)
};
addField(contextValue, path, field);
return { field, spanAdded: true };
}
function createResolverSpan(tracer, getConfig, contextValue, info, path, parentSpan) {
const attributes = {
[AttributeNames.AttributeNames.FIELD_NAME]: info.fieldName,
[AttributeNames.AttributeNames.FIELD_PATH]: path.join("."),
[AttributeNames.AttributeNames.FIELD_TYPE]: info.returnType.toString(),
[AttributeNames.AttributeNames.PARENT_NAME]: info.parentType.name
};
const span = tracer.startSpan(
`${_enum.SpanNames.RESOLVE} ${attributes[AttributeNames.AttributeNames.FIELD_PATH]}`,
{
attributes
},
parentSpan ? api.trace.setSpan(api.context.active(), parentSpan) : void 0
);
const document = contextValue[symbols.OTEL_GRAPHQL_DATA_SYMBOL].source;
const fieldNode = info.fieldNodes.find((fieldNode2) => fieldNode2.kind === "Field");
if (fieldNode) {
addSpanSource(span, document.loc, getConfig().allowValues, fieldNode.loc?.start, fieldNode.loc?.end);
}
return span;
}
function endSpan(span, error) {
if (error) {
span.recordException(error);
}
span.end();
}
function getOperation(document, operationName) {
if (!document || !Array.isArray(document.definitions)) {
return void 0;
}
if (operationName) {
return document.definitions.filter((definition) => OPERATION_VALUES.indexOf(definition?.operation) !== -1).find((definition) => operationName === definition?.name?.value);
} else {
return document.definitions.find((definition) => OPERATION_VALUES.indexOf(definition?.operation) !== -1);
}
}
function addField(contextValue, path, field) {
return contextValue[symbols.OTEL_GRAPHQL_DATA_SYMBOL].fields[path.join(".")] = field;
}
function getField(contextValue, path) {
return contextValue[symbols.OTEL_GRAPHQL_DATA_SYMBOL].fields[path.join(".")];
}
function getParentFieldSpan(contextValue, path) {
for (let i = path.length - 1; i > 0; i--) {
const field = getField(contextValue, path.slice(0, i));
if (field) {
return field.span;
}
}
return getRootSpan(contextValue);
}
function getRootSpan(contextValue) {
return contextValue[symbols.OTEL_GRAPHQL_DATA_SYMBOL].span;
}
function pathToArray(mergeItems, path) {
const flattened = [];
let curr = path;
while (curr) {
let key = curr.key;
if (mergeItems && typeof key === "number") {
key = "*";
}
flattened.push(String(key));
curr = curr.prev;
}
return flattened.reverse();
}
function repeatBreak(i) {
return repeatChar("\n", i);
}
function repeatSpace(i) {
return repeatChar(" ", i);
}
function repeatChar(char, to) {
let text = "";
for (let i = 0; i < to; i++) {
text += char;
}
return text;
}
const KindsToBeRemoved = [_enum.TokenKind.FLOAT, _enum.TokenKind.STRING, _enum.TokenKind.INT, _enum.TokenKind.BLOCK_STRING];
function getSourceFromLocation(loc, allowValues = false, inputStart, inputEnd) {
let source = "";
if (loc?.startToken) {
const start = typeof inputStart === "number" ? inputStart : loc.start;
const end = typeof inputEnd === "number" ? inputEnd : loc.end;
let next = loc.startToken.next;
let previousLine = 1;
while (next) {
if (next.start < start) {
next = next.next;
previousLine = next?.line;
continue;
}
if (next.end > end) {
next = next.next;
previousLine = next?.line;
continue;
}
let value = next.value || next.kind;
let space = "";
if (!allowValues && KindsToBeRemoved.indexOf(next.kind) >= 0) {
value = "*";
}
if (next.kind === _enum.TokenKind.STRING) {
value = `"${value}"`;
}
if (next.kind === _enum.TokenKind.EOF) {
value = "";
}
if (next.line > previousLine) {
source += repeatBreak(next.line - previousLine);
previousLine = next.line;
space = repeatSpace(next.column - 1);
} else {
if (next.line === next.prev?.line) {
space = repeatSpace(next.start - (next.prev?.end || 0));
}
}
source += space + value;
if (next) {
next = next.next;
}
}
}
return source;
}
function wrapFields(type, tracer, getConfig) {
if (!type || type[symbols.OTEL_PATCHED_SYMBOL]) {
return;
}
const fields = type.getFields();
type[symbols.OTEL_PATCHED_SYMBOL] = true;
Object.keys(fields).forEach((key) => {
const field = fields[key];
if (!field) {
return;
}
if (field.resolve) {
field.resolve = wrapFieldResolver(tracer, getConfig, field.resolve);
}
if (field.type) {
const unwrappedTypes = unwrapType(field.type);
for (const unwrappedType of unwrappedTypes) {
wrapFields(unwrappedType, tracer, getConfig);
}
}
});
}
function unwrapType(type) {
if ("ofType" in type) {
return unwrapType(type.ofType);
}
if (isGraphQLUnionType(type)) {
return type.getTypes();
}
if (isGraphQLObjectType(type)) {
return [type];
}
return [];
}
function isGraphQLUnionType(type) {
return "getTypes" in type && typeof type.getTypes === "function";
}
function isGraphQLObjectType(type) {
return "getFields" in type && typeof type.getFields === "function";
}
const handleResolveSpanError = (resolveSpan, err, shouldEndSpan) => {
if (!shouldEndSpan) {
return;
}
resolveSpan.recordException(err);
resolveSpan.setStatus({
code: api.SpanStatusCode.ERROR,
message: err.message
});
resolveSpan.end();
};
const handleResolveSpanSuccess = (resolveSpan, shouldEndSpan) => {
if (!shouldEndSpan) {
return;
}
resolveSpan.end();
};
function wrapFieldResolver(tracer, getConfig, fieldResolver, isDefaultResolver = false) {
if (wrappedFieldResolver[symbols.OTEL_PATCHED_SYMBOL] || typeof fieldResolver !== "function") {
return fieldResolver;
}
function wrappedFieldResolver(source, args, contextValue, info) {
if (!fieldResolver) {
return void 0;
}
const config = getConfig();
if (config.ignoreTrivialResolveSpans && isDefaultResolver && (isObjectLike(source) || typeof source === "function")) {
const property = source[info.fieldName];
if (typeof property !== "function") {
return fieldResolver.call(this, source, args, contextValue, info);
}
}
if (!contextValue[symbols.OTEL_GRAPHQL_DATA_SYMBOL]) {
return fieldResolver.call(this, source, args, contextValue, info);
}
const path = pathToArray(config.mergeItems, info && info.path);
const depth = path.filter((item) => typeof item === "string").length;
let span;
let shouldEndSpan = false;
if (config.depth >= 0 && config.depth < depth) {
span = getParentFieldSpan(contextValue, path);
} else {
const { field, spanAdded } = createFieldIfNotExists(tracer, getConfig, contextValue, info, path);
span = field.span;
shouldEndSpan = spanAdded;
}
return api.context.with(api.trace.setSpan(api.context.active(), span), () => {
try {
const res = fieldResolver.call(this, source, args, contextValue, info);
if (isPromise(res)) {
return res.then(
(r) => {
handleResolveSpanSuccess(span, shouldEndSpan);
return r;
},
(err) => {
handleResolveSpanError(span, err, shouldEndSpan);
throw err;
}
);
} else {
handleResolveSpanSuccess(span, shouldEndSpan);
return res;
}
} catch (err) {
handleResolveSpanError(span, err, shouldEndSpan);
throw err;
}
});
}
wrappedFieldResolver[symbols.OTEL_PATCHED_SYMBOL] = true;
return wrappedFieldResolver;
}
exports.addInputVariableAttributes = addInputVariableAttributes;
exports.addSpanSource = addSpanSource;
exports.endSpan = endSpan;
exports.getOperation = getOperation;
exports.getSourceFromLocation = getSourceFromLocation;
exports.isPromise = isPromise;
exports.wrapFieldResolver = wrapFieldResolver;
exports.wrapFields = wrapFields;
//# sourceMappingURL=utils.js.map