@itwin/core-bentley
Version:
Bentley JavaScript core components
168 lines • 8.6 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Logging
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tracing = exports.SpanKind = void 0;
const Logger_1 = require("./Logger");
// re-export so that consumers can construct full SpanOptions object without external dependencies
/**
* Mirrors the SpanKind enum from [@opentelemetry/api](https://open-telemetry.github.io/opentelemetry-js/enums/_opentelemetry_api.SpanKind.html)
* @public
* @deprecated in 4.4 - will not be removed until after 2026-06-13. OpenTelemetry Tracing helpers will become internal in a future release. Apps should use `@opentelemetry/api` directly.
*/
var SpanKind;
(function (SpanKind) {
SpanKind[SpanKind["INTERNAL"] = 0] = "INTERNAL";
SpanKind[SpanKind["SERVER"] = 1] = "SERVER";
SpanKind[SpanKind["CLIENT"] = 2] = "CLIENT";
SpanKind[SpanKind["PRODUCER"] = 3] = "PRODUCER";
SpanKind[SpanKind["CONSUMER"] = 4] = "CONSUMER";
})(SpanKind || (exports.SpanKind = SpanKind = {}));
function isValidPrimitive(val) {
return typeof val === "string" || typeof val === "number" || typeof val === "boolean";
}
// Only _homogenous_ arrays of strings, numbers, or booleans are supported as OpenTelemetry Attribute values.
// Per the spec (https://opentelemetry.io/docs/reference/specification/common/common/#attribute), empty arrays and null values are supported too.
function isValidPrimitiveArray(val) {
if (!Array.isArray(val))
return false;
let itemType;
for (const x of val) {
if (x === undefined || x === null)
continue;
if (!itemType) {
itemType = typeof x;
if (!isValidPrimitive(x))
return false;
}
if (typeof x !== itemType)
return false;
}
return true;
}
function isPlainObject(obj) {
return typeof obj === "object" && obj !== null && Object.getPrototypeOf(obj) === Object.prototype;
}
function* getFlatEntries(obj, path = "") {
if (isValidPrimitiveArray(obj)) {
yield [path, obj];
return;
}
// Prefer JSON serialization over flattening for any non-POJO types.
// There's just too many ways trying to flatten those can go wrong (Dates, Buffers, TypedArrays, etc.)
if (!isPlainObject(obj) && !Array.isArray(obj)) {
yield [path, isValidPrimitive(obj) ? obj : JSON.stringify(obj)];
return;
}
// Always serialize empty objects/arrays as empty array values
const entries = Object.entries(obj);
if (entries.length === 0)
yield [path, []];
for (const [key, val] of entries)
yield* getFlatEntries(val, (path === "") ? key : `${path}.${key}`);
}
function flattenObject(obj) {
return Object.fromEntries(getFlatEntries(obj));
}
/* eslint-disable @typescript-eslint/no-deprecated -- lots of self-references here... */
/**
* Enables OpenTelemetry tracing in addition to traditional logging.
* @public
* @deprecated in 4.4 - will not be removed until after 2026-06-13. OpenTelemetry Tracing helpers will become internal in a future release. Apps should use `@opentelemetry/api` directly.
*/
class Tracing {
static _tracer;
static _openTelemetry;
/**
* If OpenTelemetry tracing is enabled, creates a new span and runs the provided function in it.
* If OpenTelemetry tracing is _not_ enabled, runs the provided function.
* @param name name of the new span
* @param fn function to run inside the new span
* @param options span options
* @param parentContext optional context used to retrieve parent span id
*/
static async withSpan(name, fn, options, parentContext) {
if (Tracing._tracer === undefined || Tracing._openTelemetry === undefined)
return fn();
// this case is for context propagation - parentContext is typically constructed from HTTP headers
const parent = parentContext === undefined
? Tracing._openTelemetry.context.active()
: Tracing._openTelemetry.trace.setSpanContext(Tracing._openTelemetry.context.active(), parentContext);
return Tracing._openTelemetry.context.with(Tracing._openTelemetry.trace.setSpan(parent, Tracing._tracer.startSpan(name, options, Tracing._openTelemetry.context.active())), async () => {
try {
return await fn();
}
catch (err) {
if (err instanceof Error) // ignore non-Error throws, such as RpcControlResponse
Tracing._openTelemetry?.trace.getSpan(Tracing._openTelemetry.context.active())?.setAttribute("error", true);
throw err;
}
finally {
Tracing._openTelemetry?.trace.getSpan(Tracing._openTelemetry.context.active())?.end();
}
});
}
/**
* Adds a span event describing a runtime exception, as advised in OpenTelemetry documentation
* @param e error (exception) object
*/
static recordException(e) {
Tracing._openTelemetry?.trace.getSpan(Tracing._openTelemetry.context.active())?.recordException(e);
}
/**
* Enable logging to OpenTelemetry. [[Tracing.withSpan]] will be enabled, all log entries will be attached to active span as span events.
* [IModelHost.startup]($backend) will call this automatically if the `enableOpenTelemetry` option is enabled and it succeeds in requiring `@opentelemetry/api`.
* @note Node.js OpenTelemetry SDK should be initialized by the user.
*/
static enableOpenTelemetry(tracer, api) {
Tracing._tracer = tracer;
Tracing._openTelemetry = api;
Logger_1.Logger.logTrace = Tracing.withOpenTelemetry(Logger_1.LogLevel.Trace, Logger_1.Logger.logTrace.bind(Logger_1.Logger)).bind(Logger_1.Logger);
Logger_1.Logger.logInfo = Tracing.withOpenTelemetry(Logger_1.LogLevel.Info, Logger_1.Logger.logInfo.bind(Logger_1.Logger)).bind(Logger_1.Logger);
Logger_1.Logger.logWarning = Tracing.withOpenTelemetry(Logger_1.LogLevel.Warning, Logger_1.Logger.logWarning.bind(Logger_1.Logger)).bind(Logger_1.Logger);
Logger_1.Logger.logError = Tracing.withOpenTelemetry(Logger_1.LogLevel.Error, Logger_1.Logger.logError.bind(Logger_1.Logger)).bind(Logger_1.Logger);
}
static withOpenTelemetry(level, base, isError = false) {
return (category, message, metaData) => {
const oTelContext = Tracing._openTelemetry?.context.active();
if (Tracing._openTelemetry === undefined || oTelContext === undefined)
return base(category, message, metaData);
const serializedMetadata = Logger_1.Logger.getMetaData(metaData);
if (Logger_1.Logger.isEnabled(category, level)) {
try {
Tracing._openTelemetry?.trace
.getSpan(Tracing._openTelemetry.context.active())
?.addEvent(message, {
...flattenObject(serializedMetadata),
error: isError,
loggerCategory: category,
});
}
catch { } // avoid throwing random errors (with stack trace mangled by async hooks) when openTelemetry collector doesn't work
const spanContext = Tracing._openTelemetry.trace.getSpan(oTelContext)?.spanContext();
base(category, message, {
...serializedMetadata,
/* eslint-disable @typescript-eslint/naming-convention */
trace_id: spanContext?.traceId,
span_id: spanContext?.spanId,
trace_flags: spanContext?.traceFlags,
/* eslint-enable @typescript-eslint/naming-convention */
});
}
};
}
/** Set attributes on currently active openTelemetry span. Doesn't do anything if openTelemetry logging is not initialized.
* @param attributes The attributes to set
*/
static setAttributes(attributes) {
Tracing._openTelemetry?.trace.getSpan(Tracing._openTelemetry.context.active())?.setAttributes(attributes);
}
}
exports.Tracing = Tracing;
/* eslint-enable @typescript-eslint/no-deprecated */
//# sourceMappingURL=Tracing.js.map