@bugsnag/core-performance
Version:
Core performance client
143 lines (140 loc) • 8.13 kB
JavaScript
import { BatchProcessor } from './batch-processor.js';
import { validateConfig, schema } from './config.js';
import { TracePayloadEncoder } from './delivery.js';
import FixedProbabilityManager from './fixed-probability-manager.js';
import { PluginManager } from './plugin.js';
import ProbabilityFetcher from './probability-fetcher.js';
import ProbabilityManager from './probability-manager.js';
import { BufferingProcessor } from './processor.js';
import Sampler from './sampler.js';
import { DefaultSpanContextStorage } from './span-context.js';
import { CompositeSpanControlProvider } from './span-control-provider.js';
import { SpanFactory } from './span-factory.js';
import { timeToNumber } from './time.js';
import { setAppState, getAppState } from './app-state.js';
import PrioritizedSet, { Priority } from './prioritized-set.js';
function createClient(options) {
const HUB_PREFIX = '00000';
const HUB_ENDPOINT = 'https://otlp.insighthub.smartbear.com/v1/traces';
const bufferingProcessor = new BufferingProcessor();
const spanContextStorage = options.spanContextStorage || new DefaultSpanContextStorage(options.backgroundingListener);
let logger = options.schema.logger.defaultValue;
setAppState('starting');
const sampler = new Sampler(1.0);
const SpanFactoryClass = options.spanFactory || SpanFactory;
const spanFactory = new SpanFactoryClass(bufferingProcessor, sampler, options.idGenerator, options.spanAttributesSource, options.clock, options.backgroundingListener, logger, spanContextStorage);
const spanControlProvider = new CompositeSpanControlProvider();
const pluginManager = new PluginManager();
pluginManager.addPlugins(options.plugins(spanFactory, spanContextStorage));
return {
start: (config) => {
const configuration = validateConfig(config, options.schema);
// if using the default endpoint add the API key as a subdomain
// e.g. convert URL https://otlp.bugsnag.com/v1/traces to URL https://<project_api_key>.otlp.bugsnag.com/v1/traces
if (configuration.endpoint === schema.endpoint.defaultValue) {
// …switch to InsightHub when the apiKey starts with 00000
if (configuration.apiKey.startsWith(HUB_PREFIX)) {
configuration.endpoint = HUB_ENDPOINT;
}
else {
// otherwise keep the default Bugsnag domain, but prefix with the apiKey
configuration.endpoint = configuration.endpoint
.replace('https://', `https://${configuration.apiKey}.`);
}
}
// Correlate errors with span by monkey patching _notify on the error client
// and utilizing the setTraceCorrelation method on the event
if (configuration.bugsnag && typeof configuration.bugsnag.Event.prototype.setTraceCorrelation === 'function' && configuration.bugsnag.Client) {
const originalNotify = configuration.bugsnag.Client.prototype._notify;
configuration.bugsnag.Client.prototype._notify = function (...args) {
const currentSpanContext = spanContextStorage.current;
if (currentSpanContext && typeof args[0].setTraceCorrelation === 'function') {
args[0].setTraceCorrelation(currentSpanContext.traceId, currentSpanContext.id);
}
originalNotify.apply(this, args);
};
}
// add any external plugins and install
pluginManager.addPlugins(configuration.plugins);
const pluginContext = pluginManager.installPlugins(configuration, options.clock);
// add span control providers from plugins
spanControlProvider.addProviders(pluginContext.spanControlProviders);
// create a prioritized set of callbacks for onSpanStart and onSpanEnd
const spanStartCallbacks = new PrioritizedSet(pluginContext.onSpanStartCallbacks);
const spanEndCallbacks = new PrioritizedSet(pluginContext.onSpanEndCallbacks);
// user-defined callbacks have normal priority
if (configuration.onSpanStart) {
spanStartCallbacks.addAll(configuration.onSpanStart.map(callback => ({ item: callback, priority: Priority.NORMAL })));
}
if (configuration.onSpanEnd) {
spanEndCallbacks.addAll(configuration.onSpanEnd.map(callback => ({ item: callback, priority: Priority.NORMAL })));
}
configuration.onSpanStart = Array.from(spanStartCallbacks);
configuration.onSpanEnd = Array.from(spanEndCallbacks);
spanFactory.configure(configuration);
const delivery = options.deliveryFactory(configuration.endpoint);
const probabilityManagerPromise = configuration.samplingProbability === undefined
? ProbabilityManager.create(options.persistence, sampler, new ProbabilityFetcher(delivery, configuration.apiKey))
: FixedProbabilityManager.create(sampler, configuration.samplingProbability);
probabilityManagerPromise.then((manager) => {
const batchProcessor = new BatchProcessor(delivery, configuration, options.retryQueueFactory(delivery, configuration.retryQueueMaxSize), sampler, manager, new TracePayloadEncoder(options.clock, configuration, options.resourceAttributesSource));
spanFactory.reprocessEarlySpans(batchProcessor);
// register with the backgrounding listener - we do this in 'start' as
// there's nothing to do if we're backgrounded before start is called
// e.g. we can't trigger delivery until we have the apiKey and endpoint
// from configuration
options.backgroundingListener.onStateChange(state => {
batchProcessor.flush();
// ensure we have a fresh probability value when returning to the
// foreground
if (state === 'in-foreground') {
manager.ensureFreshProbability();
}
});
logger = configuration.logger;
});
pluginManager.startPlugins();
},
startSpan: (name, spanOptions) => {
const cleanOptions = spanFactory.validateSpanOptions(name, spanOptions);
const span = spanFactory.startSpan(cleanOptions.name, cleanOptions.options);
span.setAttribute('bugsnag.span.category', 'custom');
return spanFactory.toPublicApi(span);
},
startNetworkSpan: (networkSpanOptions) => {
const spanInternal = spanFactory.startNetworkSpan(networkSpanOptions);
const span = spanFactory.toPublicApi(spanInternal);
// Overwrite end method to set status code attribute
// once we release the setAttribute API we can simply return the span
const networkSpan = {
...span,
end: (endOptions) => {
spanFactory.endSpan(spanInternal, timeToNumber(options.clock, endOptions.endTime), { 'http.status_code': endOptions.status });
}
};
return networkSpan;
},
getPlugin: (Constructor) => {
return pluginManager.getPlugin(Constructor);
},
getSpanControls: (query) => {
return spanControlProvider.getSpanControls(query);
},
get currentSpanContext() {
return spanContextStorage.current;
},
get appState() {
return getAppState();
},
...(options.platformExtensions && options.platformExtensions(spanFactory, spanContextStorage))
};
}
function createNoopClient() {
const noop = () => { };
return {
start: noop,
startSpan: () => ({ id: '', traceId: '', end: noop, isValid: () => false }),
currentSpanContext: undefined
};
}
export { createClient, createNoopClient };