@sentry/nextjs
Version:
Official Sentry SDK for Next.js
258 lines (254 loc) • 9.92 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const node_crypto = require('node:crypto');
const api = require('@opentelemetry/api');
const attributes = require('@sentry/conventions/attributes');
const op = require('@sentry/conventions/op');
const core = require('@sentry/core');
const debugBuild = require('../common/debug-build.js');
const useCacheSourceFile = require('./useCacheSourceFile.js');
const NEXT_CACHE_HANDLERS_MAP = /* @__PURE__ */ Symbol.for("@next/cache-handlers-map");
const NEXT_PRIVATE_CACHE_HANDLER = /* @__PURE__ */ Symbol.for("@next/cache-handlers-private");
const SENTRY_CACHE_INSTRUMENTED = /* @__PURE__ */ Symbol.for("sentry.nextjs.cacheHandlersInstrumented");
const SENTRY_WRAPPED_HANDLERS = /* @__PURE__ */ Symbol.for("sentry.nextjs.wrappedCacheHandlers");
const SENTRY_CACHE_ORIGINS = /* @__PURE__ */ Symbol.for("sentry.nextjs.cacheOrigins");
const INTEGRATION_NAME = "NextjsUseCache";
const CACHE_SPAN_ORIGIN = "auto.cache.nextjs";
const CACHE_ORIGIN_LINK_TYPE = "cache_origin";
const CACHE_ORIGINS_MAX_SIZE = 2e3;
const NEXT_INFINITE_CACHE = 4294967294;
const NEXT_DEV_MIN_EXPIRE = 300;
function keyDigest(cacheKey) {
return node_crypto.createHash("sha1").update(cacheKey).digest("hex").slice(0, 12);
}
function shouldRecordCacheSpan() {
const activeSpan = core.getActiveSpan();
return !!activeSpan && core.spanIsSampled(activeSpan);
}
function startCacheSpan(op, digest, extraAttributes, callback) {
const client = core.getClient();
return core.startSpan(
{
// low cardinality name for span streaming, so we can't fall back to the cache key
name: client && core.hasSpanStreamingEnabled(client) ? op : digest,
op,
attributes: {
[attributes.SENTRY_ORIGIN]: CACHE_SPAN_ORIGIN,
[attributes.CACHE_KEY]: [digest],
[attributes.CACHE_OPERATION]: core.CACHE_OPERATION_NAMES[op],
...extraAttributes
}
},
callback
);
}
function isExpired(ageMs, expire) {
if (ageMs === void 0 || typeof expire !== "number") {
return false;
}
const effectiveExpire = process.env.NODE_ENV === "development" ? Math.max(expire, NEXT_DEV_MIN_EXPIRE) : expire;
return ageMs > effectiveExpire * 1e3;
}
function recordCacheEntry(span, entry) {
if (entry === void 0) {
span.setAttribute(attributes.CACHE_HIT, false);
return false;
}
const { timestamp, expire, tags } = entry ?? {};
const ageMs = typeof timestamp === "number" ? core.timestampInSeconds() * 1e3 - timestamp : void 0;
const hit = !isExpired(ageMs, expire);
span.setAttribute(attributes.CACHE_HIT, hit);
if (ageMs !== void 0) {
span.setAttribute(attributes.CACHE_ITEM_AGE, Math.max(0, Math.round(ageMs / 1e3)));
}
if (typeof expire === "number" && expire >= 0 && expire < NEXT_INFINITE_CACHE) {
span.setAttribute(attributes.CACHE_TTL, expire);
}
const stringTags = Array.isArray(tags) ? tags.filter((tag) => typeof tag === "string") : [];
if (stringTags.length > 0) {
span.setAttribute(attributes.CACHE_TAGS, stringTags);
}
return hit;
}
function getCacheOrigins() {
const globalWithCacheHandlers = globalThis;
if (!globalWithCacheHandlers[SENTRY_CACHE_ORIGINS]) {
globalWithCacheHandlers[SENTRY_CACHE_ORIGINS] = new core.LRUMap(CACHE_ORIGINS_MAX_SIZE);
}
return globalWithCacheHandlers[SENTRY_CACHE_ORIGINS];
}
function linkCacheOrigin(span, originKey, entry) {
const context = getPatchedCacheOrigin(entry) ?? getRememberedCacheOrigin(originKey, entry);
if (context) {
span.addLink({ context, attributes: { [attributes.SENTRY_LINK_TYPE]: CACHE_ORIGIN_LINK_TYPE } });
}
}
function getPatchedCacheOrigin(entry) {
const { __sentryCacheOrigin } = entry ?? {};
const origin = typeof __sentryCacheOrigin === "string" ? core.extractTraceparentData(__sentryCacheOrigin) : void 0;
return origin?.traceId && origin.parentSpanId && origin.parentSampled ? { traceId: origin.traceId, spanId: origin.parentSpanId, traceFlags: api.TraceFlags.SAMPLED } : void 0;
}
function getRememberedCacheOrigin(originKey, entry) {
const origin = getCacheOrigins().get(originKey);
const { timestamp } = entry ?? {};
return origin && origin.entryTimestamp === timestamp ? origin.context : void 0;
}
function patchCacheOrigin(pendingEntry, origin) {
const patchedEntry = Promise.resolve(pendingEntry).then((entry) => {
if (typeof entry === "object" && entry !== null) {
try {
entry.__sentryCacheOrigin = origin;
} catch (error) {
debugBuild.DEBUG_BUILD && core.debug.warn("Failed to patch a Next.js cache fill origin", error);
}
}
return entry;
});
patchedEntry.catch(() => void 0);
return patchedEntry;
}
function rememberCacheOrigin(originKey, span, pendingEntry) {
Promise.resolve(pendingEntry).then(
(entry) => {
try {
const { timestamp } = entry ?? {};
if (typeof timestamp === "number") {
getCacheOrigins().set(originKey, { context: span.spanContext(), entryTimestamp: timestamp });
}
} catch (error) {
debugBuild.DEBUG_BUILD && core.debug.warn("Failed to remember a Next.js cache fill origin", error);
}
},
() => {
}
);
}
function isCacheHandler(value) {
return typeof value === "object" && value !== null && typeof value.get === "function" && typeof value.set === "function";
}
function getWrappedHandlers() {
const globalWithCacheHandlers = globalThis;
if (!globalWithCacheHandlers[SENTRY_WRAPPED_HANDLERS]) {
globalWithCacheHandlers[SENTRY_WRAPPED_HANDLERS] = /* @__PURE__ */ new WeakSet();
}
return globalWithCacheHandlers[SENTRY_WRAPPED_HANDLERS];
}
let wrappedHandlerCount = 0;
function instrumentHandler(handler) {
try {
const wrappedHandlers = getWrappedHandlers();
if (!isCacheHandler(handler) || wrappedHandlers.has(handler)) {
return;
}
wrappedHandlers.add(handler);
const originKeyPrefix = `${wrappedHandlerCount++}:`;
core.fill(handler, "get", (originalGet) => {
return function(cacheKey, softTags) {
if (!shouldRecordCacheSpan()) {
return originalGet.call(this, cacheKey, softTags);
}
const digest = keyDigest(cacheKey);
return startCacheSpan(
op.CACHE_GET,
digest,
{},
(span) => (
// `Promise.resolve` because custom handlers may return the entry synchronously.
Promise.resolve(originalGet.call(this, cacheKey, softTags)).then((entry) => {
try {
if (recordCacheEntry(span, entry)) {
linkCacheOrigin(span, originKeyPrefix + digest, entry);
}
} catch (error) {
debugBuild.DEBUG_BUILD && core.debug.warn("Failed to read Next.js cache entry metadata", error);
}
return entry;
})
)
);
};
});
core.fill(handler, "set", (originalSet) => {
return function(cacheKey, pendingEntry) {
if (!shouldRecordCacheSpan()) {
return originalSet.call(this, cacheKey, pendingEntry);
}
const digest = keyDigest(cacheKey);
const sourceFile = useCacheSourceFile.getCacheFunctionSourceFile(cacheKey);
return startCacheSpan(op.CACHE_PUT, digest, sourceFile ? { [attributes.CODE_FILE_PATH]: sourceFile } : {}, (span) => {
if (!span.isRecording()) {
return originalSet.call(this, cacheKey, pendingEntry);
}
const patchedEntry = patchCacheOrigin(pendingEntry, core.spanToTraceHeader(span));
return Promise.resolve(originalSet.call(this, cacheKey, patchedEntry)).then((result) => {
rememberCacheOrigin(originKeyPrefix + digest, span, patchedEntry);
return result;
});
});
};
});
} catch (error) {
debugBuild.DEBUG_BUILD && core.debug.warn("Failed to instrument a Next.js cache handler", error);
}
}
function instrumentHandlersMap(handlersMap) {
for (const handler of handlersMap.values()) {
instrumentHandler(handler);
}
core.fill(handlersMap, "set", (originalSet) => {
return function(kind, handler) {
const result = originalSet.call(this, kind, handler);
instrumentHandler(handler);
return result;
};
});
}
function instrumentWhenAssigned(symbol, onValue) {
const globalWithCacheHandlers = globalThis;
const existingValue = globalWithCacheHandlers[symbol];
if (existingValue !== void 0) {
onValue(existingValue);
return;
}
let storedValue;
Object.defineProperty(globalWithCacheHandlers, symbol, {
configurable: true,
// Non-enumerable so the accessor stays out of copies/spreads of `globalThis` in apps that
// never assign the registry.
enumerable: false,
get: () => storedValue,
set: (value) => {
storedValue = value;
onValue(value);
}
});
}
function _instrumentUseCacheHandlers() {
try {
const globalWithCacheHandlers = globalThis;
if (globalWithCacheHandlers[SENTRY_CACHE_INSTRUMENTED]) {
return;
}
globalWithCacheHandlers[SENTRY_CACHE_INSTRUMENTED] = true;
instrumentWhenAssigned(NEXT_CACHE_HANDLERS_MAP, (value) => {
if (value instanceof Map) {
instrumentHandlersMap(value);
}
});
instrumentWhenAssigned(NEXT_PRIVATE_CACHE_HANDLER, instrumentHandler);
} catch (error) {
debugBuild.DEBUG_BUILD && core.debug.warn("Failed to instrument Next.js cache handlers", error);
}
}
const nextjsUseCacheIntegration = core.defineIntegration(() => {
return {
name: INTEGRATION_NAME,
setup(client) {
if (core.hasSpansEnabled(client.getOptions())) {
_instrumentUseCacheHandlers();
}
}
};
});
exports._instrumentUseCacheHandlers = _instrumentUseCacheHandlers;
exports.nextjsUseCacheIntegration = nextjsUseCacheIntegration;
//# sourceMappingURL=useCacheInstrumentation.js.map