.
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Contains predefined constants for the trace span name in next/server.
|
||||
*
|
||||
* Currently, next/server/tracer is internal implementation only for tracking
|
||||
* next.js's implementation only with known span names defined here.
|
||||
**/ // eslint typescript has a bug with TS enums
|
||||
var BaseServerSpan = /*#__PURE__*/ function(BaseServerSpan) {
|
||||
BaseServerSpan["handleRequest"] = "BaseServer.handleRequest";
|
||||
BaseServerSpan["run"] = "BaseServer.run";
|
||||
BaseServerSpan["pipe"] = "BaseServer.pipe";
|
||||
BaseServerSpan["getStaticHTML"] = "BaseServer.getStaticHTML";
|
||||
BaseServerSpan["render"] = "BaseServer.render";
|
||||
BaseServerSpan["renderToResponseWithComponents"] = "BaseServer.renderToResponseWithComponents";
|
||||
BaseServerSpan["renderToResponse"] = "BaseServer.renderToResponse";
|
||||
BaseServerSpan["renderToHTML"] = "BaseServer.renderToHTML";
|
||||
BaseServerSpan["renderError"] = "BaseServer.renderError";
|
||||
BaseServerSpan["renderErrorToResponse"] = "BaseServer.renderErrorToResponse";
|
||||
BaseServerSpan["renderErrorToHTML"] = "BaseServer.renderErrorToHTML";
|
||||
BaseServerSpan["render404"] = "BaseServer.render404";
|
||||
return BaseServerSpan;
|
||||
}(BaseServerSpan || {});
|
||||
var LoadComponentsSpan = /*#__PURE__*/ function(LoadComponentsSpan) {
|
||||
LoadComponentsSpan["loadDefaultErrorComponents"] = "LoadComponents.loadDefaultErrorComponents";
|
||||
LoadComponentsSpan["loadComponents"] = "LoadComponents.loadComponents";
|
||||
return LoadComponentsSpan;
|
||||
}(LoadComponentsSpan || {});
|
||||
var NextServerSpan = /*#__PURE__*/ function(NextServerSpan) {
|
||||
NextServerSpan["getRequestHandler"] = "NextServer.getRequestHandler";
|
||||
NextServerSpan["getRequestHandlerWithMetadata"] = "NextServer.getRequestHandlerWithMetadata";
|
||||
NextServerSpan["getServer"] = "NextServer.getServer";
|
||||
NextServerSpan["getServerRequestHandler"] = "NextServer.getServerRequestHandler";
|
||||
NextServerSpan["createServer"] = "createServer.createServer";
|
||||
return NextServerSpan;
|
||||
}(NextServerSpan || {});
|
||||
var NextNodeServerSpan = /*#__PURE__*/ function(NextNodeServerSpan) {
|
||||
NextNodeServerSpan["compression"] = "NextNodeServer.compression";
|
||||
NextNodeServerSpan["getBuildId"] = "NextNodeServer.getBuildId";
|
||||
NextNodeServerSpan["createComponentTree"] = "NextNodeServer.createComponentTree";
|
||||
NextNodeServerSpan["clientComponentLoading"] = "NextNodeServer.clientComponentLoading";
|
||||
NextNodeServerSpan["getLayoutOrPageModule"] = "NextNodeServer.getLayoutOrPageModule";
|
||||
NextNodeServerSpan["generateStaticRoutes"] = "NextNodeServer.generateStaticRoutes";
|
||||
NextNodeServerSpan["generateFsStaticRoutes"] = "NextNodeServer.generateFsStaticRoutes";
|
||||
NextNodeServerSpan["generatePublicRoutes"] = "NextNodeServer.generatePublicRoutes";
|
||||
NextNodeServerSpan["generateImageRoutes"] = "NextNodeServer.generateImageRoutes.route";
|
||||
NextNodeServerSpan["sendRenderResult"] = "NextNodeServer.sendRenderResult";
|
||||
NextNodeServerSpan["proxyRequest"] = "NextNodeServer.proxyRequest";
|
||||
NextNodeServerSpan["runApi"] = "NextNodeServer.runApi";
|
||||
NextNodeServerSpan["render"] = "NextNodeServer.render";
|
||||
NextNodeServerSpan["renderHTML"] = "NextNodeServer.renderHTML";
|
||||
NextNodeServerSpan["imageOptimizer"] = "NextNodeServer.imageOptimizer";
|
||||
NextNodeServerSpan["getPagePath"] = "NextNodeServer.getPagePath";
|
||||
NextNodeServerSpan["getRoutesManifest"] = "NextNodeServer.getRoutesManifest";
|
||||
NextNodeServerSpan["findPageComponents"] = "NextNodeServer.findPageComponents";
|
||||
NextNodeServerSpan["getFontManifest"] = "NextNodeServer.getFontManifest";
|
||||
NextNodeServerSpan["getServerComponentManifest"] = "NextNodeServer.getServerComponentManifest";
|
||||
NextNodeServerSpan["getRequestHandler"] = "NextNodeServer.getRequestHandler";
|
||||
NextNodeServerSpan["renderToHTML"] = "NextNodeServer.renderToHTML";
|
||||
NextNodeServerSpan["renderError"] = "NextNodeServer.renderError";
|
||||
NextNodeServerSpan["renderErrorToHTML"] = "NextNodeServer.renderErrorToHTML";
|
||||
NextNodeServerSpan["render404"] = "NextNodeServer.render404";
|
||||
NextNodeServerSpan["startResponse"] = "NextNodeServer.startResponse";
|
||||
// nested inner span, does not require parent scope name
|
||||
NextNodeServerSpan["route"] = "route";
|
||||
NextNodeServerSpan["onProxyReq"] = "onProxyReq";
|
||||
NextNodeServerSpan["apiResolver"] = "apiResolver";
|
||||
NextNodeServerSpan["internalFetch"] = "internalFetch";
|
||||
return NextNodeServerSpan;
|
||||
}(NextNodeServerSpan || {});
|
||||
var StartServerSpan = /*#__PURE__*/ function(StartServerSpan) {
|
||||
StartServerSpan["startServer"] = "startServer.startServer";
|
||||
return StartServerSpan;
|
||||
}(StartServerSpan || {});
|
||||
var RenderSpan = /*#__PURE__*/ function(RenderSpan) {
|
||||
RenderSpan["getServerSideProps"] = "Render.getServerSideProps";
|
||||
RenderSpan["getStaticProps"] = "Render.getStaticProps";
|
||||
RenderSpan["renderToString"] = "Render.renderToString";
|
||||
RenderSpan["renderDocument"] = "Render.renderDocument";
|
||||
RenderSpan["createBodyResult"] = "Render.createBodyResult";
|
||||
return RenderSpan;
|
||||
}(RenderSpan || {});
|
||||
var AppRenderSpan = /*#__PURE__*/ function(AppRenderSpan) {
|
||||
AppRenderSpan["renderToString"] = "AppRender.renderToString";
|
||||
AppRenderSpan["renderToReadableStream"] = "AppRender.renderToReadableStream";
|
||||
AppRenderSpan["getBodyResult"] = "AppRender.getBodyResult";
|
||||
AppRenderSpan["fetch"] = "AppRender.fetch";
|
||||
return AppRenderSpan;
|
||||
}(AppRenderSpan || {});
|
||||
var RouterSpan = /*#__PURE__*/ function(RouterSpan) {
|
||||
RouterSpan["executeRoute"] = "Router.executeRoute";
|
||||
return RouterSpan;
|
||||
}(RouterSpan || {});
|
||||
var NodeSpan = /*#__PURE__*/ function(NodeSpan) {
|
||||
NodeSpan["runHandler"] = "Node.runHandler";
|
||||
return NodeSpan;
|
||||
}(NodeSpan || {});
|
||||
var AppRouteRouteHandlersSpan = /*#__PURE__*/ function(AppRouteRouteHandlersSpan) {
|
||||
AppRouteRouteHandlersSpan["runHandler"] = "AppRouteRouteHandlers.runHandler";
|
||||
return AppRouteRouteHandlersSpan;
|
||||
}(AppRouteRouteHandlersSpan || {});
|
||||
var ResolveMetadataSpan = /*#__PURE__*/ function(ResolveMetadataSpan) {
|
||||
ResolveMetadataSpan["generateMetadata"] = "ResolveMetadata.generateMetadata";
|
||||
ResolveMetadataSpan["generateViewport"] = "ResolveMetadata.generateViewport";
|
||||
return ResolveMetadataSpan;
|
||||
}(ResolveMetadataSpan || {});
|
||||
var MiddlewareSpan = /*#__PURE__*/ function(MiddlewareSpan) {
|
||||
MiddlewareSpan["execute"] = "Middleware.execute";
|
||||
return MiddlewareSpan;
|
||||
}(MiddlewareSpan || {});
|
||||
// This list is used to filter out spans that are not relevant to the user
|
||||
export const NextVanillaSpanAllowlist = new Set([
|
||||
"Middleware.execute",
|
||||
"BaseServer.handleRequest",
|
||||
"Render.getServerSideProps",
|
||||
"Render.getStaticProps",
|
||||
"AppRender.fetch",
|
||||
"AppRender.getBodyResult",
|
||||
"Render.renderDocument",
|
||||
"Node.runHandler",
|
||||
"AppRouteRouteHandlers.runHandler",
|
||||
"ResolveMetadata.generateMetadata",
|
||||
"ResolveMetadata.generateViewport",
|
||||
"NextNodeServer.createComponentTree",
|
||||
"NextNodeServer.findPageComponents",
|
||||
"NextNodeServer.getLayoutOrPageModule",
|
||||
"NextNodeServer.startResponse",
|
||||
"NextNodeServer.clientComponentLoading"
|
||||
]);
|
||||
// These Spans are allowed to be always logged
|
||||
// when the otel log prefix env is set
|
||||
export const LogSpanAllowList = new Set([
|
||||
"NextNodeServer.findPageComponents",
|
||||
"NextNodeServer.createComponentTree",
|
||||
"NextNodeServer.clientComponentLoading"
|
||||
]);
|
||||
export { BaseServerSpan, LoadComponentsSpan, NextServerSpan, NextNodeServerSpan, StartServerSpan, RenderSpan, RouterSpan, AppRenderSpan, NodeSpan, AppRouteRouteHandlersSpan, ResolveMetadataSpan, MiddlewareSpan, };
|
||||
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+247
@@ -0,0 +1,247 @@
|
||||
import { LogSpanAllowList, NextVanillaSpanAllowlist } from './constants';
|
||||
import { isThenable } from '../../../shared/lib/is-thenable';
|
||||
const NEXT_OTEL_PERFORMANCE_PREFIX = process.env.NEXT_OTEL_PERFORMANCE_PREFIX;
|
||||
let api;
|
||||
// we want to allow users to use their own version of @opentelemetry/api if they
|
||||
// want to, so we try to require it first, and if it fails we fall back to the
|
||||
// version that is bundled with Next.js
|
||||
// this is because @opentelemetry/api has to be synced with the version of
|
||||
// @opentelemetry/tracing that is used, and we don't want to force users to use
|
||||
// the version that is bundled with Next.js.
|
||||
// the API is ~stable, so this should be fine
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
api = require('@opentelemetry/api');
|
||||
} else {
|
||||
try {
|
||||
api = require('@opentelemetry/api');
|
||||
} catch (err) {
|
||||
api = require('next/dist/compiled/@opentelemetry/api');
|
||||
}
|
||||
}
|
||||
const { context, propagation, trace, SpanStatusCode, SpanKind, ROOT_CONTEXT } = api;
|
||||
export class BubbledError extends Error {
|
||||
constructor(bubble, result){
|
||||
super(), this.bubble = bubble, this.result = result;
|
||||
}
|
||||
}
|
||||
export function isBubbledError(error) {
|
||||
if (typeof error !== 'object' || error === null) return false;
|
||||
return error instanceof BubbledError;
|
||||
}
|
||||
const closeSpanWithError = (span, error)=>{
|
||||
if (isBubbledError(error) && error.bubble) {
|
||||
span.setAttribute('next.bubble', true);
|
||||
} else {
|
||||
if (error) {
|
||||
span.recordException(error);
|
||||
span.setAttribute('error.type', error.name);
|
||||
}
|
||||
span.setStatus({
|
||||
code: SpanStatusCode.ERROR,
|
||||
message: error == null ? void 0 : error.message
|
||||
});
|
||||
}
|
||||
span.end();
|
||||
};
|
||||
/** we use this map to propagate attributes from nested spans to the top span */ const rootSpanAttributesStore = new Map();
|
||||
const rootSpanIdKey = api.createContextKey('next.rootSpanId');
|
||||
let lastSpanId = 0;
|
||||
const getSpanId = ()=>lastSpanId++;
|
||||
const clientTraceDataSetter = {
|
||||
set (carrier, key, value) {
|
||||
carrier.push({
|
||||
key,
|
||||
value
|
||||
});
|
||||
}
|
||||
};
|
||||
class NextTracerImpl {
|
||||
/**
|
||||
* Returns an instance to the trace with configured name.
|
||||
* Since wrap / trace can be defined in any place prior to actual trace subscriber initialization,
|
||||
* This should be lazily evaluated.
|
||||
*/ getTracerInstance() {
|
||||
return trace.getTracer('next.js', '0.0.1');
|
||||
}
|
||||
getContext() {
|
||||
return context;
|
||||
}
|
||||
getTracePropagationData() {
|
||||
const activeContext = context.active();
|
||||
const entries = [];
|
||||
propagation.inject(activeContext, entries, clientTraceDataSetter);
|
||||
return entries;
|
||||
}
|
||||
getActiveScopeSpan() {
|
||||
return trace.getSpan(context == null ? void 0 : context.active());
|
||||
}
|
||||
withPropagatedContext(carrier, fn, getter, force = false) {
|
||||
const activeContext = context.active();
|
||||
if (force) {
|
||||
const remoteContext = propagation.extract(ROOT_CONTEXT, carrier, getter);
|
||||
if (trace.getSpanContext(remoteContext)) {
|
||||
return context.with(remoteContext, fn);
|
||||
}
|
||||
// Preserve the current active span while still merging any extracted
|
||||
// baggage/context values from the carrier.
|
||||
const mergedContext = propagation.extract(activeContext, carrier, getter);
|
||||
return context.with(mergedContext, fn);
|
||||
}
|
||||
if (trace.getSpanContext(activeContext)) {
|
||||
// Active span is already set, too late to propagate.
|
||||
return fn();
|
||||
}
|
||||
const remoteContext = propagation.extract(activeContext, carrier, getter);
|
||||
return context.with(remoteContext, fn);
|
||||
}
|
||||
trace(...args) {
|
||||
const [type, fnOrOptions, fnOrEmpty] = args;
|
||||
// coerce options form overload
|
||||
const { fn, options } = typeof fnOrOptions === 'function' ? {
|
||||
fn: fnOrOptions,
|
||||
options: {}
|
||||
} : {
|
||||
fn: fnOrEmpty,
|
||||
options: {
|
||||
...fnOrOptions
|
||||
}
|
||||
};
|
||||
const spanName = options.spanName ?? type;
|
||||
if (!NextVanillaSpanAllowlist.has(type) && process.env.NEXT_OTEL_VERBOSE !== '1' || options.hideSpan) {
|
||||
return fn();
|
||||
}
|
||||
// Trying to get active scoped span to assign parent. If option specifies parent span manually, will try to use it.
|
||||
let spanContext = this.getSpanContext((options == null ? void 0 : options.parentSpan) ?? this.getActiveScopeSpan());
|
||||
if (!spanContext) {
|
||||
spanContext = (context == null ? void 0 : context.active()) ?? ROOT_CONTEXT;
|
||||
}
|
||||
// Check if there's already a root span in the store for this trace
|
||||
// We are intentionally not checking whether there is an active context
|
||||
// from outside of nextjs to ensure that we can provide the same level
|
||||
// of telemetry when using a custom server
|
||||
const existingRootSpanId = spanContext.getValue(rootSpanIdKey);
|
||||
const isRootSpan = typeof existingRootSpanId !== 'number' || !rootSpanAttributesStore.has(existingRootSpanId);
|
||||
const spanId = getSpanId();
|
||||
options.attributes = {
|
||||
'next.span_name': spanName,
|
||||
'next.span_type': type,
|
||||
...options.attributes
|
||||
};
|
||||
return context.with(spanContext.setValue(rootSpanIdKey, spanId), ()=>this.getTracerInstance().startActiveSpan(spanName, options, (span)=>{
|
||||
let startTime;
|
||||
if (NEXT_OTEL_PERFORMANCE_PREFIX && type && LogSpanAllowList.has(type)) {
|
||||
startTime = 'performance' in globalThis && 'measure' in performance ? globalThis.performance.now() : undefined;
|
||||
}
|
||||
let cleanedUp = false;
|
||||
const onCleanup = ()=>{
|
||||
if (cleanedUp) return;
|
||||
cleanedUp = true;
|
||||
rootSpanAttributesStore.delete(spanId);
|
||||
if (startTime) {
|
||||
performance.measure(`${NEXT_OTEL_PERFORMANCE_PREFIX}:next-${(type.split('.').pop() || '').replace(/[A-Z]/g, (match)=>'-' + match.toLowerCase())}`, {
|
||||
start: startTime,
|
||||
end: performance.now()
|
||||
});
|
||||
}
|
||||
};
|
||||
if (isRootSpan) {
|
||||
rootSpanAttributesStore.set(spanId, new Map(Object.entries(options.attributes ?? {})));
|
||||
}
|
||||
if (fn.length > 1) {
|
||||
try {
|
||||
return fn(span, (err)=>closeSpanWithError(span, err));
|
||||
} catch (err) {
|
||||
closeSpanWithError(span, err);
|
||||
throw err;
|
||||
} finally{
|
||||
onCleanup();
|
||||
}
|
||||
}
|
||||
try {
|
||||
const result = fn(span);
|
||||
if (isThenable(result)) {
|
||||
// If there's error make sure it throws
|
||||
return result.then((res)=>{
|
||||
span.end();
|
||||
// Need to pass down the promise result,
|
||||
// it could be react stream response with error { error, stream }
|
||||
return res;
|
||||
}).catch((err)=>{
|
||||
closeSpanWithError(span, err);
|
||||
throw err;
|
||||
}).finally(onCleanup);
|
||||
} else {
|
||||
span.end();
|
||||
onCleanup();
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
closeSpanWithError(span, err);
|
||||
onCleanup();
|
||||
throw err;
|
||||
}
|
||||
}));
|
||||
}
|
||||
wrap(...args) {
|
||||
const tracer = this;
|
||||
const [name, options, fn] = args.length === 3 ? args : [
|
||||
args[0],
|
||||
{},
|
||||
args[1]
|
||||
];
|
||||
if (!NextVanillaSpanAllowlist.has(name) && process.env.NEXT_OTEL_VERBOSE !== '1') {
|
||||
return fn;
|
||||
}
|
||||
return function() {
|
||||
let optionsObj = options;
|
||||
if (typeof optionsObj === 'function' && typeof fn === 'function') {
|
||||
optionsObj = optionsObj.apply(this, arguments);
|
||||
}
|
||||
const lastArgId = arguments.length - 1;
|
||||
const cb = arguments[lastArgId];
|
||||
if (typeof cb === 'function') {
|
||||
const scopeBoundCb = tracer.getContext().bind(context.active(), cb);
|
||||
return tracer.trace(name, optionsObj, (_span, done)=>{
|
||||
arguments[lastArgId] = function(err) {
|
||||
done == null ? void 0 : done(err);
|
||||
return scopeBoundCb.apply(this, arguments);
|
||||
};
|
||||
return fn.apply(this, arguments);
|
||||
});
|
||||
} else {
|
||||
return tracer.trace(name, optionsObj, ()=>fn.apply(this, arguments));
|
||||
}
|
||||
};
|
||||
}
|
||||
startSpan(...args) {
|
||||
const [type, options] = args;
|
||||
const spanContext = this.getSpanContext((options == null ? void 0 : options.parentSpan) ?? this.getActiveScopeSpan());
|
||||
return this.getTracerInstance().startSpan(type, options, spanContext);
|
||||
}
|
||||
getSpanContext(parentSpan) {
|
||||
const spanContext = parentSpan ? trace.setSpan(context.active(), parentSpan) : undefined;
|
||||
return spanContext;
|
||||
}
|
||||
getRootSpanAttributes() {
|
||||
const spanId = context.active().getValue(rootSpanIdKey);
|
||||
return rootSpanAttributesStore.get(spanId);
|
||||
}
|
||||
setRootSpanAttribute(key, value) {
|
||||
const spanId = context.active().getValue(rootSpanIdKey);
|
||||
const attributes = rootSpanAttributesStore.get(spanId);
|
||||
if (attributes && !attributes.has(key)) {
|
||||
attributes.set(key, value);
|
||||
}
|
||||
}
|
||||
withSpan(span, fn) {
|
||||
const spanContext = trace.setSpan(context.active(), span);
|
||||
return context.with(spanContext, fn);
|
||||
}
|
||||
}
|
||||
const getTracer = (()=>{
|
||||
const tracer = new NextTracerImpl();
|
||||
return ()=>tracer;
|
||||
})();
|
||||
export { getTracer, SpanStatusCode, SpanKind };
|
||||
|
||||
//# sourceMappingURL=tracer.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+9
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Takes OpenTelemetry client trace data and the `clientTraceMetadata` option configured in the Next.js config (currently
|
||||
* experimental) and returns a filtered/allowed list of client trace data entries.
|
||||
*/ export function getTracedMetadata(traceData, clientTraceMetadata) {
|
||||
if (!clientTraceMetadata) return undefined;
|
||||
return traceData.filter(({ key })=>clientTraceMetadata.includes(key));
|
||||
}
|
||||
|
||||
//# sourceMappingURL=utils.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../src/server/lib/trace/utils.ts"],"sourcesContent":["import type { ClientTraceDataEntry } from './tracer'\n\n/**\n * Takes OpenTelemetry client trace data and the `clientTraceMetadata` option configured in the Next.js config (currently\n * experimental) and returns a filtered/allowed list of client trace data entries.\n */\nexport function getTracedMetadata(\n traceData: ClientTraceDataEntry[],\n clientTraceMetadata: string[] | undefined\n): ClientTraceDataEntry[] | undefined {\n if (!clientTraceMetadata) return undefined\n return traceData.filter(({ key }) => clientTraceMetadata.includes(key))\n}\n"],"names":["getTracedMetadata","traceData","clientTraceMetadata","undefined","filter","key","includes"],"mappings":"AAEA;;;CAGC,GACD,OAAO,SAASA,kBACdC,SAAiC,EACjCC,mBAAyC;IAEzC,IAAI,CAACA,qBAAqB,OAAOC;IACjC,OAAOF,UAAUG,MAAM,CAAC,CAAC,EAAEC,GAAG,EAAE,GAAKH,oBAAoBI,QAAQ,CAACD;AACpE","ignoreList":[0]}
|
||||
Reference in New Issue
Block a user