inngest
Version:
Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.
361 lines • 18.4 kB
JavaScript
;
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _InngestSpanProcessor_batcher, _InngestSpanProcessor_spansToExport, _InngestSpanProcessor_traceParents, _InngestSpanProcessor_spanCleanup;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PublicInngestSpanProcessor = exports.InngestSpanProcessor = void 0;
const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
const resources_1 = require("@opentelemetry/resources");
const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
const debug_1 = __importDefault(require("debug"));
const consts_js_1 = require("../../../helpers/consts.js");
const devserver_js_1 = require("../../../helpers/devserver.js");
const env_js_1 = require("../../../helpers/env.js");
const als_js_1 = require("../als.js");
const access_js_1 = require("./access.js");
const consts_js_2 = require("./consts.js");
const processorDebug = (0, debug_1.default)(`${consts_js_2.debugPrefix}:InngestSpanProcessor`);
/**
* A set of resource attributes that are used to identify the Inngest app and
* the function that is being executed. This is used to store the resource
* attributes for the spans that are exported to the Inngest endpoint, and cache
* them for later use.
*/
let _resourceAttributes;
/**
* An OTel span processor that is used to export spans to the Inngest endpoint.
* This is used to track spans that are created within an Inngest run and export
* them to the Inngest endpoint for tracing.
*
* It's careful to only pick relevant spans to export and will not send any
* irrelevant spans to the Inngest endpoint.
*
* THIS IS THE INTERNAL IMPLEMENTATION OF THE SPAN PROCESSOR AND SHOULD NOT BE
* USED BY USERS DIRECTLY. USE THE {@link PublicInngestSpanProcessor} CLASS
* INSTEAD.
*/
class InngestSpanProcessor {
/**
* An OTel span processor that is used to export spans to the Inngest endpoint.
* This is used to track spans that are created within an Inngest run and export
* them to the Inngest endpoint for tracing.
*
* It's careful to only pick relevant spans to export and will not send any
* irrelevant spans to the Inngest endpoint.
*/
constructor(
/**
* The app that this span processor is associated with. This is used to
* determine the Inngest endpoint to export spans to.
*
* It is optional here as this is the private constructor and only used
* internally; we set `app` elsewhere as when we create the processor (as
* early as possible when the process starts) we don't necessarily have the
* app available yet.
*
* So, internally we can delay setting ths until later.
*/
app) {
/**
* A `BatchSpanProcessor` that is used to export spans to the Inngest
* endpoint. This is created lazily to avoid creating it until the Inngest App
* has been initialized and has had a chance to receive environment variables,
* which may be from an incoming request.
*/
_InngestSpanProcessor_batcher.set(this, void 0);
/**
* A set of spans used to track spans that we care about, so that we can
* export them to the OTel endpoint.
*
* If a span falls out of reference, it will be removed from this set as we'll
* never get a chance to export it or remove it anyway.
*/
_InngestSpanProcessor_spansToExport.set(this, new WeakSet());
/**
* A map of span IDs to their parent state, which includes a block of
* information that can be used and pushed back to the Inngest endpoint to
* ingest spans.
*/
_InngestSpanProcessor_traceParents.set(this, new Map());
/**
* A registry used to clean up items from the `traceParents` map when spans
* fall out of reference. This is used to avoid memory leaks in the case where
* a span is not exported, remains unended, and is left in memory before being
* GC'd.
*/
_InngestSpanProcessor_spanCleanup.set(this, new FinalizationRegistry((spanId) => {
if (spanId) {
__classPrivateFieldGet(this, _InngestSpanProcessor_traceParents, "f").delete(spanId);
}
}));
if (app) {
access_js_1.clientProcessorMap.set(app, this);
}
}
/**
* In order to only capture a subset of spans, we need to declare the initial
* span that we care about and then export its children.
*
* Call this method (ideally just before execution starts) with that initial
* span to trigger capturing all following children as well as initialize the
* batcher.
*/
declareStartingSpan({ span, runId, traceparent, tracestate, }) {
// Upsert the batcher ready for later. We do this here to bootstrap it with
// the correct async context as soon as we can. As this method is only
// called just before execution, we know we're all set up.
//
// Waiting to call this until we actually need the batcher would mean that
// we might not have the correct async context set up, as we'd likely be in
// some span lifecycle method that doesn't have the same chain of execution.
void this.ensureBatcherInitialized();
// If we don't have a traceparent, then we can't track this span. This is
// likely a span that we don't care about, so we can ignore it.
if (!traceparent) {
return processorDebug("no traceparent found for span", span.spanContext().spanId, "so skipping it");
}
// We also attempt to use `tracestate`. The values we fetch from these
// should be optional, as it's likely the Executor won't need us to parrot
// them back in later versions.
let appId;
let functionId;
if (tracestate) {
try {
const entries = Object.fromEntries(tracestate.split(",").map((kv) => kv.split("=")));
appId = entries[consts_js_2.TraceStateKey.AppId];
functionId = entries[consts_js_2.TraceStateKey.FunctionId];
}
catch (err) {
processorDebug("failed to parse tracestate", tracestate, "so skipping it;", err);
}
}
// This is a span that we care about, so let's make sure it and its
// children are exported.
processorDebug.extend("declareStartingSpan")("declaring:", span.spanContext().spanId, "for traceparent", traceparent);
// Set a load of attributes on this span so that we can nicely identify
// runtime, paths, etc. Only this span will have these attributes.
span.setAttributes(InngestSpanProcessor.resourceAttributes.attributes);
this.trackSpan({
appId,
functionId,
runId,
traceparent,
}, span);
}
/**
* A getter for retrieving resource attributes for the current process. This
* is used to set the resource attributes for the spans that are exported to
* the Inngest endpoint, and cache them for later use.
*/
static get resourceAttributes() {
if (!_resourceAttributes) {
_resourceAttributes = (0, resources_1.detectResourcesSync)({
detectors: [
resources_1.osDetectorSync,
resources_1.envDetectorSync,
resources_1.hostDetectorSync,
resources_1.processDetectorSync,
resources_1.serviceInstanceIdDetectorSync,
],
});
}
return _resourceAttributes;
}
/**
* The batcher is a singleton that is used to export spans to the OTel
* endpoint. It is created lazily to avoid creating it until the Inngest App
* has been initialized and has had a chance to receive environment variables,
* which may be from an incoming request.
*
* The batcher is only referenced once we've found a span we're interested in,
* so this should always have everything it needs on the app by then.
*/
ensureBatcherInitialized() {
if (!__classPrivateFieldGet(this, _InngestSpanProcessor_batcher, "f")) {
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor
__classPrivateFieldSet(this, _InngestSpanProcessor_batcher, new Promise(async (resolve, reject) => {
var _a;
try {
// We retrieve the app from the async context, so we must make sure
// that this function is called from the correct chain.
const store = await (0, als_js_1.getAsyncCtx)();
if (!store) {
throw new Error("No async context found; cannot create batcher to export traces");
}
const app = store.app;
// Fetch the URL for the Inngest endpoint using the app's config.
let url;
const path = "/v1/traces/userland";
if (app.apiBaseUrl) {
url = new URL(path, app.apiBaseUrl);
}
else {
url = new URL(path, consts_js_1.defaultInngestApiBaseUrl);
if (app["mode"] && app["mode"].isDev && app["mode"].isInferred) {
const devHost = (0, env_js_1.devServerHost)() || consts_js_1.defaultDevServerHost;
const hasDevServer = await (0, devserver_js_1.devServerAvailable)(devHost, app["fetch"]);
if (hasDevServer) {
url = new URL(path, devHost);
}
}
else if ((_a = app["mode"]) === null || _a === void 0 ? void 0 : _a.explicitDevUrl) {
url = new URL(path, app["mode"].explicitDevUrl.href);
}
}
processorDebug("batcher lazily accessed; creating new batcher with URL", url);
const exporter = new exporter_trace_otlp_http_1.OTLPTraceExporter({
url: url.href,
headers: {
Authorization: `Bearer ${app["inngestApi"]["signingKey"]}`,
},
});
resolve(new sdk_trace_base_1.BatchSpanProcessor(exporter));
}
catch (err) {
reject(err);
}
}), "f");
}
return __classPrivateFieldGet(this, _InngestSpanProcessor_batcher, "f");
}
/**
* Mark a span as being tracked by this processor, meaning it will be exported
* to the Inggest endpoint when it ends.
*/
trackSpan(parentState, span) {
const spanId = span.spanContext().spanId;
__classPrivateFieldGet(this, _InngestSpanProcessor_spanCleanup, "f").register(span, spanId, span);
__classPrivateFieldGet(this, _InngestSpanProcessor_spansToExport, "f").add(span);
__classPrivateFieldGet(this, _InngestSpanProcessor_traceParents, "f").set(spanId, parentState);
span.setAttribute(consts_js_2.Attribute.InngestTraceparent, parentState.traceparent);
span.setAttribute(consts_js_2.Attribute.InngestRunId, parentState.runId);
// Setting app ID is optional; it's likely in future versions of the
// Executor that we don't need to parrot this back.
if (parentState.appId) {
span.setAttribute(consts_js_2.Attribute.InngestAppId1, parentState.appId);
span.setAttribute(consts_js_2.Attribute.InngestAppId2, parentState.appId);
}
// Setting function ID is optional; it's likely in future versions of the
// Executor that we don't need to parrot this back.
if (parentState.functionId) {
span.setAttribute(consts_js_2.Attribute.InngestFunctionId, parentState.functionId);
}
}
/**
* Clean up any references to a span that has ended. This is used to avoid
* memory leaks in the case where a span is not exported, remains unended, and
* is left in memory before being GC'd.
*/
cleanupSpan(span) {
const spanId = span.spanContext().spanId;
// This span is no longer in use, so we can remove it from the cleanup
// registry.
__classPrivateFieldGet(this, _InngestSpanProcessor_spanCleanup, "f").unregister(span);
__classPrivateFieldGet(this, _InngestSpanProcessor_spansToExport, "f").delete(span);
__classPrivateFieldGet(this, _InngestSpanProcessor_traceParents, "f").delete(spanId);
}
/**
* An implementation of the `onStart` method from the `SpanProcessor`
* interface. This is called when a span is started, and is used to track
* spans that are children of spans we care about.
*/
onStart(span) {
const debug = processorDebug.extend("onStart");
const spanId = span.spanContext().spanId;
// 🤫 It seems to work
const parentSpanId = span.parentSpanId;
// The root span isn't captured here, but we can capture children of it
// here.
if (!parentSpanId) {
// All spans that Inngest cares about will have a parent, so ignore this
debug("no parent span ID for", spanId, "so skipping it");
return;
}
const parentState = __classPrivateFieldGet(this, _InngestSpanProcessor_traceParents, "f").get(parentSpanId);
if (parentState) {
// This span is a child of a span we care about, so add it to the list of
// tracked spans so that we also capture its children
debug("found traceparent", parentState, "in span ID", parentSpanId, "so adding", spanId);
this.trackSpan(parentState, span);
}
}
/**
* An implementation of the `onEnd` method from the `SpanProcessor` interface.
* This is called when a span ends, and is used to export spans to the Inngest
* endpoint.
*/
onEnd(span) {
const debug = processorDebug.extend("onEnd");
const spanId = span.spanContext().spanId;
try {
if (__classPrivateFieldGet(this, _InngestSpanProcessor_spansToExport, "f").has(span)) {
if (!__classPrivateFieldGet(this, _InngestSpanProcessor_batcher, "f")) {
return debug("batcher not initialized, so failed exporting span", spanId);
}
debug("exporting span", spanId);
return void __classPrivateFieldGet(this, _InngestSpanProcessor_batcher, "f").then((batcher) => batcher.onEnd(span));
}
debug("not exporting span", spanId, "as we don't care about it");
}
finally {
this.cleanupSpan(span);
}
}
/**
* An implementation of the `forceFlush` method from the `SpanProcessor`
* interface. This is called to force the processor to flush any spans that
* are currently in the batcher. This is used to ensure that spans are
* exported to the Inngest endpoint before the process exits.
*
* Notably, we call this in the `beforeResponse` middleware hook to ensure
* that spans for a run as exported as soon as possible and before the
* serverless process is killed.
*/
async forceFlush() {
var _a;
const flushDebug = processorDebug.extend("forceFlush");
flushDebug("force flushing batcher");
return (_a = __classPrivateFieldGet(this, _InngestSpanProcessor_batcher, "f")) === null || _a === void 0 ? void 0 : _a.then((batcher) => batcher.forceFlush()).catch((err) => {
flushDebug("error flushing batcher", err, "ignoring");
});
}
async shutdown() {
var _a;
processorDebug.extend("shutdown")("shutting down batcher");
return (_a = __classPrivateFieldGet(this, _InngestSpanProcessor_batcher, "f")) === null || _a === void 0 ? void 0 : _a.then((batcher) => batcher.shutdown());
}
}
exports.InngestSpanProcessor = InngestSpanProcessor;
_InngestSpanProcessor_batcher = new WeakMap(), _InngestSpanProcessor_spansToExport = new WeakMap(), _InngestSpanProcessor_traceParents = new WeakMap(), _InngestSpanProcessor_spanCleanup = new WeakMap();
/**
* An OTel span processor that is used to export spans to the Inngest endpoint.
* This is used to track spans that are created within an Inngest run and export
* them to the Inngest endpoint for tracing.
*
* It's careful to only pick relevant spans to export and will not send any
* irrelevant spans to the Inngest endpoint.
*/
class PublicInngestSpanProcessor extends InngestSpanProcessor {
constructor(
/**
* The app that this span processor is associated with. This is used to
* determine the Inngest endpoint to export spans to.
*/
app) {
super(app);
}
}
exports.PublicInngestSpanProcessor = PublicInngestSpanProcessor;
//# sourceMappingURL=processor.js.map