@delta-base/observability
Version:
Observability framework for delta-base applications
1,462 lines (1,455 loc) • 65.9 kB
JavaScript
#!/usr/bin/env node
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// package.json
var package_default;
var init_package = __esm({
"package.json"() {
package_default = {
name: "@delta-base/observability",
version: "0.0.2",
description: "Observability framework for delta-base applications",
main: "dist/index.js",
module: "dist/index.mjs",
types: "dist/index.d.ts",
exports: {
".": {
types: "./dist/index.d.ts",
import: "./dist/index.mjs",
require: "./dist/index.js"
},
"./scripts/*": {
require: "./dist/scripts/*.js"
}
},
bin: {
"collect-build-info": "./dist/scripts/collect-build-info.js"
},
files: [
"dist",
"LICENSE",
"README.md"
],
scripts: {
typecheck: "tsc --noEmit",
build: "tsup",
dev: "tsup --watch",
test: "vitest run --passWithNoTests",
"test:watch": "vitest",
prepublishOnly: "pnpm run build"
},
keywords: [
"delta-base",
"observability",
"opentelemetry",
"tracing",
"monitoring",
"logging"
],
author: "Nibbio LLC",
license: "LicenseRef-LICENSE",
repository: {
type: "git",
url: "https://github.com/nibbio-co/delta-base.git",
directory: "packages/libs/deltabase/observability"
},
dependencies: {
"@delta-base/toolkit": "workspace:*",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-trace-base": "^1.30.1",
hono: "catalog:",
zod: "catalog:"
},
devDependencies: {
"@types/node": "^20.0.0",
"@hono/zod-openapi": "catalog:",
tsup: "^8.3.6",
typescript: "^5.0.0",
vitest: "^3.1.1"
},
peerDependencies: {
"@hono/zod-openapi": "^0.17.1"
},
peerDependenciesMeta: {
"@hono/zod-openapi": {
optional: true
}
},
engines: {
node: ">=16.0.0"
},
publishConfig: {
access: "public"
},
sideEffects: false
};
}
});
// src/config.ts
var config_exports = {};
__export(config_exports, {
DEFAULT_CONTEXT_SCHEMA: () => DEFAULT_CONTEXT_SCHEMA,
DEFAULT_ERROR_PATTERNS: () => DEFAULT_ERROR_PATTERNS,
DEFAULT_OBSERVABILITY_CONFIG: () => DEFAULT_OBSERVABILITY_CONFIG,
ObservabilityConfigManager: () => ObservabilityConfigManager,
configureObservability: () => configureObservability,
configureObservabilityAdvanced: () => configureObservabilityAdvanced,
createObservabilityConfig: () => createObservabilityConfig,
observabilityConfig: () => observabilityConfig
});
function detectEnvironment() {
if (process.env.NODE_ENV) {
return process.env.NODE_ENV;
}
if (process.env.CF_ENVIRONMENT) {
return process.env.CF_ENVIRONMENT;
}
if (process.env.ENVIRONMENT) {
return process.env.ENVIRONMENT;
}
if (process.env.NODE_ENV === void 0 && (process.env.DEVELOPMENT || process.env.DEV)) {
return "development";
}
return "development";
}
function createObservabilityConfig(options = {}) {
const config = { ...DEFAULT_OBSERVABILITY_CONFIG };
if (options.serviceName) {
config.tracing.serviceName = options.serviceName;
}
if (options.autoTrackHttp !== void 0) {
config.http.trackRequests = options.autoTrackHttp;
config.http.trackResponses = options.autoTrackHttp;
}
if (options.autoTrackErrorResponses !== void 0) {
config.errors.autoTrackErrorResponses = options.autoTrackErrorResponses;
}
if (options.debug !== void 0) {
config.debug = options.debug;
}
return config;
}
function configureObservability(options) {
const config = createObservabilityConfig(options);
observabilityConfig.updateConfig(config);
}
function configureObservabilityAdvanced(config) {
observabilityConfig.updateConfig(config);
}
var DEFAULT_CONTEXT_SCHEMA, DEFAULT_ERROR_PATTERNS, DEFAULT_OBSERVABILITY_CONFIG, ObservabilityConfigManager, observabilityConfig;
var init_config = __esm({
"src/config.ts"() {
"use strict";
init_package();
DEFAULT_CONTEXT_SCHEMA = {
user: ["id", "email", "first_name", "last_name", "actor_type"],
entity: ["type", "id", "status"],
operation: ["type", "name", "domain", "layer"],
infrastructure: [
"event_store.stream_id",
"event_store.expected_version",
"event_store.next_version",
"event_store.created_new_stream",
"database.operation",
"database.table",
"database.duration_ms",
"external_service.name",
"external_service.operation",
"external_service.duration_ms"
],
error: [
"type",
"message",
"unexpected",
"validation.issue_count",
"validation.fields",
"business_rule.rule",
"business_rule.violation"
],
http: [
"method",
"path",
"status_code",
"user_agent",
"content_type",
"content_length"
],
performance: [
"duration_ms",
"category",
"timing.total_ms",
"timing.domain_ms",
"timing.validation_ms",
"timing.event_store_ms",
"timing.database_ms"
],
service: ["name", "version", "environment", "build_info"]
};
DEFAULT_ERROR_PATTERNS = [
{
name: "OpenAPIHono_ZodValidation",
matcher: (body) => {
var _a, _b;
return (body == null ? void 0 : body.success) === false && ((_a = body == null ? void 0 : body.error) == null ? void 0 : _a.name) === "ZodError" && Array.isArray((_b = body == null ? void 0 : body.error) == null ? void 0 : _b.issues);
},
tracker: (wideEvent, body) => {
wideEvent.trackError({
type: "ValidationError",
message: "Request validation failed",
validation: {
issueCount: body.error.issues.length,
fields: body.error.issues.map(
(issue) => Array.isArray(issue.path) ? issue.path.join(".") : String(issue.path)
)
},
unexpected: false
// Expected - user submitted invalid data
});
}
},
{
name: "BusinessValidation",
matcher: (body) => (body == null ? void 0 : body.success) === false && typeof (body == null ? void 0 : body.message) === "string" && !(body == null ? void 0 : body.error),
// No nested error object
tracker: (wideEvent, body) => {
wideEvent.trackError({
type: "BusinessValidationError",
message: body.message,
businessRule: {
rule: "business_validation",
violation: body.message
},
unexpected: false
// Expected - business rule violation
});
}
},
{
name: "GenericError",
matcher: (body) => (body == null ? void 0 : body.success) === false || (body == null ? void 0 : body.error) || typeof (body == null ? void 0 : body.message) === "string",
tracker: (wideEvent, body) => {
var _a;
wideEvent.trackError({
type: "GenericError",
message: (body == null ? void 0 : body.message) || ((_a = body == null ? void 0 : body.error) == null ? void 0 : _a.message) || "Unknown error",
unexpected: true
// Unexpected - could be system issue
});
}
}
];
DEFAULT_OBSERVABILITY_CONFIG = {
contextSchema: DEFAULT_CONTEXT_SCHEMA,
tracing: {
enabled: true,
serviceName: process.env.SERVICE_NAME || package_default.name,
serviceVersion: process.env.SERVICE_VERSION || package_default.version,
environment: detectEnvironment(),
defaultAttributes: {}
},
http: {
trackRequests: true,
trackResponses: true,
trackBodies: false,
maxBodySize: 1024 * 1024,
// 1MB
excludeHeaders: ["authorization", "cookie", "x-api-key"],
includeHeaders: void 0
},
errors: {
autoTrackErrorResponses: true,
autoCategorizeErrors: true,
errorPatterns: DEFAULT_ERROR_PATTERNS,
defaultUnexpected: true
},
performance: {
trackTiming: true,
categorizePerformance: true,
performanceThresholds: {
fast: 100,
slow: 1e3
},
autoCreateSpans: true
},
debug: false
};
ObservabilityConfigManager = class {
config;
constructor(config = DEFAULT_OBSERVABILITY_CONFIG) {
this.config = { ...config };
}
/**
* Get current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Update configuration
*/
updateConfig(updates) {
this.config = { ...this.config, ...updates };
}
/**
* Add custom context schema fields
*/
addContextFields(category, fields) {
this.config.contextSchema[category] = [
...this.config.contextSchema[category],
...fields
];
}
/**
* Add custom error pattern
*/
addErrorPattern(pattern) {
this.config.errors.errorPatterns.push(pattern);
}
/**
* Remove error pattern by name
*/
removeErrorPattern(name) {
this.config.errors.errorPatterns = this.config.errors.errorPatterns.filter(
(pattern) => pattern.name !== name
);
}
/**
* Get context schema for a category
*/
getContextSchema(category) {
return [...this.config.contextSchema[category]];
}
/**
* Get all context schema fields (flattened)
*/
getAllContextFields() {
return Object.values(this.config.contextSchema).flat();
}
/**
* Get error patterns
*/
getErrorPatterns() {
return [...this.config.errors.errorPatterns];
}
};
observabilityConfig = new ObservabilityConfigManager();
}
});
// src/wide-events.ts
var wide_events_exports = {};
__export(wide_events_exports, {
DEFAULT_BUILD_INFO: () => DEFAULT_BUILD_INFO,
WideEventsWrapper: () => WideEventsWrapper,
createOperationTracker: () => createOperationTracker,
createUserOperationTracker: () => createUserOperationTracker,
getBuildInfo: () => getBuildInfo,
initializeBuildInfo: () => initializeBuildInfo,
setBuildInfo: () => setBuildInfo
});
function setBuildInfo(buildInfo) {
customBuildInfo = buildInfo;
}
function getBuildInfo() {
return { ...DEFAULT_BUILD_INFO, ...customBuildInfo };
}
function initializeBuildInfo(buildInfo) {
setBuildInfo(buildInfo);
}
function createUserOperationTracker(operationName) {
return new WideEventsWrapper(
`User ${operationName}`,
`user.${operationName.toLowerCase()}`
);
}
function createOperationTracker(entity, operation) {
return new WideEventsWrapper(
`${entity} ${operation}`,
`${entity.toLowerCase()}.${operation.toLowerCase()}`
);
}
var import_api, DEFAULT_BUILD_INFO, customBuildInfo, WideEventsWrapper;
var init_wide_events = __esm({
"src/wide-events.ts"() {
"use strict";
import_api = require("@opentelemetry/api");
DEFAULT_BUILD_INFO = {
"service.name": "unknown-service",
"service.version": "0.0.0",
"service.environment": "development"
};
customBuildInfo = {};
WideEventsWrapper = class {
requestId;
rootSpan;
trackedData = {};
hasError = false;
/**
* Creates a new wide events wrapper for an operation.
*
* @param operationName - Human-readable name for the operation (e.g., "Create User")
* @param operationType - Machine-readable operation type (e.g., "user.create")
* @param serviceName - Name of the service (defaults to environment variable)
*/
constructor(operationName, operationType, serviceName = process.env.SERVICE_NAME || "unknown-service") {
this.requestId = crypto.randomUUID();
const buildInfo = getBuildInfo();
const tracer = import_api.trace.getTracer(
serviceName,
buildInfo["service.version"]
);
this.rootSpan = tracer.startSpan(operationName, {
kind: import_api.SpanKind.SERVER,
attributes: {
"request.id": this.requestId,
"operation.type": operationType,
"operation.name": operationName,
...buildInfo
}
});
this.trackedData = {
request_id: this.requestId,
operation: operationType
};
}
/**
* Set the operation type and context for this operation.
*
* This is the primary method for identifying what operation is being performed.
* It sets both the operation type identifier and any contextual metadata.
*
* @param operationType - The operation type identifier (e.g., "user.create", "user.change-password")
* @param context - Optional contextual information about the operation
* @returns This instance for method chaining
*
* @example
* ```typescript
* // Simple operation
* tracker.setOperation('user.create');
*
* // With context
* tracker.setOperation('user.create', {
* domain: 'user-management',
* layer: 'api'
* });
*
* // With custom properties
* tracker.setOperation('user.change-password', {
* domain: 'user-management',
* layer: 'business-logic',
* trigger: 'user-initiated'
* });
* ```
*/
setOperation(operationType, context3) {
const attributes = {
"operation.type": operationType
};
if (context3) {
if (context3.domain) attributes["operation.domain"] = context3.domain;
if (context3.layer) attributes["operation.layer"] = context3.layer;
for (const [key, value] of Object.entries(context3)) {
if (value !== void 0 && !["domain", "layer"].includes(key)) {
attributes[`operation.${key}`] = value;
}
}
}
this.rootSpan.setAttributes(attributes);
Object.assign(this.trackedData, {
operation: operationType,
...attributes
});
return this;
}
/**
* Track user/actor information for this operation.
*
* Use this to record who is performing the operation, including
* user details, authentication context, and actor type.
*
* @param userData - User and actor information
* @returns This instance for method chaining
*
* @example
* ```typescript
* tracker.trackUser({
* id: '123',
* email: 'john@example.com',
* actorType: 'authenticated_user'
* });
* ```
*/
trackUser(userData) {
const attributes = {};
if (userData.id) attributes["user.id"] = userData.id;
if (userData.email) attributes["user.email"] = userData.email;
if (userData.firstName) attributes["user.first_name"] = userData.firstName;
if (userData.lastName) attributes["user.last_name"] = userData.lastName;
if (userData.actorType) attributes["actor.type"] = userData.actorType;
for (const [key, value] of Object.entries(userData)) {
if (value !== void 0 && !["id", "email", "firstName", "lastName", "actorType"].includes(key)) {
attributes[`user.${key}`] = value;
}
}
this.rootSpan.setAttributes(attributes);
Object.assign(this.trackedData, attributes);
return this;
}
/**
* Track the primary entity/resource being operated on.
*
* Use this to record what thing your operation is working with,
* such as a user being created, an order being processed, etc.
*
* @param entityData - Entity/resource information
* @returns This instance for method chaining
*
* @example
* ```typescript
* tracker.trackEntity({
* type: 'user',
* id: 'user-123',
* status: 'active'
* });
* ```
*/
trackEntity(entityData) {
const attributes = {
"entity.type": entityData.type,
"entity.id": entityData.id
};
for (const [key, value] of Object.entries(entityData)) {
if (value !== void 0 && !["type", "id"].includes(key)) {
attributes[`entity.${key}`] = value;
}
}
this.rootSpan.setAttributes(attributes);
Object.assign(this.trackedData, attributes);
return this;
}
/**
* Track infrastructure and technical details.
*
* Use this to record technical aspects of your operation,
* such as database operations, external service calls, etc.
*
* @param infrastructureData - Infrastructure operation details
* @returns This instance for method chaining
*
* @example
* ```typescript
* tracker.trackInfrastructure({
* database: {
* operation: 'INSERT',
* table: 'users',
* duration: 45
* },
* eventStore: {
* streamId: 'user-123',
* expectedVersion: 0
* }
* });
* ```
*/
trackInfrastructure(infrastructureData) {
const attributes = {};
if (infrastructureData.database) {
const db = infrastructureData.database;
if (db.operation) attributes["db.operation"] = db.operation;
if (db.table) attributes["db.table"] = db.table;
if (db.query) attributes["db.query"] = db.query;
if (db.duration) attributes["db.duration_ms"] = db.duration;
}
if (infrastructureData.eventStore) {
const es = infrastructureData.eventStore;
if (es.streamId) attributes["event_store.stream_id"] = es.streamId;
if (es.expectedVersion !== void 0)
attributes["event_store.expected_version"] = es.expectedVersion;
if (es.nextVersion !== void 0)
attributes["event_store.next_version"] = es.nextVersion;
if (es.createdNewStream !== void 0)
attributes["event_store.created_new_stream"] = es.createdNewStream;
}
if (infrastructureData.externalService) {
const ext = infrastructureData.externalService;
if (ext.name) attributes["external_service.name"] = ext.name;
if (ext.operation)
attributes["external_service.operation"] = ext.operation;
if (ext.duration)
attributes["external_service.duration_ms"] = ext.duration;
}
for (const [key, value] of Object.entries(infrastructureData)) {
if (value !== void 0 && !["database", "eventStore", "externalService"].includes(key)) {
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
attributes[`infrastructure.${key}`] = value;
}
}
}
this.rootSpan.setAttributes(attributes);
Object.assign(this.trackedData, attributes);
return this;
}
/**
* Track error information.
*
* Use this to record error details, including error type,
* validation failures, business rule violations, etc.
*
* @param errorData - Error information
* @returns This instance for method chaining
*
* @example
* ```typescript
* tracker.trackError({
* type: 'ValidationError',
* message: 'Email is required',
* validation: {
* issueCount: 2,
* fields: ['email', 'firstName']
* }
* });
* ```
*/
trackError(errorData) {
const attributes = {};
if (errorData.type) attributes["error.type"] = errorData.type;
if (errorData.message) attributes["error.message"] = errorData.message;
if (errorData.unexpected !== void 0)
attributes["error.unexpected"] = errorData.unexpected;
if (errorData.validation) {
const val = errorData.validation;
if (val.issueCount)
attributes["error.validation.issue_count"] = val.issueCount;
if (val.fields)
attributes["error.validation.fields"] = val.fields.join(",");
}
if (errorData.businessRule) {
const rule = errorData.businessRule;
if (rule.rule) attributes["error.business_rule.rule"] = rule.rule;
if (rule.violation)
attributes["error.business_rule.violation"] = rule.violation;
}
for (const [key, value] of Object.entries(errorData)) {
if (value !== void 0 && ![
"type",
"message",
"unexpected",
"validation",
"businessRule"
].includes(key)) {
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
attributes[`error.${key}`] = value;
}
}
}
this.rootSpan.setAttributes(attributes);
Object.assign(this.trackedData, attributes);
this.rootSpan.setStatus({
code: import_api.SpanStatusCode.ERROR,
message: errorData.message || "Operation failed"
});
this.hasError = true;
return this;
}
/**
* Record a business event during the operation.
*
* Use this to record significant business events that occur
* during your operation, such as "validation.completed" or
* "user.created".
*
* @param eventName - Name of the business event
* @param eventData - Additional event data
* @returns This instance for method chaining
*
* @example
* ```typescript
* tracker.recordEvent('user.created', {
* userId: '123',
* source: 'api'
* });
* ```
*/
recordEvent(eventName, eventData = {}) {
this.rootSpan.addEvent(eventName, eventData);
return this;
}
/**
* Create a child span for a sub-operation.
*
* Use this to create detailed spans for specific parts of your
* operation, such as validation, database operations, etc.
*
* @param name - Name of the sub-operation
* @param attributes - Additional span attributes
* @returns OpenTelemetry span for the sub-operation
*
* @example
* ```typescript
* const validationSpan = tracker.createOperationSpan('validation', {
* 'operation.layer': 'validation'
* });
*
* try {
* // validation logic
* validationSpan.setStatus({ code: SpanStatusCode.OK });
* } catch (error) {
* validationSpan.recordException(error);
* } finally {
* validationSpan.end();
* }
* ```
*/
createOperationSpan(name, attributes = {}) {
const buildInfo = getBuildInfo();
const tracer = import_api.trace.getTracer(
process.env.SERVICE_NAME || "unknown-service",
buildInfo["service.version"]
);
return tracer.startSpan(
name,
{
kind: import_api.SpanKind.INTERNAL,
attributes
},
import_api.trace.setSpan(import_api.context.active(), this.rootSpan)
);
}
/**
* Automatically track HTTP request details.
*
* This method extracts and tracks HTTP-specific information
* from the request, including method, URL, headers, etc.
*
* @param request - HTTP request object
* @returns This instance for method chaining
*
* @internal This method is typically called by middleware
*/
trackHttpRequest(request) {
const url = new URL(request.url);
this.rootSpan.setAttributes({
"http.method": request.method,
"http.url": request.url,
"http.scheme": url.protocol.slice(0, -1),
"http.host": url.host,
"http.target": url.pathname + url.search,
"http.user_agent": request.headers.get("user-agent") || "",
"http.request_content_length": request.headers.get("content-length") || ""
});
this.rootSpan.addEvent("http.request_received", {
"http.method": request.method,
"http.path": url.pathname
});
return this;
}
/**
* Automatically track HTTP response details.
*
* This method extracts and tracks HTTP response information,
* including status code, headers, and sets appropriate span status.
*
* @param response - HTTP response object
* @returns This instance for method chaining
*
* @internal This method is typically called by middleware
*/
trackHttpResponse(response) {
this.rootSpan.setAttributes({
"http.status_code": response.status,
"http.status_text": response.statusText,
"http.response_content_length": response.headers.get("content-length") || ""
});
this.trackedData["http.status_code"] = response.status;
if (!this.hasError) {
if (response.status >= 400) {
this.rootSpan.setStatus({
code: import_api.SpanStatusCode.ERROR,
message: `HTTP ${response.status}`
});
} else {
this.rootSpan.setStatus({ code: import_api.SpanStatusCode.OK });
}
} else {
if (response.status >= 200 && response.status < 300) {
this.rootSpan.setStatus({ code: import_api.SpanStatusCode.OK });
this.hasError = false;
}
}
this.rootSpan.addEvent("http.response_sent", {
"http.status_code": response.status,
success: response.status >= 200 && response.status < 300
});
return this;
}
/**
* Execute the operation with proper OpenTelemetry context.
*
* This method wraps your operation in the appropriate OpenTelemetry
* context and ensures spans are properly managed and exported.
*
* @param fn - Operation function to execute
* @returns Promise with the operation result
*
* @example
* ```typescript
* const result = await tracker.execute(async (t) => {
* t.trackUser({ id: '123' });
* // Your operation logic here
* return { success: true };
* });
* ```
*/
async execute(fn) {
return import_api.context.with(
import_api.trace.setSpan(import_api.context.active(), this.rootSpan),
async () => {
try {
const result = await fn(this);
if (!this.trackedData["http.status_code"]) {
this.rootSpan.setStatus({ code: import_api.SpanStatusCode.OK });
}
return result;
} catch (error) {
if (error instanceof Error) {
this.rootSpan.recordException(error);
this.rootSpan.setStatus({
code: import_api.SpanStatusCode.ERROR,
message: error.message
});
}
throw error;
} finally {
this.rootSpan.end();
}
}
);
}
/**
* Get all tracked data for this operation.
*
* @returns Copy of all tracked data
*
* @internal Primarily used for testing and debugging
*/
getTrackedData() {
return { ...this.trackedData };
}
};
}
});
// src/index.ts
var index_exports = {};
__export(index_exports, {
DEFAULT_BUILD_INFO: () => DEFAULT_BUILD_INFO,
DEFAULT_CONTEXT_SCHEMA: () => DEFAULT_CONTEXT_SCHEMA,
DEFAULT_ERROR_PATTERNS: () => DEFAULT_ERROR_PATTERNS,
DEFAULT_EXPECTED_ERROR_GUARDS: () => DEFAULT_EXPECTED_ERROR_GUARDS,
DEFAULT_OBSERVABILITY_CONFIG: () => DEFAULT_OBSERVABILITY_CONFIG,
ObservabilityConfigManager: () => ObservabilityConfigManager,
WideEventsWrapper: () => WideEventsWrapper,
addContextFields: () => addContextFields,
addErrorPattern: () => addErrorPattern,
categorizeError: () => categorizeError,
configureObservability: () => configureObservability,
configureObservabilityAdvanced: () => configureObservabilityAdvanced,
createErrorGuard: () => createErrorGuard,
createObservabilityConfig: () => createObservabilityConfig,
createObservabilityErrorHandler: () => createObservabilityErrorHandler,
createObservabilityMiddleware: () => createObservabilityMiddleware,
createOperationTracker: () => createOperationTracker,
createSimpleTracker: () => createSimpleTracker,
createSpan: () => createSpan,
createUserOperationTracker: () => createUserOperationTracker,
default: () => index_default,
extractUserContext: () => extractUserContext,
getBuildInfo: () => getBuildInfo,
handleAndTrackError: () => handleAndTrackError,
initializeBuildInfo: () => initializeBuildInfo,
initializeTracing: () => initializeTracing,
isExpectedError: () => isExpectedError,
observabilityConfig: () => observabilityConfig,
observabilityMiddleware: () => observabilityMiddleware,
quickStart: () => quickStart,
setBuildInfo: () => setBuildInfo,
trackOperationTiming: () => trackOperationTiming,
trackUserFromSources: () => trackUserFromSources,
withDatabaseTracking: () => withDatabaseTracking,
withDomainTracking: () => withDomainTracking,
withEventStoreTracking: () => withEventStoreTracking,
withExternalServiceTracking: () => withExternalServiceTracking,
withSpan: () => withSpan,
withValidationTracking: () => withValidationTracking
});
module.exports = __toCommonJS(index_exports);
init_config();
// src/middleware.ts
var import_toolkit = require("@delta-base/toolkit");
var import_factory = require("hono/factory");
var import_http_exception = require("hono/http-exception");
var import_zod = require("zod");
init_config();
// src/tracing.ts
var import_api2 = require("@opentelemetry/api");
var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
init_wide_events();
var WideEventExporter = class {
// Store child spans until their parent is exported
childSpanCache = /* @__PURE__ */ new Map();
// biome-ignore lint/suspicious/noExplicitAny: do not know the type of the result
export(spans, resultCallback) {
spans.forEach((span) => {
var _a;
if (span.parentSpanId) {
const parentId = span.parentSpanId;
if (!this.childSpanCache.has(parentId)) {
this.childSpanCache.set(parentId, []);
}
(_a = this.childSpanCache.get(parentId)) == null ? void 0 : _a.push(span);
} else {
const spanId = span.spanContext().spanId;
const childSpans = this.childSpanCache.get(spanId) || [];
const allSpans = [span, ...childSpans];
const wideEvent = this.spanToWideEvent(span, allSpans);
const success = this.isSuccessfulSpan(span);
const logEntry = {
level: success ? "info" : "error",
summary: this.generateSummary(span, wideEvent),
performance_category: this.categorizePerformance(
wideEvent["timing.total_ms"]
),
error_category: success ? null : this.categorizeError(span, wideEvent),
...wideEvent
};
if (success) {
console.log(logEntry);
} else {
console.error(logEntry);
}
this.childSpanCache.delete(spanId);
}
});
resultCallback({
code: 0
/* SUCCESS */
});
}
shutdown() {
return Promise.resolve();
}
isSuccessfulSpan(span) {
const httpStatus = span.attributes["http.status_code"];
if (httpStatus) {
return httpStatus >= 200 && httpStatus < 400;
}
return span.status.code !== import_api2.SpanStatusCode.ERROR;
}
spanToWideEvent(rootSpan, allSpans) {
const attributes = rootSpan.attributes || {};
const childSpans = allSpans.filter(
(span) => span.parentSpanId === rootSpan.spanContext().spanId
);
const timing = this.extractLegacyTiming(childSpans);
const totalDurationMs = this.calculateDuration(rootSpan);
const httpContext = this.extractHttpContext(attributes);
const businessContext = this.extractBusinessContext(attributes);
const traceContext = this.extractTraceContext(rootSpan);
return {
// Request and operation context
request_id: attributes["request.id"],
timestamp: new Date(
rootSpan.startTime[0] * 1e3 + rootSpan.startTime[1] / 1e6
).toISOString(),
operation: attributes["operation.type"],
success: this.isSuccessfulSpan(rootSpan),
// HTTP context
...httpContext,
// Trace context
...traceContext,
// Business context (user, resource, domain-specific data)
...businessContext,
// Legacy timing for backward compatibility
...timing,
"timing.total_ms": totalDurationMs,
// Comprehensive span information
spans: this.formatSpansHierarchy(rootSpan, allSpans),
// Build information
...getBuildInfo(),
// Error context if applicable
...rootSpan.status.code === import_api2.SpanStatusCode.ERROR && {
error: this.extractDetailedError(attributes, rootSpan)
}
};
}
// biome-ignore lint/suspicious/noExplicitAny: do not know the type of the result
extractHttpContext(attributes) {
const httpKeys = [
"http.method",
"http.url",
"http.scheme",
"http.host",
"http.target",
"http.status_code",
"http.status_text",
"http.user_agent",
"http.request_content_length",
"http.response_content_length"
];
const httpContext = {};
httpKeys.forEach((key) => {
if (attributes[key] !== void 0) {
httpContext[key] = attributes[key];
}
});
if (attributes["http.target"]) {
httpContext["http.path"] = new URL(
attributes["http.target"],
"http://localhost"
).pathname;
}
return httpContext;
}
// biome-ignore lint/suspicious/noExplicitAny: do not know the type of the result
extractBusinessContext(attributes) {
const { observabilityConfig: observabilityConfig2 } = (init_config(), __toCommonJS(config_exports));
const businessKeys = observabilityConfig2.getAllContextFields();
const businessContext = {};
businessKeys.forEach((key) => {
if (attributes[key] !== void 0) {
businessContext[key] = attributes[key];
}
});
return businessContext;
}
extractTraceContext(span) {
return {
"trace.trace_id": span.spanContext().traceId,
"trace.span_id": span.spanContext().spanId,
...span.parentSpanId && { "trace.parent_span_id": span.parentSpanId }
};
}
extractLegacyTiming(childSpans) {
const timing = {};
childSpans.forEach((span) => {
const spanType = span.attributes["operation.layer"];
if (spanType) {
const duration = this.calculateDuration(span);
timing[`timing.${spanType}_ms`] = duration;
}
});
return timing;
}
calculateDuration(span) {
if (!span.endTime) return 0;
const startMs = span.startTime[0] * 1e3 + span.startTime[1] / 1e6;
const endMs = span.endTime[0] * 1e3 + span.endTime[1] / 1e6;
return Math.round((endMs - startMs) * 100) / 100;
}
formatSpansHierarchy(_rootSpan, allSpans) {
return allSpans.map((span) => ({
name: span.name,
span_id: span.spanContext().spanId,
parent_span_id: span.parentSpanId || void 0,
start_time: new Date(
span.startTime[0] * 1e3 + span.startTime[1] / 1e6
).toISOString(),
end_time: span.endTime ? new Date(
span.endTime[0] * 1e3 + span.endTime[1] / 1e6
).toISOString() : void 0,
duration_ms: this.calculateDuration(span),
status: span.status.code === import_api2.SpanStatusCode.ERROR ? "error" : "ok",
attributes: { ...span.attributes },
events: span.events.map((event) => ({
timestamp: new Date(
event.time[0] * 1e3 + event.time[1] / 1e6
).toISOString(),
name: event.name,
attributes: event.attributes || {}
}))
}));
}
// biome-ignore lint/suspicious/noExplicitAny: do not know the type of the result
generateSummary(span, wideEvent) {
const summaryParts = [];
if (wideEvent.request_id) {
summaryParts.push(`Request ID: ${wideEvent.request_id}`);
}
if (wideEvent.operation) {
summaryParts.push(`Operation: ${wideEvent.operation}`);
}
if (wideEvent["timing.total_ms"]) {
summaryParts.push(`Duration: ${wideEvent["timing.total_ms"]}ms`);
}
if (span.status.code === import_api2.SpanStatusCode.ERROR) {
summaryParts.push(`Error: ${span.status.message || "Unknown error"}`);
}
return summaryParts.join(" - ");
}
categorizePerformance(durationMs) {
if (durationMs < 100) {
return "fast";
}
if (durationMs < 1e3) {
return "moderate";
}
return "slow";
}
extractDetailedError(attributes, span) {
if (attributes["error.type"]) {
const error = {
type: attributes["error.type"],
message: attributes["error.message"] || span.status.message || "Unknown error"
};
if (attributes["error.validation.issue_count"]) {
error.validation = {
issueCount: attributes["error.validation.issue_count"],
fields: attributes["error.validation.fields"] ? attributes["error.validation.fields"].split(",") : []
};
}
if (attributes["error.business_rule.rule"]) {
error.businessRule = {
rule: attributes["error.business_rule.rule"],
violation: attributes["error.business_rule.violation"]
};
}
if (attributes["error.unexpected"] !== void 0) {
error.unexpected = attributes["error.unexpected"];
}
return error;
}
return {
type: "SpanError",
message: span.status.message || "Unknown error"
};
}
// biome-ignore lint/suspicious/noExplicitAny: do not know the type of the result
categorizeError(span, wideEvent) {
if (span.status.code === import_api2.SpanStatusCode.ERROR) {
if (wideEvent["error.validation.issue_count"]) {
return "validation_error";
}
if (wideEvent["error.business_rule.violation"] === "already_exists") {
return "already_exists";
}
if (wideEvent["error.unexpected"]) {
return "unexpected_error";
}
return "application_error";
}
return null;
}
};
var tracerProvider = null;
function initializeTracing() {
if (tracerProvider) {
return;
}
console.log(
"OpenTelemetry tracing initialized with build info:",
getBuildInfo()["service.name"],
getBuildInfo()["git.commit_hash_short"]
);
const wideEventExporter = new WideEventExporter();
const spanProcessor = new import_sdk_trace_base.SimpleSpanProcessor(wideEventExporter);
tracerProvider = new import_sdk_trace_base.BasicTracerProvider({
spanProcessors: [spanProcessor]
});
tracerProvider.register();
console.log("Wide Events exporter configured and ready");
}
function createSpan(options) {
const tracer = import_api2.trace.getTracer(
"delta-base-auth",
String(getBuildInfo()["service.version"])
);
const span = options.parentSpan ? tracer.startSpan(
options.name,
{
kind: options.kind || import_api2.SpanKind.INTERNAL,
attributes: options.attributes
},
import_api2.trace.setSpan(import_api2.context.active(), options.parentSpan)
) : tracer.startSpan(options.name, {
kind: options.kind || import_api2.SpanKind.INTERNAL,
attributes: options.attributes
});
return span;
}
function withSpan(span, fn) {
return import_api2.context.with(import_api2.trace.setSpan(import_api2.context.active(), span), async () => {
try {
const result = await fn(span);
span.setStatus({ code: import_api2.SpanStatusCode.OK });
return result;
} catch (error) {
span.setStatus({
code: import_api2.SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : String(error)
});
span.recordException(
error instanceof Error ? error : new Error(String(error))
);
throw error;
} finally {
span.end();
}
});
}
// src/middleware.ts
init_wide_events();
function observabilityMiddleware(options = {}) {
if (options.buildInfo) {
initializeBuildInfo(options.buildInfo);
}
if (Object.keys(options).length > 0) {
const config2 = createObservabilityConfig(options);
observabilityConfig.updateConfig(config2);
}
const config = observabilityConfig.getConfig();
const serviceName = config.tracing.serviceName || "unknown-service";
const autoTrackHttp = config.http.trackRequests && config.http.trackResponses;
const autoTrackErrorResponses = config.errors.autoTrackErrorResponses;
return (0, import_factory.createMiddleware)(async (c, next) => {
initializeTracing();
const method = c.req.method;
const path = c.req.path;
const operationName = `${method} ${path}`;
const operationType = simpleOperationTypeDerivation(method, path);
const wideEvent = new WideEventsWrapper(
operationName,
operationType,
serviceName
);
if (autoTrackHttp) {
wideEvent.trackHttpRequest(c.req.raw);
}
c.set("wideEvent", wideEvent);
await wideEvent.execute(async () => {
await next();
if (autoTrackHttp && c.res) {
wideEvent.trackHttpResponse(c.res);
}
if (autoTrackErrorResponses && c.res && isErrorResponse(c.res.status)) {
await detectAndTrackErrorResponse(wideEvent, c.res);
}
});
});
}
function simpleOperationTypeDerivation(method, path) {
const resourceMatch = path.match(/^\/([^/?]+)/);
let resource = (resourceMatch == null ? void 0 : resourceMatch[1]) || "unknown";
if (resource.endsWith("s") && resource.length > 1) {
resource = resource.slice(0, -1);
}
const methodMap = {
GET: "read",
POST: "create",
PUT: "update",
PATCH: "update",
DELETE: "delete"
};
const action = methodMap[method.toUpperCase()] || "operation";
return `${resource}.${action}`;
}
function isErrorResponse(status) {
return status >= 400;
}
async function detectAndTrackErrorResponse(wideEvent, response) {
try {
const contentType = response.headers.get("content-type");
if (!(contentType == null ? void 0 : contentType.includes("application/json"))) {
return;
}
const clonedResponse = response.clone();
const responseBody = await clonedResponse.json();
const config = observabilityConfig.getConfig();
for (const pattern of config.errors.errorPatterns) {
if (pattern.matcher(responseBody)) {
console.log(`Detected ${pattern.name} error response, tracking...`);
pattern.tracker(wideEvent, responseBody);
return;
}
}
const isClientError = response.status >= 400 && response.status < 500;
wideEvent.trackError({
type: "UnknownError",
message: `HTTP ${response.status}`,
httpStatus: response.status,
unexpected: !isClientError
// 4xx = expected (client error), 5xx = unexpected (server error)
});
} catch (error) {
console.log("Failed to parse response body for error detection:", error);
}
}
function createObservabilityErrorHandler(options = {}) {
if (Object.keys(options).length > 0) {
const config2 = createObservabilityConfig(options);
observabilityConfig.updateConfig(config2);
}
const config = observabilityConfig.getConfig();
const serviceName = config.tracing.serviceName || "unknown-service";
return (error, c) => {
const wideEvent = c.get("wideEvent");
if (wideEvent) {
trackErrorWithContext(wideEvent, error);
} else {
const method = c.req.method;
const path = c.req.path;
const operationName = `${method} ${path}`;
const operationType = simpleOperationTypeDerivation(method, path);
const errorTracker = new WideEventsWrapper(
operationName,
operationType,
serviceName
);
trackErrorWithContext(errorTracker, error);
errorTracker.execute(async () => {
}).catch(console.error);
}
return createErrorResponse(error, c);
};
}
function trackErrorWithContext(wideEvent, error) {
var _a, _b, _c, _d, _e;
if (error instanceof import_zod.ZodError) {
wideEvent.trackError({
type: "ValidationError",
message: "Request validation failed",
validation: {
issueCount: error.issues.length,
fields: error.issues.map((issue) => issue.path.join("."))
},
unexpected: false
// Expected - user submitted invalid data
});
} else if (error instanceof import_toolkit.ValidationError) {
wideEvent.trackError({
type: "BusinessValidationError",
message: error.message,
businessRule: {
rule: "business_validation",
violation: error.message
},
unexpected: false
// Expected - business rule violation
});
} else if (error instanceof import_toolkit.IllegalStateError) {
const alreadyExists = error.message.includes("already exists");
wideEvent.trackError({
type: "BusinessRuleViolation",
message: error.message,
businessRule: {
rule: "illegal_state",
violation: alreadyExists ? "already_exists" : "illegal_state"
},
unexpected: false
// Expected - business constraint violation
});
} else if (error instanceof import_http_exception.HTTPException) {
const isClientError = error.status >= 400 && error.status < 500;
wideEvent.trackError({
type: "HTTPException",
message: error.message,
httpStatus: error.status,
unexpected: !isClientError
// 4xx = expected (client error), 5xx = unexpected (server error)
});
} else if ((0, import_toolkit.isDeltaBaseError)(error)) {
const isServerError = error.status >= 500;
const unexpected = isServerError || ["INTERNAL_SERVER_ERROR", "STORAGE_ERROR", "TIMEOUT_ERROR"].includes(
error.errorType
);
const errorData = {
type: error.errorType,
message: error.message,
httpStatus: error.status,
unexpected
};
if (error.details && Object.keys(error.details).length > 0) {
errorData.details = error.details;
}
if (error.errorType === "Version conflict" || error.errorType === "STREAM_VERSION_CONFLICT") {
errorData.concurrency = {
type: "version_conflict",
details: error.details
};
} else if (error.errorType === "AUTHENTICATION_FAILED" || error.errorType === "AUTHORIZATION_FAILED") {
errorData.security = {
type: error.errorType.toLowerCase(),
details: error.details
};
} else if (error.errorType === "RATE_LIMIT_EXCEEDED") {
errorData.rateLimit = {
retryAfter: (_a = error.details) == null ? void 0 : _a.retryAfter
};
} else if (error.errorType === "NOT_FOUND" || error.errorType.endsWith("_NOT_FOUND")) {
errorData.resource = {
type: ((_b = error.details) == null ? void 0 : _b.resourceType) || "unknown",
id: ((_c = error.details) == null ? void 0 : _c.resourceId) || ((_d = error.details) == null ? void 0 : _d.eventStoreId) || ((_e = error.details) == null ? void 0 : _e.subscriptionId)
};
}
wideEvent.trackError(errorData);
return;
} else {
wideEvent.trackError({
type: "UnknownError",
message: error.message,
unexpected: true
});
}
}
function createErrorResponse(error, c) {
if (error instanceof import_zod.ZodError) {
return c.json(
{
success: false,
error: {
issues: error.issues,
name: error.name
}
},
400
);
}
if (error instanceof import_toolkit.ValidationError) {
return c.json({ success: false, message: error.message }, 400);
}
if (error instanceof import_toolkit.IllegalStateError) {
const alreadyExists = error.message.includes("already exists");
const status = alreadyExists ? 409 : 500;
return c.json({ success: false, message: error.message }, status);
}
if (error instanceof import_http_exception.HTTPException) {
return c.json(
{
success: false,
message: error.message
},
error.status
);
}
if ((0, import_toolkit.isDeltaBaseError)(error)) {
const responseBody = {
success: false,
error: {
type: error.errorType,
message: