@brizz/sdk
Version:
OpenTelemetry-based observability SDK for AI applications
1,490 lines (1,473 loc) • 67.2 kB
JavaScript
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/internal/instrumentation/auto-init.ts
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic";
import { BedrockInstrumentation } from "@traceloop/instrumentation-bedrock";
import { ChromaDBInstrumentation } from "@traceloop/instrumentation-chromadb";
import { CohereInstrumentation } from "@traceloop/instrumentation-cohere";
import { LangChainInstrumentation } from "@traceloop/instrumentation-langchain";
import { LlamaIndexInstrumentation } from "@traceloop/instrumentation-llamaindex";
import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
import { PineconeInstrumentation } from "@traceloop/instrumentation-pinecone";
import { QdrantInstrumentation } from "@traceloop/instrumentation-qdrant";
import { TogetherInstrumentation } from "@traceloop/instrumentation-together";
import { VertexAIInstrumentation } from "@traceloop/instrumentation-vertexai";
// src/internal/logger.ts
import { DiagLogLevel } from "@opentelemetry/api";
import pino from "pino";
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
LogLevel2[LogLevel2["NONE"] = 0] = "NONE";
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
LogLevel2[LogLevel2["INFO"] = 3] = "INFO";
LogLevel2[LogLevel2["DEBUG"] = 4] = "DEBUG";
return LogLevel2;
})(LogLevel || {});
var DEFAULT_LOG_LEVEL = 2 /* WARN */;
var PinoLogger = class {
_level = DEFAULT_LOG_LEVEL;
_pinoLogger = null;
constructor() {
const envLevel = this.getLogLevelFromEnv();
this._level = envLevel;
}
/**
* Lazy initialization of Pino logger to ensure it's created AFTER Jest spies
* are set up during tests. This prevents the pino-pretty transport from
* bypassing stdout/stderr spies.
*/
ensurePinoLogger() {
if (!this._pinoLogger) {
this._pinoLogger = pino({
name: "Brizz",
level: this.logLevelToPino(this._level),
// Disable transport in test environment to allow proper spy capture
transport: this.isProduction() || this.isTest() ? void 0 : {
target: "pino-pretty",
options: {
singleLine: true,
colorize: true,
translateTime: "HH:MM:ss",
ignore: "pid,hostname",
messageFormat: "[{name}] {msg}"
}
}
});
}
return this._pinoLogger;
}
isProduction() {
return process.env["NODE_ENV"] === "production";
}
isTest() {
return process.env["NODE_ENV"] === "test";
}
getLogLevelFromEnv() {
const envLevel = process.env["BRIZZ_LOG_LEVEL"];
return envLevel ? parseLogLevel(envLevel) : DEFAULT_LOG_LEVEL;
}
logLevelToPino(level) {
switch (level) {
case 4 /* DEBUG */:
return "debug";
case 3 /* INFO */:
return "info";
case 2 /* WARN */:
return "warn";
case 1 /* ERROR */:
return "error";
default:
return "silent";
}
}
formatMeta(meta) {
if (meta.length === 0) {
return {};
}
if (meta.length === 1 && typeof meta[0] === "object" && meta[0] !== null) {
return meta[0];
}
return { metadata: meta };
}
setLevel(level) {
this._level = level;
if (this._pinoLogger) {
this._pinoLogger.level = this.logLevelToPino(level);
}
}
getLevel() {
return this._level;
}
debug = (msg, ...meta) => {
if (this._level >= 4 /* DEBUG */) {
this.ensurePinoLogger().debug(this.formatMeta(meta), msg);
}
};
info = (msg, ...meta) => {
if (this._level >= 3 /* INFO */) {
this.ensurePinoLogger().info(this.formatMeta(meta), msg);
}
};
warn = (msg, ...meta) => {
if (this._level >= 2 /* WARN */) {
this.ensurePinoLogger().warn(this.formatMeta(meta), msg);
}
};
error = (msg, ...meta) => {
if (this._level >= 1 /* ERROR */) {
this.ensurePinoLogger().error(this.formatMeta(meta), msg);
}
};
};
function parseLogLevel(level) {
if (!level) {
return DEFAULT_LOG_LEVEL;
}
const normalizedLevel = level.toLowerCase().trim();
switch (normalizedLevel) {
case "debug":
return 4 /* DEBUG */;
case "info":
return 3 /* INFO */;
case "warn":
case "warning":
return 2 /* WARN */;
case "error":
return 1 /* ERROR */;
case "none":
case "off":
case "silent":
return 0 /* NONE */;
default: {
const numLevel = Number.parseInt(normalizedLevel, 10);
if (!Number.isNaN(numLevel) && numLevel >= 0 && numLevel <= 4) {
return numLevel;
}
return DEFAULT_LOG_LEVEL;
}
}
}
var logger = new PinoLogger();
function setLogLevel(level) {
const resolvedLevel = typeof level === "string" ? parseLogLevel(level) : level;
logger.setLevel(resolvedLevel);
}
function getLogLevel() {
return logger.getLevel();
}
// src/internal/instrumentation/auto-init.ts
var autoInstrumentationsLoaded = false;
var exceptionLogger = (error) => {
logger.error(`Exception in instrumentation: ${String(error)}`);
};
function loadNodeAutoInstrumentations() {
try {
const nodeInstrumentations = getNodeAutoInstrumentations();
registerInstrumentations({ instrumentations: nodeInstrumentations });
return nodeInstrumentations;
} catch (error) {
logger.error(`Failed to load Node.js auto-instrumentations: ${String(error)}`);
return [];
}
}
function loadGenAIInstrumentations() {
const instrumentations = [];
const genAIInstrumentationClasses = [
{ class: OpenAIInstrumentation, name: "OpenAI" },
{ class: AnthropicInstrumentation, name: "Anthropic" },
{ class: CohereInstrumentation, name: "Cohere" },
{ class: VertexAIInstrumentation, name: "Vertex AI" },
{ class: BedrockInstrumentation, name: "Bedrock" },
{ class: PineconeInstrumentation, name: "Pinecone" },
{ class: LangChainInstrumentation, name: "LangChain" },
{ class: LlamaIndexInstrumentation, name: "LlamaIndex" },
{ class: ChromaDBInstrumentation, name: "ChromaDB" },
{ class: QdrantInstrumentation, name: "Qdrant" },
{ class: TogetherInstrumentation, name: "Together" }
];
for (const config of genAIInstrumentationClasses) {
try {
const instrumentation = new config.class({ exceptionLogger });
instrumentations.push(instrumentation);
logger.debug(`Auto-loaded ${config.name} instrumentation`);
} catch (error) {
logger.error(`Failed to auto-load ${config.name} instrumentation: ${String(error)}`);
}
}
try {
registerInstrumentations({ instrumentations });
logger.info(`Auto-registered ${instrumentations.length} GenAI instrumentations`);
} catch (error) {
logger.error(`Failed to register GenAI instrumentations: ${String(error)}`);
}
return instrumentations;
}
function autoInitializeInstrumentations() {
if (autoInstrumentationsLoaded) {
return;
}
try {
const nodeInstrumentations = loadNodeAutoInstrumentations();
const genAIInstrumentations = loadGenAIInstrumentations();
autoInstrumentationsLoaded = true;
logger.info(
`Auto-initialization complete: ${nodeInstrumentations.length} node + ${genAIInstrumentations.length} GenAI instrumentations`
);
} catch (error) {
logger.error(`Auto-initialization failed: ${String(error)}`);
autoInstrumentationsLoaded = false;
}
}
autoInitializeInstrumentations();
// src/internal/sdk.ts
import { resourceFromAttributes as resourceFromAttributes2 } from "@opentelemetry/resources";
import { NodeSDK } from "@opentelemetry/sdk-node";
// src/internal/config.ts
function resolveConfig(options) {
const envLogLevel = process.env["BRIZZ_LOG_LEVEL"] || options.logLevel?.toString() || DEFAULT_LOG_LEVEL.toString();
let resolvedLogLevel;
if (envLogLevel) {
resolvedLogLevel = parseLogLevel(envLogLevel);
} else if (typeof options.logLevel === "string") {
resolvedLogLevel = parseLogLevel(options.logLevel);
} else {
resolvedLogLevel = options.logLevel || DEFAULT_LOG_LEVEL;
}
setLogLevel(resolvedLogLevel);
let maskingStatus;
if (options.masking === true) {
maskingStatus = "enabled";
} else if (options.masking === false) {
maskingStatus = "disabled";
} else if (typeof options.masking === "object") {
maskingStatus = "custom";
} else {
maskingStatus = "default-disabled";
}
logger.debug("Starting configuration resolution", {
appName: options.appName,
baseUrl: options.baseUrl,
hasApiKey: !!options.apiKey,
disableBatch: options.disableBatch,
logLevel: resolvedLogLevel,
headersCount: Object.keys(options.headers || {}).length,
masking: maskingStatus
});
let resolvedMasking;
if (options.masking === true) {
resolvedMasking = {
spanMasking: {},
eventMasking: {}
};
} else if (options.masking && typeof options.masking === "object") {
resolvedMasking = options.masking;
}
const resolvedConfig = {
...options,
appName: process.env["BRIZZ_APP_NAME"] || options.appName || "unknown-app",
baseUrl: process.env["BRIZZ_BASE_URL"] || options.baseUrl || "https://telemetry.brizz.dev",
headers: { ...options.headers },
apiKey: process.env["BRIZZ_API_KEY"] || options.apiKey,
disableBatch: process.env["BRIZZ_DISABLE_BATCH"] === "true" || !!options.disableBatch,
logLevel: resolvedLogLevel,
masking: resolvedMasking
};
if (resolvedConfig.apiKey) {
resolvedConfig.headers["Authorization"] = `Bearer ${resolvedConfig.apiKey}`;
}
if (process.env["BRIZZ_HEADERS"]) {
try {
const envHeaders = JSON.parse(process.env["BRIZZ_HEADERS"]);
Object.assign(resolvedConfig.headers, envHeaders);
logger.debug("Added headers from environment variable", {
headers: Object.keys(envHeaders)
});
} catch (error) {
logger.error("Failed to parse BRIZZ_HEADERS environment variable", { error });
throw new Error("Invalid JSON in BRIZZ_HEADERS environment variable");
}
}
logger.debug("Configuration resolved with environment variables", {
appName: resolvedConfig.appName,
baseUrl: resolvedConfig.baseUrl,
hasApiKey: !!resolvedConfig.apiKey,
disableBatch: resolvedConfig.disableBatch,
envOverrides: {
hasEnvApiKey: !!process.env["BRIZZ_API_KEY"],
hasEnvBaseUrl: !!process.env["BRIZZ_BASE_URL"],
hasEnvBatch: !!process.env["BRIZZ_DISABLE_BATCH"],
hasEnvHeaders: !!process.env["BRIZZ_HEADERS"]
}
});
return resolvedConfig;
}
// src/internal/instrumentation/registry.ts
import { AnthropicInstrumentation as AnthropicInstrumentation2 } from "@traceloop/instrumentation-anthropic";
import { BedrockInstrumentation as BedrockInstrumentation2 } from "@traceloop/instrumentation-bedrock";
import { ChromaDBInstrumentation as ChromaDBInstrumentation2 } from "@traceloop/instrumentation-chromadb";
import { CohereInstrumentation as CohereInstrumentation2 } from "@traceloop/instrumentation-cohere";
import { LangChainInstrumentation as LangChainInstrumentation2 } from "@traceloop/instrumentation-langchain";
import { LlamaIndexInstrumentation as LlamaIndexInstrumentation2 } from "@traceloop/instrumentation-llamaindex";
import { OpenAIInstrumentation as OpenAIInstrumentation2 } from "@traceloop/instrumentation-openai";
import { PineconeInstrumentation as PineconeInstrumentation2 } from "@traceloop/instrumentation-pinecone";
import { QdrantInstrumentation as QdrantInstrumentation2 } from "@traceloop/instrumentation-qdrant";
import { TogetherInstrumentation as TogetherInstrumentation2 } from "@traceloop/instrumentation-together";
import { VertexAIInstrumentation as VertexAIInstrumentation2 } from "@traceloop/instrumentation-vertexai";
var InstrumentationRegistry = class _InstrumentationRegistry {
static instance;
manualModules = null;
static getInstance() {
if (!_InstrumentationRegistry.instance) {
_InstrumentationRegistry.instance = new _InstrumentationRegistry();
}
return _InstrumentationRegistry.instance;
}
/**
* Set manual instrumentation modules for Next.js/Webpack environments
*/
setManualModules(modules) {
this.manualModules = modules;
logger.info("Manual instrumentation modules configured for Next.js/Webpack compatibility");
}
/**
* Helper to load instrumentation with optional manual module
*/
loadInstrumentation(InstrumentationClass, name, manualModule, exceptionLogger2) {
try {
const inst = new InstrumentationClass({
exceptionLogger: exceptionLogger2 || ((error) => logger.error(`Exception in instrumentation: ${String(error)}`))
});
if (manualModule) {
inst.manuallyInstrument(manualModule);
logger.debug(`Manual instrumentation enabled for ${name}`);
}
return inst;
} catch (error) {
logger.error(`Failed to load ${name} instrumentation: ${String(error)}`);
return null;
}
}
/**
* Get manual instrumentations only.
* Auto-instrumentations are handled at import time.
*/
getManualInstrumentations() {
if (!this.manualModules || Object.keys(this.manualModules).length === 0) {
logger.debug("No manual instrumentation modules configured");
return [];
}
const instrumentations = [];
const exceptionLogger2 = (error) => {
logger.error(`Exception in manual instrumentation: ${String(error)}`);
};
this.loadManualInstrumentations(instrumentations, exceptionLogger2);
logger.info(`Loaded ${instrumentations.length} manual instrumentations`);
return instrumentations;
}
/**
* Load manual instrumentations only (with modules provided by user).
* @private
*/
loadManualInstrumentations(instrumentations, exceptionLogger2) {
const instrumentationConfigs = [
{ class: OpenAIInstrumentation2, name: "OpenAI", module: this.manualModules?.openAI },
{ class: AnthropicInstrumentation2, name: "Anthropic", module: this.manualModules?.anthropic },
{ class: CohereInstrumentation2, name: "Cohere", module: this.manualModules?.cohere },
{
class: VertexAIInstrumentation2,
name: "Vertex AI",
module: this.manualModules?.google_vertexai
},
{ class: BedrockInstrumentation2, name: "Bedrock", module: this.manualModules?.bedrock },
{ class: PineconeInstrumentation2, name: "Pinecone", module: this.manualModules?.pinecone },
{ class: LangChainInstrumentation2, name: "LangChain", module: this.manualModules?.langchain },
{
class: LlamaIndexInstrumentation2,
name: "LlamaIndex",
module: this.manualModules?.llamaindex
},
{ class: ChromaDBInstrumentation2, name: "ChromaDB", module: this.manualModules?.chromadb },
{ class: QdrantInstrumentation2, name: "Qdrant", module: this.manualModules?.qdrant },
{ class: TogetherInstrumentation2, name: "Together", module: this.manualModules?.together }
];
for (const config of instrumentationConfigs) {
if (config.module) {
const instrumentation = this.loadInstrumentation(
config.class,
config.name,
config.module,
exceptionLogger2
);
if (instrumentation) {
instrumentations.push(instrumentation);
}
}
}
}
};
// src/internal/log/logging.ts
import { SeverityNumber } from "@opentelemetry/api-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import {
LoggerProvider
} from "@opentelemetry/sdk-logs";
// src/internal/log/processors/log-processor.ts
import { context } from "@opentelemetry/api";
import { BatchLogRecordProcessor, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs";
// src/internal/masking/patterns.ts
var DEFAULT_PII_PATTERNS = [
// Email addresses
{
name: "email_addresses",
pattern: String.raw`\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`
},
// Phone numbers (US format)
{
name: "us_phone_numbers",
pattern: String.raw`(?:^|[\s])(?:\+?1[-.\s]*)?(?:\([0-9]{3}\)\s?[0-9]{3}[-.\s]?[0-9]{4}|[0-9]{3}[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}|[0-9]{10})(?=[\s]|$)`
},
// Social Security Numbers
{
name: "ssn",
pattern: String.raw`\b(?!000|666|9\d{2})\d{3}[-\s]?(?!00)\d{2}[-\s]?(?!0000)\d{4}\b`
},
// Credit card numbers
{
name: "credit_cards",
pattern: String.raw`\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\\d{3})\\d{11})\b`
},
{
name: "credit_cards_with_separators",
pattern: String.raw`\b(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2})[-\s]?\\d{4}[-\s]?\\d{4}[-\s]?\\d{4}\b`
},
// IP addresses (IPv4)
{
name: "ipv4_addresses",
pattern: String.raw`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?!\.[0-9])\b`
},
// API keys/tokens
{
name: "generic_api_keys",
pattern: String.raw`\b(?:[Aa][Pp][Ii][_-]?[Kk][Ee][Yy]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt])[_-]?[=:]?\s*["']?(?:[a-zA-Z0-9\-_.]{20,})["']?\b`
},
{
name: "openai_keys",
pattern: String.raw`\bsk[-_][a-zA-Z0-9]{20,}\b`
},
{
name: "base64_secrets",
pattern: String.raw`\b[A-Za-z0-9+/]{64,}={0,2}\b`
},
// AWS Keys
{
name: "aws_access_keys",
pattern: String.raw`\b(?:AKIA|ABIA|ACCA|ASIA)[0-9A-Z]{16}\b`
},
{
name: "aws_secret_keys",
pattern: String.raw`\b[A-Za-z0-9/+=]*[A-Z][A-Za-z0-9/+=]*[a-z][A-Za-z0-9/+=]*[/+=][A-Za-z0-9/+=]{30,}\b`
},
// GitHub tokens
{
name: "github_tokens",
pattern: String.raw`\bghp_[a-zA-Z0-9]{36}\b`
},
// Slack tokens
{
name: "slack_tokens",
pattern: String.raw`\bxox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24,34}\b`
},
// Stripe keys
{
name: "stripe_keys",
pattern: String.raw`\b(?:sk|pk)_(?:test|live)_[a-zA-Z0-9]{24,}\b`
},
// JWT tokens
{
name: "jwt_tokens",
pattern: String.raw`\beyJ[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\b`
},
// MAC addresses
{
name: "mac_addresses",
pattern: String.raw`\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b`
},
// IPv6 addresses
{
name: "ipv6_addresses",
pattern: String.raw`\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b`
},
// Medical records
{
name: "medical_record_numbers",
pattern: String.raw`\b(?:[Mm][Rr][Nn])[-\s]?\d{6,10}\b`
},
// Bitcoin addresses
{
name: "bitcoin_addresses",
pattern: String.raw`\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b`
},
// Ethereum addresses
{
name: "ethereum_addresses",
pattern: String.raw`\b0x[a-fA-F0-9]{40}(?![a-fA-F0-9])\b`
},
// UUIDs
{
name: "uuids",
pattern: String.raw`\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(?![0-9a-fA-F-])\b`
},
// Database connection strings
{
name: "database_connections",
pattern: String.raw`\b(?:[Mm][Oo][Nn][Gg][Oo][Dd][Bb]|[Pp][Oo][Ss][Tt][Gg][Rr][Ee][Ss]|[Mm][Yy][Ss][Qq][Ll]|[Rr][Ee][Dd][Ii][Ss]|[Mm][Ss][Ss][Qq][Ll]|[Oo][Rr][Aa][Cc][Ll][Ee]):\\/\\/[^\\s]+\b`
},
// Private keys
{
name: "rsa_private_keys",
pattern: "-----BEGIN (?:RSA )?PRIVATE KEY-----"
},
{
name: "pgp_private_keys",
pattern: "-----BEGIN PGP PRIVATE KEY BLOCK-----"
},
{
name: "certificates",
pattern: "-----BEGIN CERTIFICATE-----"
},
// Date of birth patterns
{
name: "date_of_birth_us",
pattern: String.raw`\b(?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12]\\d|3[01])[-/](?:19|20)\\d{2}\b`
},
{
name: "date_of_birth_iso",
pattern: String.raw`\b(?:19|20)\\d{2}[-/](?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12]\\d|3[01])\b`
},
// US Identification Numbers
{
name: "us_passport_numbers",
pattern: String.raw`\b[A-Z]?\\d{6,9}\b`
},
{
name: "drivers_license",
pattern: String.raw`\b[A-Z]{1,2}\\d{3,8}[-\s]?\\d{2,5}[-\s]?\\d{2,5}[-\s]?\\d{1,5}[-\s]?\\d?\b`
},
{
name: "bank_account_numbers",
pattern: String.raw`\b\\d{10,17}\b`
},
{
name: "aba_routing_numbers",
pattern: String.raw`\b((0[0-9])|(1[0-2])|(2[1-9])|(3[0-2])|(6[1-9])|(7[0-2])|80)([0-9]{7})\b`
},
{
name: "health_insurance_numbers",
pattern: String.raw`\b\\d{10}[A-Z]\b`
},
{
name: "employee_ids",
pattern: String.raw`\b(?:[Ee][Mm][Pp]|[Ee][Mm][Pp][Ll][Oo][Yy][Ee][Ee]|[Ee])[-\s]?\\d{5,8}\b`
},
{
name: "tax_ein",
pattern: String.raw`\b\\d{2}-\\d{7}\b`
},
{
name: "medicare_beneficiary_id",
pattern: String.raw`\b[1-9][A-Z][A-Z0-9]\\d-[A-Z][A-Z0-9]\\d-[A-Z][A-Z0-9]\\d{2}\b`
},
{
name: "national_provider_id",
pattern: String.raw`\b1\\d{9}\b`
},
{
name: "dea_numbers",
pattern: String.raw`\b[A-Z]{2}\\d{7}\b`
},
{
name: "itin",
pattern: String.raw`\b9\\d{2}(?:[ \\-]?)[7,8]\\d(?:[ \\-]?)\\d{4}\b`
},
// Vehicle and Location
{
name: "vin_numbers",
pattern: String.raw`\b[A-HJ-NPR-Z0-9]{17}\b`
},
{
name: "coordinates",
pattern: String.raw`\b[-+]?(?:[0-8]?\\d(?:\\.\\d+)?|90(?:\\.0+)?),\\s*[-+]?(?:1[0-7]\\d(?:\\.\\d+)?|180(?:\\.0+)?|[0-9]?\\d(?:\\.\\d+)?)\b`
},
{
name: "us_license_plates",
pattern: String.raw`\b[A-Z]{1,3}[-\s]\\d{1,4}[A-Z]?\b|\b\\d{1,2}[A-Z]{1,3}\\d{1,4}\b`
},
{
name: "us_zip_codes",
pattern: String.raw`\b(\\d{5}-\\d{4}|\\d{5})\b`
},
{
name: "us_street_addresses",
pattern: String.raw`\b\\d{1,8}\b[\\s\\S]{10,100}?\b(AK|AL|AR|AZ|CA|CO|CT|DC|DE|FL|GA|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN|MO|MS|MT|NC|ND|NE|NH|NJ|NM|NV|NY|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VT|WA|WI|WV|WY)\b\\s\\d{5}\b`
},
// International Banking
{
name: "iban",
pattern: String.raw`\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b`
},
{
name: "swift_bic",
pattern: String.raw`\b[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?\b`
},
// Additional API Keys and Tokens
{
name: "google_oauth",
pattern: String.raw`\bya29\\.[a-zA-Z0-9_-]{60,}\b`
},
{
name: "firebase_tokens",
pattern: String.raw`\bAAAA[A-Za-z0-9_-]{100,}\b`
},
{
name: "google_app_ids",
pattern: String.raw`\b[0-9]+-\w+\.apps\.googleusercontent\.com\b`
},
{
name: "facebook_secrets",
pattern: String.raw`\b(facebook_secret|FACEBOOK_SECRET|facebook_app_secret|FACEBOOK_APP_SECRET)[a-z_ =\\s"'\\:]{0,5}[^a-zA-Z0-9][a-f0-9]{32}[^a-zA-Z0-9]`
},
{
name: "github_keys",
pattern: String.raw`\b(GITHUB_SECRET|GITHUB_KEY|github_secret|github_key|github_token|GITHUB_TOKEN|github_api_key|GITHUB_API_KEY)[a-z_ =\\s\"'\\:]{0,10}[^a-zA-Z0-9][a-zA-Z0-9]{40}[^a-zA-Z0-9]`
},
{
name: "heroku_keys",
pattern: String.raw`\b(heroku_api_key|HEROKU_API_KEY|heroku_secret|HEROKU_SECRET)[a-z_ =\\s\"'\\:]{0,10}[^a-zA-Z0-9-]\\w{8}(?:-\\w{4}){3}-\\w{12}[^a-zA-Z0-9\\-]`
},
{
name: "slack_api_keys",
pattern: String.raw`\b(slack_api_key|SLACK_API_KEY|slack_key|SLACK_KEY)[a-z_ =\\s\"'\\:]{0,10}[^a-f0-9][a-f0-9]{32}[^a-f0-9]`
},
{
name: "slack_api_tokens",
pattern: String.raw`\b(xox[pb](?:-[a-zA-Z0-9]+){4,})\b`
},
// Extended Private Keys and Certificates
{
name: "dsa_private_keys",
pattern: String.raw`-----BEGIN DSA PRIVATE KEY-----(?:[a-zA-Z0-9\+\=\/"']|\s)+?-----END DSA PRIVATE KEY-----`
},
{
name: "ec_private_keys",
pattern: String.raw`-----BEGIN (?:EC|ECDSA) PRIVATE KEY-----(?:[a-zA-Z0-9\+\=\/"']|\s)+?-----END (?:EC|ECDSA) PRIVATE KEY-----`
},
{
name: "encrypted_private_keys",
pattern: String.raw`-----BEGIN ENCRYPTED PRIVATE KEY-----(?:.|\s)+?-----END ENCRYPTED PRIVATE KEY-----`
},
{
name: "ssl_certificates",
pattern: String.raw`-----BEGIN CERTIFICATE-----(?:.|\n)+?\s-----END CERTIFICATE-----`
},
{
name: "ssh_dss_public",
pattern: String.raw`\bssh-dss [0-9A-Za-z+/]+[=]{2}\b`
},
{
name: "ssh_rsa_public",
pattern: String.raw`\bssh-rsa AAAA[0-9A-Za-z+/]+[=]{0,3} [^@]+@[^@]+\b`
},
{
name: "putty_ssh_keys",
pattern: String.raw`PuTTY-User-Key-File-2: ssh-(?:rsa|dss)\s*Encryption: none(?:.|\s?)*?Private-MAC:`
},
// International Phone Numbers
{
name: "france_phone_numbers",
pattern: String.raw`\b([0O]?[1lI][1lI])?[3E][3E][0O]?[\\dOIlZEASB]{9}\b`
},
{
name: "german_phone_numbers",
pattern: String.raw`\b[\d\w]\d{2}[\d\w]{6}\d[\d\w]\b`
},
{
name: "uk_phone_numbers",
pattern: String.raw`\b([0O]?[1lI][1lI])?[4A][4A][\\dOIlZEASB]{10,11}\b`
},
// International IDs
{
name: "uk_drivers_license",
pattern: String.raw`\b[A-Z]{5}\d{6}[A-Z]{2}\d{1}[A-Z]{2}\b`
},
{
name: "uk_passport",
pattern: String.raw`\b\\d{10}GB[RP]\\d{7}[UMF]{1}\\d{9}\b`
},
{
name: "argentina_dni",
pattern: String.raw`\b\\d{2}\\.\\d{3}\\.\\d{3}\b`
},
{
name: "australia_tfn",
pattern: String.raw`\b[Tt][Ff][Nn](:|:\\s|\\s|)(\\d{8,9})\b`
},
{
name: "canada_passport",
pattern: String.raw`\b[\\w]{2}[\\d]{6}\b`
},
{
name: "croatia_vat",
pattern: String.raw`\bHR\\d{11}\b`
},
{
name: "czech_vat",
pattern: String.raw`\bCZ\\d{8,10}\b`
},
{
name: "denmark_personal_id",
pattern: String.raw`\b(?:\\d{10}|\\d{6}[-\\s]\\d{4})\b`
},
{
name: "france_national_id",
pattern: String.raw`\b\\d{12}\b`
},
{
name: "france_ssn",
pattern: String.raw`\b(?:\\d{13}|\\d{13}\\s\\d{2})\b`
},
{
name: "france_passport",
pattern: String.raw`\b\\d{2}11\\d{5}\b`
},
{
name: "california_drivers_license",
pattern: String.raw`\b[A-Z]{1}\\d{7}\b`
},
// Medical and Healthcare
{
name: "hipaa_ndc",
pattern: String.raw`\b\\d{4,5}-\\d{3,4}-\\d{1,2}\b`
},
// Security and Network
{
name: "cve_numbers",
pattern: String.raw`\b[Cc][Vv][Ee]-\\d{4}-\\d{4,7}\b`
},
{
name: "microsoft_oauth",
pattern: String.raw`https://login\.microsoftonline\.com/common/oauth2/v2\.0/token|https://login\.windows\.net/common/oauth2/token`
},
{
name: "postgres_connections",
pattern: String.raw`\b(?:[Pp][Oo][Ss][Tt][Gg][Rr][Ee][Ss]|[Pp][Gg][Ss][Qq][Ll])\\:\\/\\/`
},
{
name: "box_links",
pattern: String.raw`https://app\.box\.com/[s|l]/\S+`
},
{
name: "dropbox_links",
pattern: String.raw`https://www\.dropbox\.com/(?:s|l)/\S+`
},
// Credit Card Variants
{
name: "amex_cards",
pattern: String.raw`\b3[47][0-9]{13}\b`
},
{
name: "visa_cards",
pattern: String.raw`\b4[0-9]{12}(?:[0-9]{3})?\b`
},
{
name: "discover_cards",
pattern: String.raw`\b65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}\b`
},
{
name: "enhanced_credit_cards",
pattern: String.raw`\b((4\\d{3}|5[1-5]\\d{2}|2\\d{3}|3[47]\\d{1,2})[\\s\\-]?\\d{4,6}[\\s\\-]?\\d{4,6}?([\\s\\-]\\d{3,4})?(\\d{3})?)\b`
},
// Bank Routing Numbers (US specific)
{
name: "bbva_routing_ca",
pattern: String.raw`\\b321170538\\b`
},
{
name: "boa_routing_ca",
pattern: String.raw`\\b(?:121|026)00(?:0|9)(?:358|593)\\b`
},
{
name: "chase_routing_ca",
pattern: String.raw`\\b322271627\\b`
},
{
name: "citibank_routing_ca",
pattern: String.raw`\\b32(?:11|22)71(?:18|72)4\\b`
},
{
name: "usbank_routing_ca",
pattern: String.raw`\\b12(?:1122676|2235821)\\b`
},
{
name: "united_bank_routing_ca",
pattern: String.raw`\\b122243350\\b`
},
{
name: "wells_fargo_routing_ca",
pattern: String.raw`\\b121042882\\b`
},
// Unrealistic alphanumeric identifiers
{
name: "generic_non_usual",
pattern: String.raw`(?:^|\s)(?=[A-Za-z0-9_\)\*\=@]*[A-Za-z])(?=[A-Za-z0-9_\)\*\=@]*[0-9])([A-Za-z0-9_\)\*\=@]{5,})(?=\s|$)`
}
];
// src/internal/masking/utils.ts
function isValidPatternName(name) {
return /^[a-zA-Z0-9_]+$/.test(name);
}
function isLikelyReDoSPattern(pattern) {
const dangerousPatterns = [
// Nested quantifiers like (a+)+, (a*)+, (a+)*
/\([^)]*[+*]\)[+*]/,
// Alternation with overlapping groups like (a|a)*
/\([^)]*\|[^)]*\)[+*]/,
// Complex backtracking patterns - but more specific
/\([^)]*[+*][^)]*[+*][^)]*\)[+*]/
];
return dangerousPatterns.some((dangerous) => dangerous.test(pattern));
}
function getGroupedPattern(patternEntry) {
let name = patternEntry.name;
if (!name || name === "") {
name = `pattern_${Math.random().toString(16).slice(2)}`;
}
if (!isValidPatternName(name)) {
throw new Error(
`Pattern name '${name}' must only contain alphanumeric characters and underscores`
);
}
if (isLikelyReDoSPattern(patternEntry.pattern)) {
throw new Error(`Potentially dangerous ReDoS pattern detected: '${patternEntry.pattern}'`);
}
try {
new RegExp(patternEntry.pattern);
} catch (error) {
throw new Error(`Invalid regex pattern '${patternEntry.pattern}': ${String(error)}`);
}
return `(?<${name}>${patternEntry.pattern})`;
}
function isIPatternEntry(obj) {
return typeof obj === "object" && obj !== null && typeof obj.pattern === "string" && (obj.name === void 0 || typeof obj.name === "string");
}
function isIEventMaskingRule(obj) {
return typeof obj === "object" && obj !== null && typeof obj.mode === "string" && Array.isArray(obj.patterns) && obj.patterns.every(
(p) => isIPatternEntry(p) || typeof p === "string"
) && (obj.attributePattern === void 0 || typeof obj.attributePattern === "string");
}
function convertPatternsToPatternEntries(patterns) {
const patternEntries = [];
for (const pattern of patterns) {
if (typeof pattern === "string") {
patternEntries.push({ pattern, name: "" });
} else if (isIPatternEntry(pattern)) {
patternEntries.push(pattern);
} else {
throw new Error("Patterns must be either strings or PatternEntry instances");
}
}
return patternEntries;
}
function compilePatternEntries(patternEntries) {
const patternGroups = [];
for (const patternEntry of patternEntries) {
try {
patternGroups.push(getGroupedPattern(patternEntry));
} catch (error) {
logger.warn(`Invalid pattern '${patternEntry.name}': ${patternEntry.pattern}`, error);
continue;
}
}
if (patternGroups.length === 0) {
return null;
}
try {
return new RegExp(patternGroups.join("|"));
} catch (error) {
logger.warn("Failed to compile pattern entries into regex", error);
return null;
}
}
function getCompiledAttributeNamePattern(rule) {
if (!rule.attributePattern) {
logger.debug("No attribute pattern provided, using default .*");
return /.*/;
}
let compiledPatternString = rule.attributePattern;
if (isIEventMaskingRule(rule) && rule.eventNamePattern) {
compiledPatternString = `(${compiledPatternString})|(${rule.eventNamePattern})`;
}
return new RegExp(compiledPatternString);
}
function shouldContinueExecution(startTime, iterations, timeoutMs) {
if (Date.now() - startTime > timeoutMs) {
logger.warn("Regex execution timed out - potential ReDoS pattern detected");
return false;
}
const maxIterations = 1e3;
if (iterations > maxIterations) {
logger.warn("Regex execution exceeded maximum iterations - potential ReDoS pattern detected");
return false;
}
return true;
}
function createGlobalPattern(pattern) {
return new RegExp(
pattern.source,
pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g"
);
}
function executeRegexWithTimeout(pattern, value, timeoutMs = 100) {
const matches = [];
const globalPattern = createGlobalPattern(pattern);
const startTime = Date.now();
let match;
let iterations = 0;
while ((match = globalPattern.exec(value)) !== null) {
if (!shouldContinueExecution(startTime, ++iterations, timeoutMs)) {
break;
}
matches.push(match);
if (match[0].length === 0) {
globalPattern.lastIndex = match.index + 1;
}
}
return matches;
}
function maskStringByPattern(value, pattern, mode = "full") {
const matches = [];
try {
const regexMatches = executeRegexWithTimeout(pattern, value);
for (const match of regexMatches) {
matches.push({
start: match.index,
end: match.index + match[0].length,
text: match[0],
groups: match.groups
});
}
} catch (error) {
logger.warn("Regex execution failed, skipping masking", error);
return value;
}
for (const matchInfo of matches.reverse()) {
let patternName = "unknown";
if (matchInfo.groups) {
for (const [groupName, groupValue] of Object.entries(matchInfo.groups)) {
if (groupValue !== void 0) {
if (groupName.includes("_") && /\d$/.test(groupName)) {
const parts = groupName.split("_");
patternName = parts[0] || groupName;
} else {
patternName = groupName;
}
break;
}
}
}
logger.info(`Masking detected: pattern '${patternName}' found match in value`);
const masked = mode === "partial" ? matchInfo.text[0] + "*****" : "*****";
value = value.slice(0, matchInfo.start) + masked + value.slice(matchInfo.end);
}
return value;
}
function maskStringByPatternBasedRule(value, rule) {
const patternEntries = convertPatternsToPatternEntries(rule.patterns);
if (patternEntries.length === 0) {
logger.warn("No patterns provided for masking rule, returning original value");
return value;
}
const mode = rule.mode;
if (!patternEntries || patternEntries.length === 0) {
return mode === "partial" && value ? value[0] + "*****" : "*****";
}
const compiledPatterns = compilePatternEntries(patternEntries);
if (!compiledPatterns) {
return value;
}
return maskStringByPattern(value, compiledPatterns, mode);
}
function maskValue(value, rule) {
if (typeof value === "string") {
return maskStringByPatternBasedRule(value, rule);
} else if (typeof value === "boolean" || typeof value === "number") {
return maskStringByPatternBasedRule(String(value), rule);
} else if (Array.isArray(value)) {
return value.map((v) => maskStringByPatternBasedRule(String(v), rule));
} else if (value !== null && typeof value === "object") {
const result = {};
for (const [k, v] of Object.entries(value)) {
result[k] = maskValue(v, rule);
}
return result;
} else {
throw new Error(`Unsupported value type for masking: ${typeof value}`);
}
}
function maskAttributes(attributes, rules, outputOriginalValue = false) {
if (!attributes || Object.keys(attributes).length === 0) {
return {};
}
const maskedAttributes = { ...attributes };
for (const rule of rules) {
const compiledAttributeNamePattern = getCompiledAttributeNamePattern(rule);
const attributesToMask = compiledAttributeNamePattern ? Object.keys(maskedAttributes).filter((attr) => compiledAttributeNamePattern.test(attr)) : Object.keys(maskedAttributes);
for (const attribute of attributesToMask) {
const value = maskedAttributes[attribute];
if (value === null || value === void 0) {
continue;
}
if (outputOriginalValue) {
logger.debug(`Masking attribute '${attribute}' with original value`, { value });
}
maskedAttributes[attribute] = maskValue(value, rule);
}
}
return maskedAttributes;
}
// src/internal/semantic-conventions.ts
import { createContextKey } from "@opentelemetry/api";
var BRIZZ = "brizz";
var PROPERTIES = "properties";
var SESSION_ID = "session.id";
var PROPERTIES_CONTEXT_KEY = createContextKey(PROPERTIES);
// src/internal/log/processors/log-processor.ts
var DEFAULT_LOG_MASKING_RULES = [
{
mode: "partial",
attributePattern: "event.name",
patterns: DEFAULT_PII_PATTERNS
}
];
var BrizzSimpleLogRecordProcessor = class extends SimpleLogRecordProcessor {
config;
constructor(exporter, config) {
super(exporter);
this.config = config;
}
onEmit(logRecord) {
const maskingConfig = this.config.masking?.eventMasking;
if (maskingConfig) {
maskLog(logRecord, maskingConfig);
}
const associationProperties = context.active().getValue(PROPERTIES_CONTEXT_KEY);
if (associationProperties) {
for (const [key, value] of Object.entries(associationProperties)) {
logRecord.setAttribute(`${BRIZZ}.${key}`, value);
}
}
super.onEmit(logRecord);
}
};
var BrizzBatchLogRecordProcessor = class extends BatchLogRecordProcessor {
config;
constructor(exporter, config) {
super(exporter);
this.config = config;
}
onEmit(logRecord) {
const maskingConfig = this.config.masking?.eventMasking;
if (maskingConfig) {
maskLog(logRecord, maskingConfig);
}
const associationProperties = context.active().getValue(PROPERTIES_CONTEXT_KEY);
if (associationProperties) {
for (const [key, value] of Object.entries(associationProperties)) {
logRecord.setAttribute(`${BRIZZ}.${key}`, value);
}
}
super.onEmit(logRecord);
}
};
function maskLog(logRecord, config) {
if (!logRecord.attributes) {
return logRecord;
}
let rules = config.rules || [];
if (!config.disableDefaultRules) {
rules = [...DEFAULT_LOG_MASKING_RULES, ...rules];
}
try {
if (logRecord.attributes && Object.keys(logRecord.attributes).length > 0) {
const attributesRecord = {};
for (const [key, value] of Object.entries(logRecord.attributes)) {
attributesRecord[key] = value;
}
const maskedAttributes = maskAttributes(attributesRecord, rules);
if (maskedAttributes) {
const newAttributes = {};
for (const [key, value] of Object.entries(maskedAttributes)) {
newAttributes[key] = value;
}
logRecord.setAttributes(newAttributes);
}
}
if (config.maskBody && logRecord.body !== void 0 && logRecord.body !== null) {
let maskedBody = logRecord.body;
for (const rule of rules) {
maskedBody = maskValue(maskedBody, rule);
}
logRecord.setBody(maskedBody);
}
return logRecord;
} catch (error) {
logger.error("Error masking log record:", error);
return logRecord;
}
}
// src/internal/log/logging.ts
var LoggingModule = class _LoggingModule {
static instance = null;
logExporter = null;
logProcessor = null;
loggerProvider = null;
static getInstance() {
if (!_LoggingModule.instance) {
throw new Error("Brizz must be initialized before accessing LoggingModule");
}
return _LoggingModule.instance;
}
/**
* Initialize the logging module with the provided configuration
*/
setup(config) {
logger.info("Setting up logging module");
this.initLogExporter(config);
this.initLogProcessor(config);
this.initLoggerProvider(config);
_LoggingModule.instance = this;
logger.info("Logging module setup completed");
}
/**
* Initialize the log exporter
*/
initLogExporter(config) {
if (this.logExporter) {
logger.debug("Log exporter already initialized, skipping re-initialization");
return;
}
if (config.customLogExporter) {
logger.debug("Using custom log exporter");
this.logExporter = config.customLogExporter;
logger.debug("Custom log exporter initialized successfully");
return;
}
const logsUrl = config.baseUrl.replace(/\/$/, "") + "/v1/logs";
logger.debug("Initializing default OTLP log exporter", { url: logsUrl });
const headers = { ...config.headers };
this.logExporter = new OTLPLogExporter({
url: logsUrl,
headers
});
logger.debug("OTLP log exporter initialized successfully");
}
/**
* Initialize the log processor with masking support
*/
initLogProcessor(config) {
if (this.logProcessor) {
logger.debug("Log processor already initialized, skipping re-initialization");
return;
}
if (!this.logExporter) {
throw new Error("Log exporter must be initialized before processor");
}
logger.debug("Initializing log processor", {
disableBatch: config.disableBatch,
hasMasking: !!config.masking?.eventMasking
});
this.logProcessor = config.disableBatch ? new BrizzSimpleLogRecordProcessor(this.logExporter, config) : new BrizzBatchLogRecordProcessor(this.logExporter, config);
logger.debug("Log processor initialized successfully");
}
/**
* Initialize the logger provider
*/
initLoggerProvider(config) {
if (this.loggerProvider) {
logger.debug("Logger provider already initialized, skipping re-initialization");
return;
}
if (!this.logProcessor) {
throw new Error("Log processor must be initialized before logger provider");
}
logger.debug("Creating resource with service name", {
serviceName: config.appName
});
const resource = resourceFromAttributes({
"service.name": config.appName
});
logger.debug("Creating logger provider with resource");
this.loggerProvider = new LoggerProvider({
resource,
processors: [this.logProcessor]
});
logger.debug("Logger provider initialization completed");
}
/**
* Emit a custom event to the telemetry pipeline
*/
emitEvent(name, attributes, body, severityNumber = SeverityNumber.INFO) {
logger.debug("Attempting to emit event", {
name,
hasAttributes: !!attributes,
attributesCount: attributes ? Object.keys(attributes).length : 0,
hasBody: !!body,
severityNumber
});
if (!this.loggerProvider) {
logger.error("Cannot emit event: Logger provider not initialized");
throw new Error("Logging module not initialized");
}
const logAttributes = { "event.name": name };
if (attributes) {
Object.assign(logAttributes, attributes);
logger.debug("Combined log attributes", { attributes: Object.keys(logAttributes) });
}
logger.debug("Getting logger instance for brizz_events");
const eventLogger = this.loggerProvider.getLogger("brizz.events");
logger.debug("Emitting log record with eventName", { eventName: name });
try {
eventLogger.emit({
eventName: name,
attributes: logAttributes,
severityNumber,
body
});
logger.debug("Event successfully emitted", { eventName: name });
} catch (error) {
logger.error(`Failed to emit event '${name}'`, { error, eventName: name });
logger.error("Log record that failed", {
eventName: name,
hasAttributes: logAttributes,
severityNumber,
hasBody: body
});
throw error instanceof Error ? error : new Error(String(error));
}
}
/**
* Check if the module is initialized
*/
isInitialized() {
return this.loggerProvider !== null;
}
/**
* Get the logger provider
*/
getLoggerProvider() {
return this.loggerProvider;
}
/**
* Shutdown the logging module
*/
async shutdown() {
logger.debug("Shutting down logging module");
if (this.loggerProvider) {
await this.loggerProvider.shutdown();
this.loggerProvider = null;
}
this.logProcessor = null;
this.logExporter = null;
if (_LoggingModule.instance === this) {
_LoggingModule.instance = null;
}
logger.debug("Logging module shutdown completed");
}
};
function emitEvent(name, attributes, body, severityNumber = SeverityNumber.INFO) {
return LoggingModule.getInstance().emitEvent(name, attributes, body, severityNumber);
}
// src/internal/metric/metrics.ts
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
var MetricsModule = class _MetricsModule {
static instance = null;
metricsExporter = null;
metricsReader = null;
static getInstance() {
if (!_MetricsModule.instance) {
throw new Error("Brizz must be initialized before accessing MetricsModule");
}
return _MetricsModule.instance;
}
/**
* Initialize the metrics module with the provided configuration
*/
setup(config) {
logger.info("Setting up metrics module");
this.initMetricsReader(config);
_MetricsModule.instance = this;
logger.info("Metrics module setup completed");
}
/**
* Initialize the metrics exporter
*/
initMetricsExporter(config) {
if (this.metricsExporter) {
logger.debug("Metrics exporter already initialized, skipping re-initialization");
return;
}
const metricsUrl = config.baseUrl.replace(/\/$/, "") + "/v1/metrics";
logger.debug("Initializing metrics exporter", { url: metricsUrl });
this.metricsExporter = new OTLPMetricExporter({
url: metricsUrl,
headers: config.headers
});
logger.debug("Metrics exporter initialized successfully");
}
/**
* Initialize the metrics reader
*/
initMetricsReader(config) {
if (this.metricsReader) {
logger.debug("Metrics reader already initialized, skipping re-initialization");
return;
}
if (config.customMetricReader) {
logger.debug("Using custom metric reader");
this.metricsReader = config.customMetricReader;
logger.debug("Custom metric reader initialized successfully");
return;
}
logger.debug(
"Using default metrics flow - creating OTLP exporter and PeriodicExportingMetricReader"
);
this.initMetricsExporter(config);
if (!this.metricsExporter) {
throw new Error("Failed to initialize metrics exporter");
}
this.metricsReader = new PeriodicExportingMetricReader({
exporter: this.metricsExporter
});
logger.debug("Default metrics reader initialized successfully");
}
/**
* Get the metrics exporter
*/
getMetricsExporter() {
if (!this.metricsExporter) {
throw new Error("Metrics module not initialized");
}
return this.metricsExporter;
}
/**
* Get the metrics reader
*/
getMetricsReader() {
if (!this.metricsReader) {
throw new Error("Metrics module not initialized");
}
return this.metricsReader;
}
/**
* Shutdown the metrics module
*/
shutdown() {
logger.debug("Shutting down metrics module");
this.metricsReader = null;
this.metricsExporter = null;
if (_MetricsModule.instance === this) {
_MetricsModule.instance = null;
}
logger.debug("Metrics module shutdown completed");
}
};
function getMetricsExporter() {
return MetricsModule.getInstance().getMetricsExporter();
}
function getMetricsReader() {
return MetricsModule.getInstance().getMetricsReader();
}
// src/internal/trace/tracing.ts
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
// src/internal/trace/processors/span-processor.ts
import { context as context2 } from "@opentelemetry/api";
import {
BatchSpanProcessor,
SimpleSpanProcessor
} from "@opentelemetry/sdk-trace-base";
// src/internal/trace/transformations/vercel-ai.ts
import { SpanAttributes } from "@traceloop/ai-semantic-conventions";
var AI_GENERATE_TEXT_DO_GENERATE = "ai.generateText.doGenerate";
var AI_STREAM_TEXT_DO_STREAM = "ai.streamText.doStream";
var HANDLED_SPAN_NAMES = {
[AI_GENERATE_TEXT_DO_GENERATE]: "gen_ai.chat",
[AI_STREAM_TEXT_DO_STREAM]: "gen_ai.chat",
"ai.streamText": "ai.streamText",
"ai.toolCall": (span) => {
const toolName = span.attributes["ai.toolCall.name"];
return `${String(toolName ?? "unknown")}.tool`;
}
};
var AI_RESPONSE_TEXT = "ai.response.text";
var AI_PROMPT_MESSAGES = "ai.prompt.messages";
var AI_USAGE_PROMPT_TOKENS = "ai.usage.promptTokens";
var AI_USAGE_COMPLETION_TOKENS = "ai.usage.completionTokens";
var AI_MODEL_PROVIDER = "ai.model.provider";
var transformAiSdkSpanName = (span) => {
if (span.name in HANDLED_SPAN_NAMES) {
const handler = HANDLED_SPAN_NAMES[span.name];
if (typeof handler === "function") {
span.name = handler(span);
} else if (handler) {
span.name = handler;
}
}
};
var transformResponseText = (attributes) => {
if (AI_RESPONSE_TEXT in attributes) {
attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`] = attributes[AI_RESPONSE_TEXT];
attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`] = "assistant";
delete attributes[AI_RESPONSE_TEXT];
}
};
var transformPromptMessages = (attributes) => {
if (AI_PROMPT_MESSAGES in attributes) {
try {
const messages = JSON.parse(attributes[AI_PROMPT_MESSAGES]);
for (const [index, msg] of messages.entries()) {
const message = msg;
logger.debug("Transforming prompt message", { msg: message, type: typeof message.content });
if (typeof message.content === "string") {
attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = message.content;
} else {
if (Array.isArray(message.content) && message.content.length > 0) {
const lastContent = message.content.at(-1);
if (lastContent?.text) {
attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = lastContent.text;
}
} else {
attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] = JSON.stringify(
message.content
);
}
}
attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.role`] = message.role;
}
delete attributes[AI_PROMPT_MESSAGES];
} catch (error) {
logger.debug("Skipping prompt messages transformation because of JSON parsing error", {
e: error
});
}
}
};
var transformPromptTokens = (attributes) => {
if (AI_USAGE_PROMPT_TOKENS in attributes) {
attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`