UNPKG

@itwin/core-bentley

Version:

Bentley JavaScript core components

168 lines • 8.6 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * 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