'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var node_stream = require('node:stream'); var applicationinsights = require('applicationinsights'); var abstractTransport = require('pino-abstract-transport'); /** * Telemetry exception * @extends {Error} */ class Exception extends Error { /** @type {string | undefined} */ code = undefined; /** * @param {import('pino').SerializedError} serializedError */ constructor(serializedError) { const { message, type, code, stack } = serializedError; super(message); this.name = type; this.type = type; this.code = code; this.stack = stack; } } /** * Transform pino log record to Application Insights Telemetry * * logstream -> transform-to-telemetry -> application insights * * @extends {Transform} */ class TelemetryTransformation extends node_stream.Transform { /** Log line key names to ignore when extracting properties */ ignoreKeys = ['hostname', 'pid', 'level', 'time', 'msg']; /** * @constructor * @param {import('stream').TransformOptions} [options] - optional stream options * @param {import('../types/interfaces.js').TelemetryTransformationConfig} [config] - optional transform options */ constructor(options, config) { super({ ...options, objectMode: true }); this.ignoreKeys = config?.ignoreKeys || this.ignoreKeys; } /** * * @param {string | object} chunk * @param {string} _encoding * @param {CallableFunction} callback */ _transform(chunk, _encoding, callback) { const telemetry = this.convertToTelemetry(chunk); callback(null, telemetry); } /** * Convert to telemetryish object * @param {string | object} chunk * @returns {import('../types/interfaces.js').LogTelemetry} */ convertToTelemetry(chunk) { const line = typeof chunk === 'string' ? JSON.parse(chunk) : chunk; const severity = this.convertLevel(line.level); return { time: new Date(line.time), msg: line.msg, severity, properties: this.extractProperties(line, this.ignoreKeys), ...(line.tagOverrides && { tagOverrides: line.tagOverrides }), ...(line.err && { exception: new Exception(line.err) }), }; } /** * Convert pino log level to SeverityLevel * @param {number} level * @returns {import('applicationinsights').Contracts.SeverityLevel} */ convertLevel(level) { switch (level) { case 30: return applicationinsights.Contracts.SeverityLevel.Information; case 40: return applicationinsights.Contracts.SeverityLevel.Warning; case 50: return applicationinsights.Contracts.SeverityLevel.Error; case 60: return applicationinsights.Contracts.SeverityLevel.Critical; default: return applicationinsights.Contracts.SeverityLevel.Verbose; } } /** * Extract properties from log line * @param {any} line * @param {string[]} [ignoreKeys] * @returns {any} */ extractProperties(line, ignoreKeys) { /** @type {Record} */ const properties = {}; for (const [k, v] of Object.entries(line)) { if (ignoreKeys?.includes(k) || k === 'tagOverrides') continue; properties[k] = v; } return properties; } } /** * Compose Application Insights pino transport * @param {import('../types/interfaces.js').ConnectionStringComposeConfig | import('../types/interfaces.js').DestinationComposeConfig} opts - transport options * @param {typeof TelemetryTransformation} [Transformation] - optional Telemetry transformation stream * @returns {ReturnType} */ function compose(opts, Transformation = TelemetryTransformation) { const track = opts.track ?? trackTraceAndException; if (!opts.destination && (typeof track !== 'function' || !opts.connectionString)) { throw new TypeError('track function and connectionString are required'); } /** @type {Writable | Transform} */ let destination; if (opts.destination) { if (typeof opts.destination.write !== 'function') throw new TypeError('destination must be a writable stream'); destination = opts.destination; } else { const client = new applicationinsights.TelemetryClient(opts.connectionString); if (opts.config) { if (opts.config.disableStatsbeat) { client.getStatsbeat().enable(false); } Object.assign(client.config, opts.config); } const trackTelemetry = track.bind(client); destination = new node_stream.Writable({ objectMode: true, autoDestroy: true, write(chunk, _encoding, callback) { trackTelemetry(chunk); callback(); }, }); } const transformToTelemetry = new Transformation({ objectMode: true, autoDestroy: true }, { ignoreKeys: opts.ignoreKeys }); return abstractTransport((source) => { return node_stream.promises.pipeline(source, transformToTelemetry, destination); }); } /** * Default track function * * Tracks trace and occasional exception * @param {import('../types/interfaces.js').LogTelemetry} chunk * @this {import('applicationinsights').TelemetryClient} */ function trackTraceAndException(chunk) { const { time, severity, msg: message, properties, tagOverrides, exception } = chunk; this.trackTrace({ time, severity, message, properties, tagOverrides }); if (exception) this.trackException({ time, severity, exception, tagOverrides }); } exports.Exception = Exception; exports.TelemetryTransformation = TelemetryTransformation; exports.default = compose; exports.trackTraceAndException = trackTraceAndException; module.exports = Object.assign(exports.default, exports);