From 6f4c648c2f2ac9409eed46d55a428ec91d844a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 30 Mar 2026 10:23:16 -0300 Subject: [PATCH 01/10] feat(daemon): add OpenTelemetry distributed tracing Implements RFC 0001-opentelemetry-observability for the daemon package. - SDK init in tracing.ts with BatchSpanProcessor and OTLP HTTP exporter - Auto-instrumentation for mysql2, http, and aws-sdk (fs/dns disabled) - Manual spans on all critical event handlers via withSpan() helper - Root event.processing.* spans via tracedService() wrapper in SyncMachine - Winston log correlation: trace_id and span_id injected into log output - Tracing is a no-op when OTEL_EXPORTER_OTLP_ENDPOINT is not set - Graceful shutdown on SIGTERM with span flush - Local dev setup: docker-compose.dev.yml with MySQL + Jaeger Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/daemon/docker-compose.dev.yml | 27 + packages/daemon/env.development | 33 + packages/daemon/package.json | 11 + packages/daemon/src/index.ts | 6 +- packages/daemon/src/logger.ts | 7 +- packages/daemon/src/machines/SyncMachine.ts | 46 +- packages/daemon/src/services/index.ts | 216 ++- packages/daemon/src/tracing.ts | 53 + yarn.lock | 1611 ++++++++++++++++++- 9 files changed, 1870 insertions(+), 140 deletions(-) create mode 100644 packages/daemon/docker-compose.dev.yml create mode 100644 packages/daemon/env.development create mode 100644 packages/daemon/src/tracing.ts diff --git a/packages/daemon/docker-compose.dev.yml b/packages/daemon/docker-compose.dev.yml new file mode 100644 index 00000000..1f802867 --- /dev/null +++ b/packages/daemon/docker-compose.dev.yml @@ -0,0 +1,27 @@ +services: + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: hathor + MYSQL_DATABASE: wallet_service + MYSQL_USER: hathor + MYSQL_PASSWORD: hathor + ports: + - "3306:3306" + volumes: + - daemon-mysql-data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 5s + timeout: 3s + retries: 10 + start_period: 15s + + jaeger: + image: jaegertracing/jaeger:latest + ports: + - "16686:16686" + - "4318:4318" + +volumes: + daemon-mysql-data: diff --git a/packages/daemon/env.development b/packages/daemon/env.development new file mode 100644 index 00000000..58a2a2dd --- /dev/null +++ b/packages/daemon/env.development @@ -0,0 +1,33 @@ +# Fullnode connection (remote) +# Point to any fullnode with the event WebSocket API enabled. +# USE_SSL=true for wss:// and https://, false for ws:// and http:// +export FULLNODE_HOST=wallet-service.private-nodes.hathor.network +export USE_SSL=true +export NETWORK=mainnet +export FULLNODE_NETWORK=mainnet + +# These are fetched from the fullnode on first connect. +# Run: yarn dev:fetch-ids +# Then copy STREAM_ID and FULLNODE_PEER_ID from export-identifiers.sh +export FULLNODE_PEER_ID=d9ace4305193d76b9d5ec83831d69d31fa7fbcd43b05a448b73269d21c5cc04f +export STREAM_ID=c373e689-0b37-4df6-accf-829bd5c2013e + +# Local MySQL (from docker-compose.dev.yml) +export DB_ENDPOINT=localhost +export DB_NAME=wallet_service +export DB_USER=hathor +export DB_PASS=hathor +export DB_PORT=3306 + +# AWS stubs (not used locally, but required by config validation) +export NEW_TX_SQS=local-stub +export PUSH_NOTIFICATION_ENABLED=false +export WALLET_SERVICE_LAMBDA_ENDPOINT=local-stub +export STAGE=local +export ACCOUNT_ID=000000000000 +export ALERT_MANAGER_TOPIC=local-stub +export ALERT_MANAGER_REGION=us-east-1 +export APPLICATION_NAME=wallet-service-daemon + +# OpenTelemetry (optional — remove to disable tracing) +export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 diff --git a/packages/daemon/package.json b/packages/daemon/package.json index 468cafae..e3e4068c 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -13,6 +13,11 @@ "lint": "eslint .", "build": "tsc -b", "start": "node dist/index.js", + "dev:up": "docker compose -f docker-compose.dev.yml up -d", + "dev:down": "docker compose -f docker-compose.dev.yml down", + "dev:destroy": "docker compose -f docker-compose.dev.yml down -v", + "dev:migrate": "cd ../.. && DB_ENDPOINT=localhost DB_NAME=wallet_service DB_USER=hathor DB_PASS=hathor DB_PORT=3306 npx sequelize-cli db:migrate && DB_ENDPOINT=localhost DB_NAME=wallet_service DB_USER=hathor DB_PASS=hathor DB_PORT=3306 npx sequelize-cli db:seed:all", + "dev:fetch-ids": "FULLNODE_WEBSOCKET_BASEURL=${FULLNODE_HOST:-hathor-dynasty.network} node ../../scripts/fetch-fullnode-ids.js", "replay-balance": "yarn dlx ts-node src/scripts/replay-balance.ts", "watch": "tsc -w", "test_images_up": "docker compose -f ./__tests__/integration/scripts/docker-compose.yml up -d", @@ -55,6 +60,12 @@ "dependencies": { "@aws-sdk/client-lambda": "3.540.0", "@aws-sdk/client-sqs": "3.540.0", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/auto-instrumentations-node": "0.56.1", + "@opentelemetry/exporter-trace-otlp-http": "0.57.2", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-node": "0.57.2", + "@opentelemetry/sdk-trace-node": "1.30.1", "assert": "2.1.0", "aws-sdk": "2.1454.0", "axios": "1.6.2", diff --git a/packages/daemon/src/index.ts b/packages/daemon/src/index.ts index fc9ffc4f..41edf4e1 100644 --- a/packages/daemon/src/index.ts +++ b/packages/daemon/src/index.ts @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +// Must be imported before all other modules to patch libraries for auto-instrumentation +import './tracing'; + import { interpret } from 'xstate'; import { SyncMachine } from './machines'; import logger from './logger'; @@ -17,7 +20,8 @@ const main = async () => { const machine = interpret(SyncMachine); machine.onTransition((state) => { - logger.info(`Transitioned to ${bigIntUtils.JSONBigInt.stringify(state.value)}`); + const stateValue = bigIntUtils.JSONBigInt.stringify(state.value); + logger.info(`Transitioned to ${stateValue}`); }); machine.onDone(() => { diff --git a/packages/daemon/src/logger.ts b/packages/daemon/src/logger.ts index 41d53f9a..b24ce609 100644 --- a/packages/daemon/src/logger.ts +++ b/packages/daemon/src/logger.ts @@ -14,9 +14,10 @@ export default createLogger({ format: format.combine( format.colorize(), format.timestamp(), - format.printf(({ timestamp, level, message }) => ( - `${timestamp} [${SERVICE_NAME}][${level}]: ${message}` - )), + format.printf(({ timestamp, level, message, trace_id, span_id }) => { + const traceInfo = trace_id ? ` [trace_id=${trace_id} span_id=${span_id}]` : ''; + return `${timestamp} [${SERVICE_NAME}][${level}]${traceInfo}: ${message}`; + }), ), transports: [ new transports.Console(), diff --git a/packages/daemon/src/machines/SyncMachine.ts b/packages/daemon/src/machines/SyncMachine.ts index f943dec0..acd88e39 100644 --- a/packages/daemon/src/machines/SyncMachine.ts +++ b/packages/daemon/src/machines/SyncMachine.ts @@ -10,6 +10,7 @@ import { assign, spawn, } from 'xstate'; +import { trace, SpanStatusCode } from '@opentelemetry/api'; import { LRU } from '../utils'; import { WebSocketActor, HealthCheckActor, MonitoringActor } from '../actors'; import { @@ -30,6 +31,33 @@ import { handleTokenCreated, checkForMissedEvents, } from '../services'; + +const tracer = trace.getTracer('wallet-service-daemon'); + +/** + * Wraps an xstate service function with a root OTel span, so the span context + * is active for the entire event processing lifecycle (including child spans + * created inside the service). + */ +function tracedService( + name: string, + fn: (...args: any[]) => Promise, +): (...args: any[]) => Promise { + return (...args: any[]) => { + return tracer.startActiveSpan(`event.processing.${name}`, async (span) => { + try { + const result = await fn(...args); + return result; + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + throw e; + } finally { + span.end(); + } + }); + }; +} import { hasNextChange, metadataChanged, @@ -392,16 +420,16 @@ export const SyncMachine = Machine({ }, }, { services: { - handleVertexAccepted, - handleVertexRemoved, - handleVoidedTx, - handleTxFirstBlock, - handleNcExecVoided, - handleUnvoidedTx, - handleReorgStarted, - handleTokenCreated, + handleVertexAccepted: tracedService('handleVertexAccepted', handleVertexAccepted), + handleVertexRemoved: tracedService('handleVertexRemoved', handleVertexRemoved), + handleVoidedTx: tracedService('handleVoidedTx', handleVoidedTx), + handleTxFirstBlock: tracedService('handleTxFirstBlock', handleTxFirstBlock), + handleNcExecVoided: tracedService('handleNcExecVoided', handleNcExecVoided), + handleUnvoidedTx: tracedService('handleUnvoidedTx', handleUnvoidedTx), + handleReorgStarted: tracedService('handleReorgStarted', handleReorgStarted), + handleTokenCreated: tracedService('handleTokenCreated', handleTokenCreated), fetchInitialState, - metadataDiff, + metadataDiff: tracedService('metadataDiff', metadataDiff), updateLastSyncedEvent, checkForMissedEvents, }, diff --git a/packages/daemon/src/services/index.ts b/packages/daemon/src/services/index.ts index 1768c93c..48ded184 100644 --- a/packages/daemon/src/services/index.ts +++ b/packages/daemon/src/services/index.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import { trace, SpanStatusCode } from '@opentelemetry/api'; import hathorLib from '@hathor/wallet-lib'; import { Connection as MysqlConnection } from 'mysql2/promise'; import axios from 'axios'; @@ -91,6 +92,22 @@ import { invokeOnTxPushNotificationRequestedLambda, getDaemonUptime, retryWithBa import { addAlert, Severity } from '@wallet-service/common'; import { JSONBigInt } from '@hathor/wallet-lib/lib/utils/bigint'; +const tracer = trace.getTracer('wallet-service-daemon'); + +async function withSpan(name: string, fn: () => Promise): Promise { + return tracer.startActiveSpan(name, async (s) => { + try { + return await fn(); + } catch (e) { + s.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + s.recordException(e as Error); + throw e; + } finally { + s.end(); + } + }); +} + export const METADATA_DIFF_EVENT_TYPES = { IGNORE: 'IGNORE', TX_VOIDED: 'TX_VOIDED', @@ -104,12 +121,15 @@ export const METADATA_DIFF_EVENT_TYPES = { const DUPLICATE_TX_ALERT_GRACE_PERIOD = 10; // seconds export const metadataDiff = async (_context: Context, event: Event) => { + return tracer.startActiveSpan('metadataDiff', async (span) => { const fullNodeEvent = (event as Extract).event as StandardFullNodeEvent; const { hash, metadata: { voided_by, first_block, nc_execution }, } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); + const isRetryableError = (error: any): boolean => { const code = error?.code; return code === 'ETIMEDOUT' @@ -121,10 +141,10 @@ export const metadataDiff = async (_context: Context, event: Event) => { try { return await retryWithBackoff( async () => { - let mysql; + let mysql: MysqlConnection | undefined; try { mysql = await getDbConnection(); - const dbTx: DbTransaction | null = await getTransactionById(mysql, hash); + const dbTx: DbTransaction | null = await withSpan('getTransactionById', () => getTransactionById(mysql!, hash)); if (!dbTx) { if (voided_by.length > 0) { @@ -213,9 +233,14 @@ export const metadataDiff = async (_context: Context, event: Event) => { }, ); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.error('metadataDiff error', { eventId: fullNodeEvent.event.id, error: e }); return Promise.reject(e); + } finally { + span.end(); } + }); }; export const isBlock = (version: number): boolean => version === hathorLib.constants.BLOCK_VERSION @@ -272,6 +297,7 @@ export function isNanoContract(headers: EventTxHeader[]) { * @param _event - The event being processed (unused, context.event is used instead) */ export const handleVertexAccepted = async (context: Context, _event: Event) => { + return tracer.startActiveSpan('handleVertexAccepted', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); const { @@ -308,15 +334,20 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { headers = [], } = fullNodeData; + span.setAttribute('tx.hash', hash); + span.setAttribute('tx.version', version); + const isNano = isNanoContract(headers); - const dbTx: DbTransaction | null = await getTransactionById(mysql, hash); + const dbTx: DbTransaction | null = await withSpan('getTransactionById', () => getTransactionById(mysql, hash)); if (dbTx) { const daemonUptime = getDaemonUptime(); // We do not log if the daemon has just started, because it's expected that // we receive an initial duplicate transaction from the fullnode in this case. - if (daemonUptime < DUPLICATE_TX_ALERT_GRACE_PERIOD) return; + if (daemonUptime < DUPLICATE_TX_ALERT_GRACE_PERIOD) { + return; + } logger.error(`Transaction ${hash} already in the database and the daemon has not been recently restarted (uptime of ${daemonUptime} seconds). This is unexpected.`); @@ -384,7 +415,7 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { const firstBlock: string | null = metadata.first_block ?? null; logger.debug('Will add the tx with height', height); // TODO: add is_nanocontract to transaction table? - await addOrUpdateTx( + await withSpan('addOrUpdateTx', () => addOrUpdateTx( mysql, hash, height, @@ -392,13 +423,13 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { version, weight, firstBlock, - ); + )); // Add utxos - await addUtxos(mysql, hash, txOutputs, heightlock); + await withSpan('addUtxos', () => addUtxos(mysql, hash, txOutputs, heightlock)); // Mark tx utxos as spent - await updateTxOutputSpentBy(mysql, txInputs, hash); + await withSpan('updateTxOutputSpentBy', () => updateTxOutputSpentBy(mysql, txInputs, hash)); // Genesis tx has no inputs and outputs, so nothing to be updated, avoid it // Nano contracts are a special case since they can have an address to update even without inputs/outputs @@ -411,10 +442,10 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { const addressBalanceMap: StringMap = getAddressBalanceMap(txInputs, txOutputs, headers); // update address tables (address, address_balance, address_tx_history) - await updateAddressTablesWithTx(mysql, hash, timestamp, addressBalanceMap); + await withSpan('updateAddressTablesWithTx', () => updateAddressTablesWithTx(mysql, hash, timestamp, addressBalanceMap)); // for the addresses present on the tx, check if there are any wallets associated - const addressWalletMap: StringMap = await getAddressWalletInfo(mysql, Object.keys(addressBalanceMap)); + const addressWalletMap: StringMap = await withSpan('getAddressWalletInfo', () => getAddressWalletInfo(mysql, Object.keys(addressBalanceMap))); const addressesPerWallet = Object.entries(addressWalletMap).reduce( (result: StringMap<{ addresses: string[], walletDetails: Wallet }>, [address, wallet]: [string, Wallet]) => { @@ -477,7 +508,7 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { // update wallet_balance and wallet_tx_history tables const walletBalanceMap: StringMap = getWalletBalanceMap(addressWalletMap, addressBalanceMap); - await updateWalletTablesWithTx(mysql, hash, timestamp, walletBalanceMap); + await withSpan('updateWalletTablesWithTx', () => updateWalletTablesWithTx(mysql, hash, timestamp, walletBalanceMap)); // prepare the transaction data to be sent to the SQS queue const txData: Transaction = { @@ -548,15 +579,20 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { await mysql.commit(); } catch (e) { await mysql.rollback(); + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.error('Error handling vertex accepted', e); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; export const handleVertexRemoved = async (context: Context, _event: Event) => { + return tracer.startActiveSpan('handleVertexRemoved', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); @@ -572,6 +608,8 @@ export const handleVertexRemoved = async (context: Context, _event: Event) => { version, } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); + const dbTx: DbTransaction | null = await getTransactionById(mysql, hash); if (!dbTx) { @@ -595,13 +633,17 @@ export const handleVertexRemoved = async (context: Context, _event: Event) => { await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); await mysql.commit(); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.debug(e); await mysql.rollback(); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; /** @@ -650,7 +692,7 @@ export const voidTx = async ( headers: EventTxHeader[], version: number, ) => { - const dbTxOutputs: DbTxOutput[] = await getTxOutputsFromTx(mysql, hash); + const dbTxOutputs: DbTxOutput[] = await withSpan('getTxOutputsFromTx', () => getTxOutputsFromTx(mysql, hash)); const txOutputs: TxOutputWithIndex[] = prepareOutputs(outputs, tokens); const txInputs: TxInput[] = prepareInputs(inputs, tokens); @@ -669,17 +711,18 @@ export const voidTx = async ( const addressBalanceMap: StringMap = getAddressBalanceMap(txInputs, txOutputsWithLocked, headers); - await voidTransaction(mysql, hash); + await withSpan('voidTransaction', () => voidTransaction(mysql, hash)); // CRITICAL: markUtxosAsVoided must be called before voidAddressTransaction // and voidWalletTransaction as those methods recalculate balances based on // the UTXOs table. - await markUtxosAsVoided(mysql, dbTxOutputs); - await voidAddressTransaction(mysql, hash, addressBalanceMap, version); + await withSpan('markUtxosAsVoided', () => markUtxosAsVoided(mysql, dbTxOutputs)); + await withSpan('voidAddressTransaction', () => voidAddressTransaction(mysql, hash, addressBalanceMap, version)); // CRITICAL: Unspend the inputs when voiding a transaction // The inputs of the voided transaction need to be marked as unspent // But only if they were actually spent by this transaction if (inputs.length > 0) { + await withSpan('unspendInputs', async () => { // First, check which inputs were actually spent by this transaction const inputsSpentByThisTx: DbTxOutput[] = []; @@ -708,14 +751,15 @@ export const voidTx = async ( if (inputsSpentByThisTx.length > 0) { await unspendUtxos(mysql, inputsSpentByThisTx); } + }); } // CRITICAL: Update wallet balances when voiding a transaction - await voidWalletTransaction(mysql, hash, addressBalanceMap); + await withSpan('voidWalletTransaction', () => voidWalletTransaction(mysql, hash, addressBalanceMap)); // CRITICAL: Clear tx_proposal marks from inputs that were used in this voided transaction // This ensures the UTXOs can be used in new transactions after the void - await clearTxProposalForVoidedTx(mysql, txInputs); + await withSpan('clearTxProposalForVoidedTx', () => clearTxProposalForVoidedTx(mysql, txInputs)); /** * Delete ALL tokens created by this voided transaction. @@ -749,6 +793,7 @@ export const voidTx = async ( }; export const handleVoidedTx = async (context: Context) => { + return tracer.startActiveSpan('handleVoidedTx', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); @@ -764,6 +809,7 @@ export const handleVoidedTx = async (context: Context) => { version, } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); logger.debug(`Will handle voided tx for ${hash}`); await voidTx( mysql, @@ -778,16 +824,21 @@ export const handleVoidedTx = async (context: Context) => { await mysql.commit(); await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.debug(e); await mysql.rollback(); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; export const handleUnvoidedTx = async (context: Context) => { + return tracer.startActiveSpan('handleUnvoidedTx', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); @@ -796,6 +847,7 @@ export const handleUnvoidedTx = async (context: Context) => { const { hash } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); logger.debug(`Tx ${hash} got unvoided, cleaning up the database.`); await cleanupVoidedTx(mysql, hash); @@ -804,16 +856,21 @@ export const handleUnvoidedTx = async (context: Context) => { await mysql.commit(); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.debug(e); await mysql.rollback(); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; export const handleTxFirstBlock = async (context: Context) => { + return tracer.startActiveSpan('handleTxFirstBlock', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); @@ -828,6 +885,7 @@ export const handleTxFirstBlock = async (context: Context) => { weight, } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); const firstBlock: string | null = metadata.first_block ?? null; // When first_block is null, height should also be null (tx back in mempool) const height: number | null = firstBlock ? metadata.height : null; @@ -843,12 +901,16 @@ export const handleTxFirstBlock = async (context: Context) => { await mysql.commit(); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.error('E: ', e); await mysql.rollback(); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; /** @@ -864,6 +926,7 @@ export const handleTxFirstBlock = async (context: Context) => { * on nano contract execution. */ export const handleNcExecVoided = async (context: Context) => { + return tracer.startActiveSpan('handleNcExecVoided', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); @@ -871,6 +934,8 @@ export const handleNcExecVoided = async (context: Context) => { const fullNodeEvent = context.event as StandardFullNodeEvent; const { hash } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); + // Get all tokens created by this transaction const tokensCreated = await getTokensCreatedByTx(mysql, hash); @@ -888,12 +953,16 @@ export const handleNcExecVoided = async (context: Context) => { await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); await mysql.commit(); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.error('handleNcExecVoided error: ', e); await mysql.rollback(); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; export const updateLastSyncedEvent = async (context: Context) => { @@ -952,61 +1021,74 @@ export const fetchInitialState = async () => { }; export const handleReorgStarted = async (context: Context): Promise => { - if (!context.event) { - throw new Error('No event in context'); - } + return tracer.startActiveSpan('handleReorgStarted', async (span) => { + try { + if (!context.event) { + throw new Error('No event in context'); + } - const fullNodeEvent = context.event; - if (fullNodeEvent.event.type !== FullNodeEventTypes.REORG_STARTED) { - throw new Error('Invalid event type for REORG_STARTED'); - } + const fullNodeEvent = context.event; + if (fullNodeEvent.event.type !== FullNodeEventTypes.REORG_STARTED) { + throw new Error('Invalid event type for REORG_STARTED'); + } - const { reorg_size, previous_best_block, new_best_block, common_block } = fullNodeEvent.event.data; - const { REORG_SIZE_INFO, REORG_SIZE_MINOR, REORG_SIZE_MAJOR, REORG_SIZE_CRITICAL } = getConfig(); + const { reorg_size, previous_best_block, new_best_block, common_block } = fullNodeEvent.event.data; - const metadata = { - reorg_size, - previous_best_block, - new_best_block, - common_block, - }; + span.setAttribute('reorg.size', reorg_size); + const { REORG_SIZE_INFO, REORG_SIZE_MINOR, REORG_SIZE_MAJOR, REORG_SIZE_CRITICAL } = getConfig(); - if (reorg_size >= REORG_SIZE_CRITICAL) { - await addAlert( - 'Critical Reorg Detected', - `A critical reorg of size ${reorg_size} has occurred.`, - Severity.CRITICAL, - metadata, - logger, - ); - } else if (reorg_size >= REORG_SIZE_MAJOR) { - await addAlert( - 'Major Reorg Detected', - `A major reorg of size ${reorg_size} has occurred.`, - Severity.MAJOR, - metadata, - logger, - ); - } else if (reorg_size >= REORG_SIZE_MINOR) { - await addAlert( - 'Minor Reorg Detected', - `A minor reorg of size ${reorg_size} has occurred.`, - Severity.MINOR, - metadata, - logger, - ); - } else if (reorg_size >= REORG_SIZE_INFO) { - await addAlert( - 'Reorg Detected', - `A reorg of size ${reorg_size} has occurred.`, - Severity.INFO, - metadata, - logger, - ); + const metadata = { + reorg_size, + previous_best_block, + new_best_block, + common_block, + }; + + if (reorg_size >= REORG_SIZE_CRITICAL) { + await addAlert( + 'Critical Reorg Detected', + `A critical reorg of size ${reorg_size} has occurred.`, + Severity.CRITICAL, + metadata, + logger, + ); + } else if (reorg_size >= REORG_SIZE_MAJOR) { + await addAlert( + 'Major Reorg Detected', + `A major reorg of size ${reorg_size} has occurred.`, + Severity.MAJOR, + metadata, + logger, + ); + } else if (reorg_size >= REORG_SIZE_MINOR) { + await addAlert( + 'Minor Reorg Detected', + `A minor reorg of size ${reorg_size} has occurred.`, + Severity.MINOR, + metadata, + logger, + ); + } else if (reorg_size >= REORG_SIZE_INFO) { + await addAlert( + 'Reorg Detected', + `A reorg of size ${reorg_size} has occurred.`, + Severity.INFO, + metadata, + logger, + ); + } + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + throw e; + } finally { + span.end(); } + }); }; export const handleTokenCreated = async (context: Context) => { + return tracer.startActiveSpan('handleTokenCreated', async (span) => { const mysql = await getDbConnection(); await mysql.beginTransaction(); @@ -1028,6 +1110,8 @@ export const handleTokenCreated = async (context: Context) => { nc_exec_info, } = fullNodeEvent.event.data; + span.setAttribute('token.uid', token_uid); + logger.debug(`Handling TOKEN_CREATED event for token ${token_uid}: ${token_name} (${token_symbol}) v${token_version}`); // Store the mapping between token and the transaction that created it @@ -1066,12 +1150,16 @@ export const handleTokenCreated = async (context: Context) => { await mysql.commit(); logger.debug(`Successfully stored token ${token_uid} created by tx ${txId}`); } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); logger.error('Error handling TOKEN_CREATED event', e); await mysql.rollback(); throw e; } finally { mysql.destroy(); + span.end(); } + }); }; /** diff --git a/packages/daemon/src/tracing.ts b/packages/daemon/src/tracing.ts new file mode 100644 index 00000000..14a70fea --- /dev/null +++ b/packages/daemon/src/tracing.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'; +import { Resource } from '@opentelemetry/resources'; + +const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; + +const spanProcessor = endpoint + ? new BatchSpanProcessor( + new OTLPTraceExporter(), + { + maxQueueSize: 2048, + maxExportBatchSize: 512, + scheduledDelayMillis: 5000, + exportTimeoutMillis: 5000, + }, + ) + : undefined; + +const sdk = new NodeSDK({ + resource: new Resource({ + 'service.name': process.env.OTEL_SERVICE_NAME || 'wallet-service-daemon', + 'service.version': process.env.SERVICE_VERSION || 'unknown', + 'deployment.environment': process.env.STAGE || 'local', + }), + ...(spanProcessor && { spanProcessor }), + instrumentations: [ + getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-fs': { enabled: false }, + '@opentelemetry/instrumentation-dns': { enabled: false }, + }), + ], +}); + +sdk.start(); + +process.once('SIGTERM', async () => { + try { + await sdk.shutdown(); + process.exit(0); + } catch (err) { + console.error('OTel SDK shutdown error:', err); + process.exit(1); + } +}); diff --git a/yarn.lock b/yarn.lock index 055c5139..64372c0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3251,6 +3251,16 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:^1.7.1": + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" + dependencies: + "@grpc/proto-loader": "npm:^0.8.0" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/bb9bfe2f749179ae5ac7774d30486dfa2e0b004518c28de158b248e0f6f65f40138f01635c48266fa540670220f850216726e3724e1eb29d078817581c96e4db + languageName: node + linkType: hard + "@grpc/proto-loader@npm:^0.7.13": version: 0.7.13 resolution: "@grpc/proto-loader@npm:0.7.13" @@ -3265,6 +3275,20 @@ __metadata: languageName: node linkType: hard +"@grpc/proto-loader@npm:^0.8.0": + version: 0.8.0 + resolution: "@grpc/proto-loader@npm:0.8.0" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.5.3" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/216813bdca52cd3a84ac355ad93c2c3f54252be47327692fe666fd85baa5b1d50aa681ebc5626ab08926564fb2deae3b2ea435aa5bd883197650bbe56f2ae108 + languageName: node + linkType: hard + "@hapi/accept@npm:^6.0.3": version: 6.0.3 resolution: "@hapi/accept@npm:6.0.3" @@ -4064,110 +4088,1244 @@ __metadata: languageName: node linkType: hard -"@kwsites/promise-deferred@npm:^1.1.1": - version: 1.1.1 - resolution: "@kwsites/promise-deferred@npm:1.1.1" - checksum: 10/07455477a0123d9a38afb503739eeff2c5424afa8d3dbdcc7f9502f13604488a4b1d9742fc7288832a52a6422cf1e1c0a1d51f69a39052f14d27c9a0420b6629 +"@kwsites/promise-deferred@npm:^1.1.1": + version: 1.1.1 + resolution: "@kwsites/promise-deferred@npm:1.1.1" + checksum: 10/07455477a0123d9a38afb503739eeff2c5424afa8d3dbdcc7f9502f13604488a4b1d9742fc7288832a52a6422cf1e1c0a1d51f69a39052f14d27c9a0420b6629 + languageName: node + linkType: hard + +"@middy/core@npm:2.5.7": + version: 2.5.7 + resolution: "@middy/core@npm:2.5.7" + checksum: 10/1ab47fcf1e8e28fc31d666d10ee4a08f853bad743301622bf6046199340bbcd765cbdc929a1d6c49c529028aab770ba4415cc9c061b463aa641175674b901c71 + languageName: node + linkType: hard + +"@middy/http-cors@npm:2.5.7": + version: 2.5.7 + resolution: "@middy/http-cors@npm:2.5.7" + dependencies: + "@middy/util": "npm:^2.5.7" + checksum: 10/2fdb402bff9866c073459e147dd3891e78145ab6d3544b58a5f6666d4a06972ce6ea2bf16f75e62b8a7da0da7ced8a8487fa22126cbf81304a0bb114ea687bcc + languageName: node + linkType: hard + +"@middy/util@npm:^2.5.7": + version: 2.5.7 + resolution: "@middy/util@npm:2.5.7" + checksum: 10/89713c2a3a9fe0a5720a2216853c122676365e45f5627f4337a23f6833db7d819bdc5930e70243b206569207d707e93ec25d2199e76689e9489dda4d05031ecb + languageName: node + linkType: hard + +"@noble/hashes@npm:^1.2.0": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: 10/685f59d2d44d88e738114b71011d343a9f7dce9dfb0a121f1489132f9247baa60bc985e5ec6f3213d114fbd1e1168e7294644e46cbd0ce2eba37994f28eeb51b + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^1.0.0": + version: 1.1.1 + resolution: "@npmcli/fs@npm:1.1.1" + dependencies: + "@gar/promisify": "npm:^1.0.1" + semver: "npm:^7.3.5" + checksum: 10/8b5e6d75759b9f1a8b7885913df274c8cbbb1221176872615f2aecedf47b2c36e5dfbf4046ff1a905c9f3592fbd32051b3050b8a897bf03514a1a404b39af074 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/fs@npm:3.1.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/f3a7ab3a31de65e42aeb6ed03ed035ef123d2de7af4deb9d4a003d27acc8618b57d9fb9d259fe6c28ca538032a028f37337264388ba27d26d37fff7dde22476e + languageName: node + linkType: hard + +"@npmcli/move-file@npm:^1.0.1": + version: 1.1.2 + resolution: "@npmcli/move-file@npm:1.1.2" + dependencies: + mkdirp: "npm:^1.0.4" + rimraf: "npm:^3.0.2" + checksum: 10/c96381d4a37448ea280951e46233f7e541058cf57a57d4094dd4bdcaae43fa5872b5f2eb6bfb004591a68e29c5877abe3cdc210cb3588cbf20ab2877f31a7de7 + languageName: node + linkType: hard + +"@one-ini/wasm@npm:0.1.1": + version: 0.1.1 + resolution: "@one-ini/wasm@npm:0.1.1" + checksum: 10/673c11518dba2e582e42415cbefe928513616f3af25e12f6e4e6b1b98b52b3e6c14bc251a361654af63cd64f208f22a1f7556fa49da2bf7efcf28cb14f16f807 + languageName: node + linkType: hard + +"@opentelemetry/api-logs@npm:0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/api-logs@npm:0.55.0" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/db4ff570c39d02715b4a62926abfb9e787f3dd511d2470a72bdf06dce4faf5b6de84dcd5809e8ff406ae647a5117cfdd41864743ad1d3b1bb7c6656abce529c3 + languageName: node + linkType: hard + +"@opentelemetry/api-logs@npm:0.56.0, @opentelemetry/api-logs@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/api-logs@npm:0.56.0" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/5a6e25015acada7449d11124e9adbbe6670e1e9f7e8b46c60360ac89bb1537f2be326bcf18c66dcbcdee9f34e3a18bd4807c5a40faa0a4ac0135cb3675efb2a9 + languageName: node + linkType: hard + +"@opentelemetry/api-logs@npm:0.57.2, @opentelemetry/api-logs@npm:^0.57.1": + version: 0.57.2 + resolution: "@opentelemetry/api-logs@npm:0.57.2" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/8e3bac962e8f1fc93bfee6b433121bd2e07e8a8d1b86ef0d9d4a2c54d1759b64c74cf5da400f82f5ab5a4fe0da481726d8635fd1b15d123cf43090fa0adb8ea8 + languageName: node + linkType: hard + +"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 + languageName: node + linkType: hard + +"@opentelemetry/auto-instrumentations-node@npm:0.56.1": + version: 0.56.1 + resolution: "@opentelemetry/auto-instrumentations-node@npm:0.56.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/instrumentation-amqplib": "npm:^0.46.1" + "@opentelemetry/instrumentation-aws-lambda": "npm:^0.50.3" + "@opentelemetry/instrumentation-aws-sdk": "npm:^0.49.1" + "@opentelemetry/instrumentation-bunyan": "npm:^0.45.1" + "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.45.1" + "@opentelemetry/instrumentation-connect": "npm:^0.43.1" + "@opentelemetry/instrumentation-cucumber": "npm:^0.14.1" + "@opentelemetry/instrumentation-dataloader": "npm:^0.16.1" + "@opentelemetry/instrumentation-dns": "npm:^0.43.1" + "@opentelemetry/instrumentation-express": "npm:^0.47.1" + "@opentelemetry/instrumentation-fastify": "npm:^0.44.2" + "@opentelemetry/instrumentation-fs": "npm:^0.19.1" + "@opentelemetry/instrumentation-generic-pool": "npm:^0.43.1" + "@opentelemetry/instrumentation-graphql": "npm:^0.47.1" + "@opentelemetry/instrumentation-grpc": "npm:^0.57.1" + "@opentelemetry/instrumentation-hapi": "npm:^0.45.2" + "@opentelemetry/instrumentation-http": "npm:^0.57.1" + "@opentelemetry/instrumentation-ioredis": "npm:^0.47.1" + "@opentelemetry/instrumentation-kafkajs": "npm:^0.7.1" + "@opentelemetry/instrumentation-knex": "npm:^0.44.1" + "@opentelemetry/instrumentation-koa": "npm:^0.47.1" + "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.44.1" + "@opentelemetry/instrumentation-memcached": "npm:^0.43.1" + "@opentelemetry/instrumentation-mongodb": "npm:^0.52.0" + "@opentelemetry/instrumentation-mongoose": "npm:^0.46.1" + "@opentelemetry/instrumentation-mysql": "npm:^0.45.1" + "@opentelemetry/instrumentation-mysql2": "npm:^0.45.2" + "@opentelemetry/instrumentation-nestjs-core": "npm:^0.44.1" + "@opentelemetry/instrumentation-net": "npm:^0.43.1" + "@opentelemetry/instrumentation-pg": "npm:^0.51.1" + "@opentelemetry/instrumentation-pino": "npm:^0.46.1" + "@opentelemetry/instrumentation-redis": "npm:^0.46.1" + "@opentelemetry/instrumentation-redis-4": "npm:^0.46.1" + "@opentelemetry/instrumentation-restify": "npm:^0.45.1" + "@opentelemetry/instrumentation-router": "npm:^0.44.1" + "@opentelemetry/instrumentation-socket.io": "npm:^0.46.1" + "@opentelemetry/instrumentation-tedious": "npm:^0.18.1" + "@opentelemetry/instrumentation-undici": "npm:^0.10.1" + "@opentelemetry/instrumentation-winston": "npm:^0.44.1" + "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.30.1" + "@opentelemetry/resource-detector-aws": "npm:^1.12.0" + "@opentelemetry/resource-detector-azure": "npm:^0.6.1" + "@opentelemetry/resource-detector-container": "npm:^0.6.1" + "@opentelemetry/resource-detector-gcp": "npm:^0.33.1" + "@opentelemetry/resources": "npm:^1.24.0" + "@opentelemetry/sdk-node": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.4.1 + checksum: 10/e0aeef6b44f45630f8142d80bd832e137c6dd8e5cc0317606911dc07d395dff414273ce3bd99136dc598046bff54ef8f2c13ca186770b718b6a13cc922ea5fc6 + languageName: node + linkType: hard + +"@opentelemetry/context-async-hooks@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/context-async-hooks@npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/95c3ec3683afb26e5d00a6efbdc459f76d1526a4f5bda07b265bb1f62a77770242695a48feb44b7b479490f89503e2283a2efdb833ed0cdf0256398feed9870f + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.30.1, @opentelemetry/core@npm:^1.0.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.26.0, @opentelemetry/core@npm:^1.8.0": + version: 1.30.1 + resolution: "@opentelemetry/core@npm:1.30.1" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/fa3df9619fdbf8f607132d72915849754b71c4c5f5f705b30c8c59b209abe97206decf25cb8ebafdbb6105a4baab2acddee47468cb9d0b67f1a8df96cebc3548 + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.2" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/sdk-logs": "npm:0.57.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/6213b3164f556eb2ee49410ffe49f1049bc2c38cbe9b7a6512fad55d76ddf45b13bd812adfb90e3af841031d087527072d716967538eeb807da6d15bce376494 + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-http@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.57.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.2" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/sdk-logs": "npm:0.57.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/3fcaa1783cc90f8f3ab9f1efa57b59c6eb577118bfc6e174c0b80b75f713dcbc854185be3cced2e6e08797dce4a67ea60e6919f2d2dab01e112aaea4b861c08b + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-proto@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.57.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.2" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-logs": "npm:0.57.2" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/4258c4c7a4ca9f50c5cf2ce1b2034e93955230e489c0f1fe84dc2c25aa7b01495592c39bb3c8890cd348d17e28271d47c35cac48214a89dc00801bf1ade0a13b + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.2" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.57.2" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-metrics": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/6369dbc22b058cc5960bc6abe0d2ae4b506752682b82ba41710be7660b06a85bbd7ae4d5e87417a1cc9fbc837b829b8206cd422b64ed081ffe7599553b4267a9 + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-http@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-metrics": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/60e676467f5e50caca33c3d5e119c1d07b42b8c03df37661e84f5817a57a6366915dbcef43c60b0d6f3b7b01915e587a0bec4452e4c6dfb9cdbb6197babf156d + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-proto@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.57.2" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-metrics": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/50753c591808c2a470bc7220debd710032f17945570db3bfdd0fa429b8863db584d6ed73e537c5e04df5954ca612ed5e0071f126ffb34b4be965ddc3191774d7 + languageName: node + linkType: hard + +"@opentelemetry/exporter-prometheus@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-prometheus@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-metrics": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/bffa0c5a444e219bb2fbbff8839edee6eba434a1ef37875aa097d2ebe65a7cf0119042238d251cead2d2c5419550c09ae3a44931c4b9610383835ce02f799a0a + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.2" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/48c1b993d9d25f837d709e1b5f9c5a2c16ffbaa0756e37de2231292abec1cbf3d43f124f5b72297f3118cc095e246535788d4d1a5e4d4c3134c0894712c5d81f + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-http@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/cf449fd6f0d33c0ee83ec9946cfb40236efffa870d7b5c9809577fe23065f89795e73f890e4d07886da51c776dfe5332384f98b930a5bcb35a2c5828c4dff972 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-proto@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/3df71d1289f7059cac6b8a44b6dafd266a1e7edfe6f44780cfb89911516b146093397a7b973eaf9e75b85988aaa2fc4103837ca72a22dfdcad3734553e7b83aa + languageName: node + linkType: hard + +"@opentelemetry/exporter-zipkin@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/exporter-zipkin@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/66c1074660f521bc48760309c053fb1216e9f709d3b546024de02a63c7a7c80ff817c24913c29e4d9e899a41f4722e594d42a9fc5a2c9dc83ba7628b8b568b18 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-amqplib@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.46.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/4f718937b865adec3aa7756484cf4192493f1e8946a448ec74711b08f44646eab112683fbd25ed2fce3e78aaacbe6b1a61d05fc08ad2a3303ae0873d8b74159a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-lambda@npm:0.50.3, @opentelemetry/instrumentation-aws-lambda@npm:^0.50.3": + version: 0.50.3 + resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.50.3" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/aws-lambda": "npm:8.10.147" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5c436a891f1177f6ad2c84eb5c6ca244029597ec9d93a384cd72e1300e5d22c40b4aca4fff1a9f4c5d28c0289946572e1085bb03b3657d05a048c2f16bb3df12 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-sdk@npm:0.48.0": + version: 0.48.0 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.48.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/propagation-utils": "npm:^0.30.14" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/7d036e6467dbbaed5f3569ead30f628e73c6144c769278dec29eb9ef85804dda48c37a864c05ce0fcf69f89ea7447999e0630db612c361e72980fbe7e6615d39 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-sdk@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.49.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/propagation-utils": "npm:^0.30.16" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/dc3c6e13634385896e22c87196270ee6348200156f6f0dd2af6b817b08178593f7ed10e79560a413bdfd619ca5b047b04e9d78bf6327611afdea0cfde72af5e1 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-bunyan@npm:^0.45.1": + version: 0.45.1 + resolution: "@opentelemetry/instrumentation-bunyan@npm:0.45.1" + dependencies: + "@opentelemetry/api-logs": "npm:^0.57.1" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@types/bunyan": "npm:1.8.11" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/a1062f71cce2bb376feabce224e910394c55911352fe89e4808e770fb297905dfdc4079a9885f359504f403b49ff6bc36b0ae12b1bb8bc0c078ce203fc03ce95 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-cassandra-driver@npm:^0.45.1": + version: 0.45.1 + resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.45.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d7f588aec6bd0e45886974869daddcf0642ccbb51b8f5327bb80f6b9eea02604eb7de811d378796e25923d4961e5a2fe2f7bed5eb60f40f109ff8049611bef3a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-connect@npm:^0.43.1": + version: 0.43.1 + resolution: "@opentelemetry/instrumentation-connect@npm:0.43.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/connect": "npm:3.4.38" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/a0421252d0ab8b1d101ba2ae155453b67241907ff83885c0a1e2fc2ec721510132687c210156f8f3744072caddff82b7c65f185025998c994372d9c2bc45d52e + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-cucumber@npm:^0.14.1": + version: 0.14.1 + resolution: "@opentelemetry/instrumentation-cucumber@npm:0.14.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/6b1c7be08af7873d80740323fecbc72193ab818d6dbf6d1d94b4d94d9b2eebdf0e40931253c8affa9f98b1da0ef7609033d1ed989cc26dbd6f0630f3fa3567ed + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-dataloader@npm:^0.16.1": + version: 0.16.1 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.16.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e5bfee98e279029c0e4e8348cc787b1e9e203e9df47cdf9d4cc1e1ddaa96de59b6852ebc5e2b5f6ebf8a152d3fb17b7e3dc54d258072b78dc4f7edf3a164fe92 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-dns@npm:^0.43.1": + version: 0.43.1 + resolution: "@opentelemetry/instrumentation-dns@npm:0.43.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/9cc9224bcbfc2a25232e9bf96f37abb729b84fefa68652909a575c3554fc4a72e01aa2985aff8171ba535d7610da82c0ed9e1abddc9ac174216aff76ceb38553 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-express@npm:^0.47.1": + version: 0.47.1 + resolution: "@opentelemetry/instrumentation-express@npm:0.47.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/2c1250dc3d2c1562ff9764c2935788636afdd895fadcaf5d5c67093090c8a9e3f67a79c52ba33d4431b7f70c32bb5473f4d4fa1d619dae6fca95dc6476025dc2 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fastify@npm:^0.44.2": + version: 0.44.2 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.44.2" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e0fa50982dbc908c9ff63603fb91dda9a75248ce799c0669cc12783e45b14a5f44354edc78d1676918cf4514cd98310b7dbaf04d6d422838d7016ddda40e24c4 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fs@npm:^0.19.1": + version: 0.19.1 + resolution: "@opentelemetry/instrumentation-fs@npm:0.19.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fce06b71357c56a5d19e9491aa39e863550b8989fdecd7b9d03de483676e923218ff21151a04bc9fbd669b25652a1b8731bc811cfea5db74a0b0a5f8783c23b9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-generic-pool@npm:^0.43.1": + version: 0.43.1 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.43.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/26d8b286f0f8f6269c7ccf5877755c6fcb10210bc7e1251ba6aa97ca8ada71efcbc69accd9622a43a243b5a799d624dc55fb3992e7c38529fb2c25eb5830bd45 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-graphql@npm:^0.47.1": + version: 0.47.1 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.47.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/43937309484a593edfb8ab58296c02ac63e7718e407af8331be60e608342485803e8ff8e6ecf558a306ab2a44b1d252caf51823006c21be414a11d94b8fad033 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-grpc@npm:^0.57.1": + version: 0.57.2 + resolution: "@opentelemetry/instrumentation-grpc@npm:0.57.2" + dependencies: + "@opentelemetry/instrumentation": "npm:0.57.2" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5a5a1c2ca94042f05c8c1b6fcd7b71fd0b1b5eba119a765dd2f2dec7f38349ad71038d69e33b02396737b568fe43aac117fadb1a7e149939c2c7ef7568ef144d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-hapi@npm:^0.45.2": + version: 0.45.2 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.45.2" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fa1ece45533fe14425638a89fa53087b3367545f6c5ba76ff0da8d2032612be2afe1a3265dc6e5954a5bb17dbaccb0a345abdbc4e3d6be69173737c17fbe3c9d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-http@npm:0.57.2, @opentelemetry/instrumentation-http@npm:^0.57.1": + version: 0.57.2 + resolution: "@opentelemetry/instrumentation-http@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/instrumentation": "npm:0.57.2" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + forwarded-parse: "npm:2.1.2" + semver: "npm:^7.5.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/ca1d25c9aa6ab4142da5172e1bcd3402e0c38ce4306201691e6316774b25a08f75f8b7f0a2e72eac5dce1c1d80e1b30ece8ba4fa4ed6ccff0c51a276b9d56823 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-ioredis@npm:^0.47.1": + version: 0.47.1 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.47.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5fb9bdce48f3e4cae162f55fa97e1f5e11e6f032b4a6b77d49e7c46fa00b2cc85f5a27aa4e8ca55ec5807246efb8ab3203ba732a530fd1f7017aef7222986da7 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-kafkajs@npm:^0.7.1": + version: 0.7.1 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.7.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/8e88acf2438699d8e2564140b848140eee75d530f95e6ec3d5be2f8c6f592ed7a32489ee1a9714d9c48040969a2314b7153c25194e7d14850dedd429e8a43006 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-knex@npm:^0.44.1": + version: 0.44.1 + resolution: "@opentelemetry/instrumentation-knex@npm:0.44.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/8c6091edfa57580f740789594c99ecb5b424024d025118573a18e0045207f4233d4e88fddbd1d559307301800ea33b242288fc267b4de9baea298680faf4a061 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-koa@npm:^0.47.1": + version: 0.47.1 + resolution: "@opentelemetry/instrumentation-koa@npm:0.47.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/7d764f23c361f94850954a2132b6627ebb90739e7d6d3eda7a7b491a6452593c3c7e651c7a12cdbc050258a58912ed41b854b602c8b702034407f1f767d8c45d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-lru-memoizer@npm:^0.44.1": + version: 0.44.1 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.44.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/0676f0a9ef37f9a2ee74cde881531f50808e5d394b39f7b0be60a0e465a02d83e0e013989b85628f9a91a11f6fad560a98e7e3047a0ba52f303cd33d5ff07631 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-memcached@npm:^0.43.1": + version: 0.43.1 + resolution: "@opentelemetry/instrumentation-memcached@npm:0.43.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/memcached": "npm:^2.2.6" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d503efeeb6814e5adff87d36641546ab332a9c37e13cedf94676f2b1dda495c1cd1475cdf9c3d850a1b3818e4fe46471d0902163fddc55044b39f0d5b09e53ea + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongodb@npm:^0.52.0": + version: 0.52.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.52.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/251e4e750608240552184e99a8fc22b3367e3275a87f646d9ab8d639b82dc397e1271ed6746f996cf57d72d63d291c5909c4b9c735248bc3de247e6120dc5362 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongoose@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.46.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/126d580a8db2562e67f4398325db826bd975632d7d13e9eebfdbd49de7f0715b0793acda21f291bb5e617ec6403be307a7f636a99e36be213ff074651ee9ff50 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql2@npm:^0.45.2": + version: 0.45.2 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.45.2" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/c93c9deb22eee30bdd2e13a88f8483247d82f617591763ceadbb9cbc0eafe9d965083de6dea3bf32781a1a4dee595e657799a6bf52cd0f8c3008d097a2b9c627 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.43.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.55.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/mysql": "npm:2.15.26" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e6b98b8c4487d8e3b2bc887ec4a1cb0760c3442b1383ab7695d27e32f54497452c5c78bf1c743f848ce5db92da524441e69328055781b617684600c84e6c1811 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql@npm:^0.45.1": + version: 0.45.1 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.45.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/mysql": "npm:2.15.26" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/787716756d258fae7a0a8256c3e3c08ac89a51617fe86d4b2d5f9dfc73a93825327daacb1a841917fd7e2a462a69eeb69d431be070cfcead1c562a466386504a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-nestjs-core@npm:^0.44.1": + version: 0.44.1 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.44.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/9411fe5088b795d75bffd1d48cef87692f8e62340ea8a94548ab5b132be2a9d462658cde8958d41826537d2481dab07fec4e01d1c21989061069f857a8d3ab60 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-net@npm:^0.43.1": + version: 0.43.1 + resolution: "@opentelemetry/instrumentation-net@npm:0.43.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e524c9b16567a6748cb9c179fb5e9000aa46ee7a83f80526a248dd861c21f460b55d75cb9dd2b6bcf1393033a9c07bbda8c3c75c9f24fdcd481160acab67f411 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pg@npm:^0.51.1": + version: 0.51.1 + resolution: "@opentelemetry/instrumentation-pg@npm:0.51.1" + dependencies: + "@opentelemetry/core": "npm:^1.26.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + "@types/pg": "npm:8.6.1" + "@types/pg-pool": "npm:2.0.6" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d99a06eb596c5c96f89479d318dd6f9f8603a03b34f0707957b9ed2de36b1fe31d515c7ff2bc162ef1594ad1e5c77c19e7f6f13e7eda4a5ff8ab5cefee8946f8 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pino@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-pino@npm:0.46.1" + dependencies: + "@opentelemetry/api-logs": "npm:^0.57.1" + "@opentelemetry/core": "npm:^1.25.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/c10f1839dd64912744e7919f0072acf7cb504088c9d94977ada9b033956d1d20438ee1c263b712a85861edbd798b224eae51384a4977f24b6b63db28443624f5 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis-4@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-redis-4@npm:0.46.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/823132a8191bc566323c728a5645f52638e53a58b26f9e58828e80a4d75fc233b146dc138ce1a85feb09d8f89459a1fed39593d5be32fe9d4e5425335ec21332 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-redis@npm:0.44.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.55.0" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5712900b79be1cb5e469b1ba3573bfb41a029b15282fbb162e7e91193cafb6c822a5cb2685fc982c5d0a2ddc72a24c76f0d44561e958726a9ade1bbdec6ba264 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-redis@npm:0.46.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/009c56ec5623a3255e2b8b49d574b95ffc42c1eca749c1ecd1d51c977e5136107dc80e587f94af13c7bea595dab64694d8cd15779ab7a5463507048b10723ad9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-restify@npm:^0.45.1": + version: 0.45.1 + resolution: "@opentelemetry/instrumentation-restify@npm:0.45.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/baef07e77de0e17682c468a0c1f7fee79ebcd800fe92880c7eaab5e7b9fadf580a81734222308fb44b8fdb2a914846e028af1a37521b04c022d0b198d2e54ce7 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-router@npm:^0.44.1": + version: 0.44.1 + resolution: "@opentelemetry/instrumentation-router@npm:0.44.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/ea59f2047586e0aa917be09753bef2f134ce9ea1caf918f8a5ff6b89731d8a71b48eaea9a5b20f8016671a4ebd47d10847344f52c37c8cb77ef187ebe32e5f5f + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-socket.io@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.46.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/22cf3d997e8d7bd78969cf0503f5339b9319b78fd8a9d973a90e9a58f8080fe86ee6af48641be0d5450eb909c9ca93f1c2303f9b170f1ee78962235dd87d628d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-tedious@npm:^0.18.1": + version: 0.18.1 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.18.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/tedious": "npm:^4.0.14" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/f508d72b1e9b8bb6b3b179a8a2dc82e9a24dfab100063dc07fcc0fc059b75a35b85d25259ddd6e8e959f192aec55cc98f1427feb4349a14841d14981e17d1f8f + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-undici@npm:^0.10.1": + version: 0.10.1 + resolution: "@opentelemetry/instrumentation-undici@npm:0.10.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.7.0 + checksum: 10/99a560e9f221f3a8dc1ee4c3d6ec776ffe90ab79b73f50fecd2ae73adcdd623397cf63c039808e60403be4a151e229f3381c2dee5d6b07a6d298a4b3919c3d8d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-winston@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-winston@npm:0.43.0" + dependencies: + "@opentelemetry/api-logs": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.56.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/9cd8e4b047bea576e6277c911d1f44df2b880b2c9f01081fff9b5ee4f1131dd6dd481ee11ab887344d22535211af1f6a75bcd9c34185183855acdd12975024ca + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-winston@npm:^0.44.1": + version: 0.44.1 + resolution: "@opentelemetry/instrumentation-winston@npm:0.44.1" + dependencies: + "@opentelemetry/api-logs": "npm:^0.57.1" + "@opentelemetry/instrumentation": "npm:^0.57.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/6cea0a18bcbf91d4c89f6423ad452f1c2bfa15b69b9f1f2dacc39450c57dea46e604c29e24041ef39addac5d5eb06499c47a33c06c52b8bda6db295b7cb0e3ba + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.57.2, @opentelemetry/instrumentation@npm:^0.57.1": + version: 0.57.2 + resolution: "@opentelemetry/instrumentation@npm:0.57.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.2" + "@types/shimmer": "npm:^1.2.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/b66b840e87976a5edf551a7011a395df8df5985571ac0506412943d07b4309fcc78fe71d3f55217a00f44384fbf61f59f1e54d544ab12f5490f6a7a56b71e02a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation@npm:0.55.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.55.0" + "@types/shimmer": "npm:^1.2.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/94f0765d0fc4934ddfe7b349f034b106ede65457e677a844eee0589b5269285772c90979531ae031f2cfc6a8fa88511cb9e8b7310978bc83c1bfa126f49342ca + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation@npm:0.56.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.56.0" + "@types/shimmer": "npm:^1.2.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/7c3802eb6b55b39b6904526d052b918619c9cde0f71a35bc2f23ac6c3a10ea66b08a65adf6862cdbaac7b231fb5119204b4d5531be25b96933a9d8b91a9ce062 + languageName: node + linkType: hard + +"@opentelemetry/otlp-exporter-base@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.57.2" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e1dd6731a5b9e05bfc9d46984787a12af0c24c86ef3c0aa1b311679715804e0bb5d2842898365c30703067f7e7ecd61eff82132a99411673cd3a1729b80c4aef + languageName: node + linkType: hard + +"@opentelemetry/otlp-grpc-exporter-base@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.57.2" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/otlp-exporter-base": "npm:0.57.2" + "@opentelemetry/otlp-transformer": "npm:0.57.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5dd39d9801c33ee658b71547706f8a2bee7b19784bd91bf7c40948752017d168d8ef856c8e02f2b7e0883c499f49da3709b98789fc365ca9cfd61c35954821c2 + languageName: node + linkType: hard + +"@opentelemetry/otlp-transformer@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/otlp-transformer@npm:0.57.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.2" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-logs": "npm:0.57.2" + "@opentelemetry/sdk-metrics": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + protobufjs: "npm:^7.3.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fc50d9358d2dc356c94b5dd87e70f534ff10cce1d541047ab20f830410b30b5e706cfce4147f16305954e21ca45ac1ceb3996268fa23af1be325fde96ddce28d + languageName: node + linkType: hard + +"@opentelemetry/propagation-utils@npm:^0.30.14, @opentelemetry/propagation-utils@npm:^0.30.16": + version: 0.30.16 + resolution: "@opentelemetry/propagation-utils@npm:0.30.16" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/8d282553d08aadfc9f8879ea6fbb33089765a535f960d72ae27fb98e8dab9c7e0394217cb9e2999479f9c2486fa9454cf4ffffeda30f4a96f8fe011276942b75 + languageName: node + linkType: hard + +"@opentelemetry/propagator-b3@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/propagator-b3@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/df246fff27cd2075221fa4731fdfd26f0fdb1c82f6045bcc8f4abeee808c4fc9f396097106755ae0c975fafe3e602b8870541fabedb29376c6c58b83e2eb8a7c + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/propagator-jaeger@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/a3603852129a09a1224acc36b90994be5b1b7eb9a7a6fe87b09522c16b348a5567de8ea3fc441ee0717e8eacecfca1eaaf79ec9bfcb8fded8463fc25c29c3c9e + languageName: node + linkType: hard + +"@opentelemetry/redis-common@npm:^0.36.2": + version: 0.36.2 + resolution: "@opentelemetry/redis-common@npm:0.36.2" + checksum: 10/e7f610f79c95bab9156a9831162c7b55b94ab43c5e47ecb9efcc10c08a236395fdd54b6bb018da981e6641bac9da6fda1b50636fb49db584e87d988750d255e1 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.30.1": + version: 0.30.1 + resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.30.1" + dependencies: + "@opentelemetry/core": "npm:^1.26.0" + "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/13e657d6e6f1f3de00f8603daf91a1b91ac39a6100c4d45f0dbbdadf7b32d2f93d5ae191f34a4c8fac4ed3d843c429f365853e8f3279f6ef2203776ae2724b9f languageName: node linkType: hard -"@middy/core@npm:2.5.7": - version: 2.5.7 - resolution: "@middy/core@npm:2.5.7" - checksum: 10/1ab47fcf1e8e28fc31d666d10ee4a08f853bad743301622bf6046199340bbcd765cbdc929a1d6c49c529028aab770ba4415cc9c061b463aa641175674b901c71 +"@opentelemetry/resource-detector-aws@npm:^1.12.0": + version: 1.12.0 + resolution: "@opentelemetry/resource-detector-aws@npm:1.12.0" + dependencies: + "@opentelemetry/core": "npm:^1.0.0" + "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/95702aed5d2a22fab0429439d57ede5c53b3512618af21ffc098b0e6fca86266e9703a32325427082bb5776697d5e74a4e99f6c757e4b6bcf497e32663e705f9 languageName: node linkType: hard -"@middy/http-cors@npm:2.5.7": - version: 2.5.7 - resolution: "@middy/http-cors@npm:2.5.7" +"@opentelemetry/resource-detector-azure@npm:^0.6.1": + version: 0.6.1 + resolution: "@opentelemetry/resource-detector-azure@npm:0.6.1" dependencies: - "@middy/util": "npm:^2.5.7" - checksum: 10/2fdb402bff9866c073459e147dd3891e78145ab6d3544b58a5f6666d4a06972ce6ea2bf16f75e62b8a7da0da7ced8a8487fa22126cbf81304a0bb114ea687bcc + "@opentelemetry/core": "npm:^1.25.1" + "@opentelemetry/resources": "npm:^1.10.1" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/882938d98675a0035ebc7508815c8286ef8b307d9d1b7bf1f71ab482eba2922986aee907bc202e6e1c94d7886853b91962554e6d4f9f1ab899246342d9cc5372 languageName: node linkType: hard -"@middy/util@npm:^2.5.7": - version: 2.5.7 - resolution: "@middy/util@npm:2.5.7" - checksum: 10/89713c2a3a9fe0a5720a2216853c122676365e45f5627f4337a23f6833db7d819bdc5930e70243b206569207d707e93ec25d2199e76689e9489dda4d05031ecb +"@opentelemetry/resource-detector-container@npm:^0.6.1": + version: 0.6.1 + resolution: "@opentelemetry/resource-detector-container@npm:0.6.1" + dependencies: + "@opentelemetry/core": "npm:^1.26.0" + "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/2c5c1e7e5f3375a1a2e62787249c0097d25ae1c8e9b8cf5d106003226dffdfdfc709112447ea6469052fd901c7c217d5fcc04bc48fe4207383b9d7610e927228 languageName: node linkType: hard -"@noble/hashes@npm:^1.2.0": - version: 1.3.2 - resolution: "@noble/hashes@npm:1.3.2" - checksum: 10/685f59d2d44d88e738114b71011d343a9f7dce9dfb0a121f1489132f9247baa60bc985e5ec6f3213d114fbd1e1168e7294644e46cbd0ce2eba37994f28eeb51b +"@opentelemetry/resource-detector-gcp@npm:^0.33.1": + version: 0.33.1 + resolution: "@opentelemetry/resource-detector-gcp@npm:0.33.1" + dependencies: + "@opentelemetry/core": "npm:^1.0.0" + "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + gcp-metadata: "npm:^6.0.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/c79dc9790b0218dec811611d618e1abea5366ea5b9ad15d1b2de62453ca81762a90d36cdfe16a39deac01f7bd4620ec02a40c81322f62e97765a26da2e1e41a7 languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" +"@opentelemetry/resources@npm:1.30.1, @opentelemetry/resources@npm:^1.10.0, @opentelemetry/resources@npm:^1.10.1, @opentelemetry/resources@npm:^1.24.0": + version: 1.30.1 + resolution: "@opentelemetry/resources@npm:1.30.1" dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/9b7544b639e8fee41315e2646615676ffb1020dba0f6c81e6ec1dd2daf5409fc6ce3d2b629bbd9cd32f85decc3a8bfa5dc8cc52bb72bd84c1777ca25b4301aa0 languageName: node linkType: hard -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 +"@opentelemetry/sdk-logs@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/sdk-logs@npm:0.57.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.2" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.4.0 <1.10.0" + checksum: 10/4c60faf32cb177e64a44aa139372e01a69b6d9805c4296a2cb555ea3f824f1a4ebb52909742ef62f1fcc0701348168d2c318e6e74902996c04de7a8ebdb013ed languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" +"@opentelemetry/sdk-metrics@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/sdk-metrics@npm:1.30.1" dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/cfdbef083eab77ee62cf4d3f29508a0f444a2a2413554b2977632ea1e238fbd472c964b48e09eb48e2131f6cdc1957ff079838d53de5777207a041b594dd917a + languageName: node + linkType: hard + +"@opentelemetry/sdk-node@npm:0.57.2, @opentelemetry/sdk-node@npm:^0.57.1": + version: 0.57.2 + resolution: "@opentelemetry/sdk-node@npm:0.57.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.57.2" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.57.2" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.57.2" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.57.2" + "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.57.2" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.57.2" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.57.2" + "@opentelemetry/exporter-prometheus": "npm:0.57.2" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.57.2" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.57.2" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.57.2" + "@opentelemetry/exporter-zipkin": "npm:1.30.1" + "@opentelemetry/instrumentation": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-logs": "npm:0.57.2" + "@opentelemetry/sdk-metrics": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + "@opentelemetry/sdk-trace-node": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/f3a9b87c52880446f787cc86c1dc6d13049a4305b0d26c4b65565ae253de238ead700bf6da4f8d62d684b1d29a27d10a8c4a606c38b5501da3ad87de0ce7e6ca languageName: node linkType: hard -"@npmcli/fs@npm:^1.0.0": - version: 1.1.1 - resolution: "@npmcli/fs@npm:1.1.1" +"@opentelemetry/sdk-trace-base@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/sdk-trace-base@npm:1.30.1" dependencies: - "@gar/promisify": "npm:^1.0.1" - semver: "npm:^7.3.5" - checksum: 10/8b5e6d75759b9f1a8b7885913df274c8cbbb1221176872615f2aecedf47b2c36e5dfbf4046ff1a905c9f3592fbd32051b3050b8a897bf03514a1a404b39af074 + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/3ba794622c9ff1d147b77fcd0c8547a6a1356edb5af884cf1d09838c71a004a044ea55d4c742b956e9247e46053583bdbda533836686b2f54ee1ecfc527254ff languageName: node linkType: hard -"@npmcli/fs@npm:^3.1.0": - version: 3.1.0 - resolution: "@npmcli/fs@npm:3.1.0" +"@opentelemetry/sdk-trace-node@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/sdk-trace-node@npm:1.30.1" dependencies: - semver: "npm:^7.3.5" - checksum: 10/f3a7ab3a31de65e42aeb6ed03ed035ef123d2de7af4deb9d4a003d27acc8618b57d9fb9d259fe6c28ca538032a028f37337264388ba27d26d37fff7dde22476e + "@opentelemetry/context-async-hooks": "npm:1.30.1" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/propagator-b3": "npm:1.30.1" + "@opentelemetry/propagator-jaeger": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + semver: "npm:^7.5.2" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/75d793718e3785abbfab337a6d8ab12cfdfdb2ec8c3c642c2657ebf8d589397beea10646b2fb094aaac106d4539a8105626bfcb3ad845eb852ff460148b3e34f languageName: node linkType: hard -"@npmcli/move-file@npm:^1.0.1": - version: 1.1.2 - resolution: "@npmcli/move-file@npm:1.1.2" - dependencies: - mkdirp: "npm:^1.0.4" - rimraf: "npm:^3.0.2" - checksum: 10/c96381d4a37448ea280951e46233f7e541058cf57a57d4094dd4bdcaae43fa5872b5f2eb6bfb004591a68e29c5877abe3cdc210cb3588cbf20ab2877f31a7de7 +"@opentelemetry/semantic-conventions@npm:1.28.0": + version: 1.28.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.28.0" + checksum: 10/c182a3206769b5d5a8ab89a5c674d046fd789421cef27ea55af179990e314732433c98e5017aa23e99f15fd2b0e13cb129bb6c2282da6860ce9419adf32b2e87 languageName: node linkType: hard -"@one-ini/wasm@npm:0.1.1": - version: 0.1.1 - resolution: "@one-ini/wasm@npm:0.1.1" - checksum: 10/673c11518dba2e582e42415cbefe928513616f3af25e12f6e4e6b1b98b52b3e6c14bc251a361654af63cd64f208f22a1f7556fa49da2bf7efcf28cb14f16f807 +"@opentelemetry/semantic-conventions@npm:^1.27.0": + version: 1.40.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.40.0" + checksum: 10/edb58894590e42e631006a9f5741955fad248e3589aa334a5e59080c535ead44ee9f376c444ef2be094d1e6c1a2e596538c1df0a31a04508551e91b1a5d5c93c languageName: node linkType: hard -"@opentelemetry/api@npm:^1.3.0": - version: 1.9.0 - resolution: "@opentelemetry/api@npm:1.9.0" - checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 +"@opentelemetry/sql-common@npm:^0.40.1": + version: 0.40.1 + resolution: "@opentelemetry/sql-common@npm:0.40.1" + dependencies: + "@opentelemetry/core": "npm:^1.1.0" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + checksum: 10/f887b4135be56c9ef6e29f040c9f75f34709e38c11897d59d284d7e73175a2dd2c6267c18061144e81a0045fc461b7813769db2e49c42a8d6becc58b1456d55c languageName: node linkType: hard @@ -6445,6 +7603,13 @@ __metadata: languageName: node linkType: hard +"@types/aws-lambda@npm:8.10.147": + version: 8.10.147 + resolution: "@types/aws-lambda@npm:8.10.147" + checksum: 10/c80df3be4ed42acf7f1a139afeaa96c69466d63ce3e6578a9411bd956806a10ae182fb45fa6dc9e9b31e5da08762af3758abeb84f989edac4c8011d8bf5f7b8f + languageName: node + linkType: hard + "@types/aws-lambda@npm:8.10.95": version: 8.10.95 resolution: "@types/aws-lambda@npm:8.10.95" @@ -6512,6 +7677,15 @@ __metadata: languageName: node linkType: hard +"@types/bunyan@npm:1.8.11": + version: 1.8.11 + resolution: "@types/bunyan@npm:1.8.11" + dependencies: + "@types/node": "npm:*" + checksum: 10/8906ec6c0573f563139681344c2a1d220c394e4aa5b88ff99b011894734f74aafc9d0b3a71758dda3b0ee30e9b6370a8aa48ac26b5585f3c8d7e0fe8db6ca844 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.3 resolution: "@types/cacheable-request@npm:6.0.3" @@ -6540,6 +7714,15 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:3.4.38": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10/7eb1bc5342a9604facd57598a6c62621e244822442976c443efb84ff745246b10d06e8b309b6e80130026a396f19bf6793b7cecd7380169f369dac3bfc46fb99 + languageName: node + linkType: hard + "@types/debug@npm:^4.1.8": version: 4.1.9 resolution: "@types/debug@npm:4.1.9" @@ -6737,6 +7920,15 @@ __metadata: languageName: node linkType: hard +"@types/memcached@npm:^2.2.6": + version: 2.2.10 + resolution: "@types/memcached@npm:2.2.10" + dependencies: + "@types/node": "npm:*" + checksum: 10/2577a163052cd899f1d8355e8cc918624003db58bfba72f63af80cb5917274e46fbde67465507f6468153fc6bda5e24a262fa114fb1de4d6d6a4bc072ce945e0 + languageName: node + linkType: hard + "@types/mime@npm:*": version: 3.0.2 resolution: "@types/mime@npm:3.0.2" @@ -6767,6 +7959,15 @@ __metadata: languageName: node linkType: hard +"@types/mysql@npm:2.15.26": + version: 2.15.26 + resolution: "@types/mysql@npm:2.15.26" + dependencies: + "@types/node": "npm:*" + checksum: 10/8f205eeaca8f94e998ce4707354bfd02b6ca0da5b7c22289f8f6ff864d549bfb95ca7ddc2f2ebe69eb8f7e3d1f5d8a5b9a2f98aee13824dbc48051bf53a1664d + languageName: node + linkType: hard + "@types/mysql@npm:^2.15.6": version: 2.15.22 resolution: "@types/mysql@npm:2.15.22" @@ -6838,6 +8039,37 @@ __metadata: languageName: node linkType: hard +"@types/pg-pool@npm:2.0.6": + version: 2.0.6 + resolution: "@types/pg-pool@npm:2.0.6" + dependencies: + "@types/pg": "npm:*" + checksum: 10/cc54ce97115effc982bd052f79901a78215e76554aca0ecc92e78eb907e4fb2962924039369cd9aaf48075f1637593ce14647c62d3a2eb03789ce5d1c6df750b + languageName: node + linkType: hard + +"@types/pg@npm:*": + version: 8.18.0 + resolution: "@types/pg@npm:8.18.0" + dependencies: + "@types/node": "npm:*" + pg-protocol: "npm:*" + pg-types: "npm:^2.2.0" + checksum: 10/fdfcaff97f0bd067bf4c4750592bd627a772c5ac4d4164332efe121f9fc2112479dcf913bafd91fe8e86581d5994897e5fd5b4faaf734a42719540037d3b64e7 + languageName: node + linkType: hard + +"@types/pg@npm:8.6.1": + version: 8.6.1 + resolution: "@types/pg@npm:8.6.1" + dependencies: + "@types/node": "npm:*" + pg-protocol: "npm:*" + pg-types: "npm:^2.2.0" + checksum: 10/bf1134ea194ad9cb8bfe0aab7a532713c63bae1d95909fa45e8dc1945e44ede74f2d4c5b2cd2f9712c6b970896929e0d82480f9c9da79addf405c089b590e562 + languageName: node + linkType: hard + "@types/qs@npm:*": version: 6.9.8 resolution: "@types/qs@npm:6.9.8" @@ -6910,6 +8142,13 @@ __metadata: languageName: node linkType: hard +"@types/shimmer@npm:^1.2.0": + version: 1.2.0 + resolution: "@types/shimmer@npm:1.2.0" + checksum: 10/f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -6917,6 +8156,15 @@ __metadata: languageName: node linkType: hard +"@types/tedious@npm:^4.0.14": + version: 4.0.14 + resolution: "@types/tedious@npm:4.0.14" + dependencies: + "@types/node": "npm:*" + checksum: 10/c8f6480cf68d95b5e9f64fa6210f50915e8ff124638965a2c5a4c87641cc7f762155b9a8e01e3e517d48f8931e2d3920a40c4e677398e8b93c9cf1c8a36d2fbb + languageName: node + linkType: hard + "@types/tough-cookie@npm:*": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" @@ -7686,6 +8934,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" + peerDependencies: + acorn: ^8 + checksum: 10/8bfbfbb6e2467b9b47abb4d095df717ab64fce2525da65eabee073e85e7975fb3a176b6c8bba17c99a7d8ede283a10a590272304eb54a93c4aa1af9790d47a8b + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -7711,6 +8968,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.14.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: 10/690c673bb4d61b38ef82795fab58526471ad7f7e67c0e40c4ff1e10ecd80ce5312554ef633c9995bfc4e6d170cef165711f9ca9e49040b62c0c66fbf2dd3df2b + languageName: node + linkType: hard + "acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.10.0 resolution: "acorn@npm:8.10.0" @@ -9146,6 +10412,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.2.2": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -9673,6 +10946,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.3.5": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad + languageName: node + linkType: hard + "decompress-response@npm:^6.0.0": version: 6.0.0 resolution: "decompress-response@npm:6.0.0" @@ -11678,6 +12963,13 @@ __metadata: languageName: node linkType: hard +"forwarded-parse@npm:2.1.2": + version: 2.1.2 + resolution: "forwarded-parse@npm:2.1.2" + checksum: 10/fca4df8898248d123d9d29a9fdf48005dd757366c2c17c1e195e8311a9aa89caf9f5e592f58f7d3d635087675ff39e85c32c6205838510f6f1fa4109de519930 + languageName: node + linkType: hard + "fs-constants@npm:^1.0.0": version: 1.0.0 resolution: "fs-constants@npm:1.0.0" @@ -11865,6 +13157,17 @@ __metadata: languageName: node linkType: hard +"gcp-metadata@npm:^6.0.0": + version: 6.1.1 + resolution: "gcp-metadata@npm:6.1.1" + dependencies: + gaxios: "npm:^6.1.1" + google-logging-utils: "npm:^0.0.2" + json-bigint: "npm:^1.0.0" + checksum: 10/f6b1a604d5888db261a9a3ca0a494338b5cdbf815efa393aa38051d814387545bbfd9f25874bf8ea36441f2052625add42658e8973648e53f9b90f151b4bad1b + languageName: node + linkType: hard + "gcp-metadata@npm:^6.1.0": version: 6.1.0 resolution: "gcp-metadata@npm:6.1.0" @@ -12154,6 +13457,13 @@ __metadata: languageName: node linkType: hard +"google-logging-utils@npm:^0.0.2": + version: 0.0.2 + resolution: "google-logging-utils@npm:0.0.2" + checksum: 10/f8f5ec3087ef4563d12ee1afc603e6b42b4d703c1f10c9f37b3080e6f4a2e9554e0fd9dcdce97ded5a46ead465c706ff2bc791ad2ca478ed8dc62fdc4b06cac6 + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -12551,6 +13861,18 @@ __metadata: languageName: node linkType: hard +"import-in-the-middle@npm:^1.8.1": + version: 1.15.0 + resolution: "import-in-the-middle@npm:1.15.0" + dependencies: + acorn: "npm:^8.14.0" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10/a1ff65ea557ffe67e63dd67b411255fedd8622b324ff22ba99f4436b8fcf74da0333e62b4e8142f447e5db64a42ec9e65f926d50fa55e89c4e4d64626d8cf5f8 + languageName: node + linkType: hard + "import-local@npm:^3.0.2": version: 3.1.0 resolution: "import-local@npm:3.1.0" @@ -12781,6 +14103,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.16.1": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10/452b2c2fb7f889cbbf7e54609ef92cf6c24637c568acc7e63d166812a0fb365ae8a504c333a29add8bdb1686704068caa7f4e4b639b650dde4f00a038b8941fb + languageName: node + linkType: hard + "is-core-module@npm:^2.4.0": version: 2.15.1 resolution: "is-core-module@npm:2.15.1" @@ -14897,6 +16228,13 @@ __metadata: languageName: node linkType: hard +"module-details-from-path@npm:^1.0.3": + version: 1.0.4 + resolution: "module-details-from-path@npm:1.0.4" + checksum: 10/2ebfada5358492f6ab496b70f70a1042f2ee7a4c79d29467f59ed6704f741fb4461d7cecb5082144ed39a05fec4d19e9ff38b731c76228151be97227240a05b2 + languageName: node + linkType: hard + "moment-timezone@npm:^0.5.43": version: 0.5.43 resolution: "moment-timezone@npm:0.5.43" @@ -15849,6 +17187,33 @@ __metadata: languageName: node linkType: hard +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: 10/a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9 + languageName: node + linkType: hard + +"pg-protocol@npm:*": + version: 1.13.0 + resolution: "pg-protocol@npm:1.13.0" + checksum: 10/302cd3920df00f178519693557c34949d64c8b3af7e2c12772b14f61547b947e4c761b4ca2319dbba5b0906207bb1b535cbfc7006d40d47fd823e277c2690a71 + languageName: node + linkType: hard + +"pg-types@npm:^2.2.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: "npm:1.0.1" + postgres-array: "npm:~2.0.0" + postgres-bytea: "npm:~1.0.0" + postgres-date: "npm:~1.0.4" + postgres-interval: "npm:^1.1.0" + checksum: 10/87a84d4baa91378d3a3da6076c69685eb905d1087bf73525ae1ba84b291b9dd8738c6716b333d8eac6cec91bf087237adc3e9281727365e9cbab0d9d072778b1 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -15934,6 +17299,36 @@ __metadata: languageName: node linkType: hard +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 10/aff99e79714d1271fe942fec4ffa2007b755e7e7dc3d2feecae3f1ceecb86fd3637c8138037fc3d9e7ec369231eeb136843c0b25927bf1ce295245a40ef849b4 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.1 + resolution: "postgres-bytea@npm:1.0.1" + checksum: 10/fc5fa49f59ac1f0eba841db55bd6b6c2232d1575d1734311e2097a2d5fd8b58e1239cbd64eeaf0b6752268fe7d2819e002bf90b0afd333be9f2b9d157d2cd7e7 + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 10/571ef45bec4551bb5d608c31b79987d7a895141f7d6c7b82e936a52d23d97474c770c6143e5cf8936c1cdc8b0dfd95e79f8136bf56a90164182a60f242c19f2b + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: "npm:^4.0.0" + checksum: 10/746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2 + languageName: node + linkType: hard + "prebuild-install@npm:^7.1.1": version: 7.1.2 resolution: "prebuild-install@npm:7.1.2" @@ -16099,6 +17494,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.3.0, protobufjs@npm:^7.5.3": + version: 7.5.4 + resolution: "protobufjs@npm:7.5.4" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 10/88d677bb6f11a2ecec63fdd053dfe6d31120844d04e865efa9c8fbe0674cd077d6624ecfdf014018a20dcb114ae2a59c1b21966dd8073e920650c71370966439 + languageName: node + linkType: hard + "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -16365,6 +17780,17 @@ __metadata: languageName: node linkType: hard +"require-in-the-middle@npm:^7.1.1": + version: 7.5.2 + resolution: "require-in-the-middle@npm:7.5.2" + dependencies: + debug: "npm:^4.3.5" + module-details-from-path: "npm:^1.0.3" + resolve: "npm:^1.22.8" + checksum: 10/d8f137d72eec1c53987647d19cd3bd2c64d5417bcd06b9ac8f7a14e83924c1e7636e327df7d96066a2b446b41f50d0bc1856a521388d5e90ba5c3b18dd5ab4e8 + languageName: node + linkType: hard + "resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" @@ -16422,6 +17848,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.8": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/e1b2e738884a08de03f97ee71494335eba8c2b0feb1de9ae065e82c48997f349f77a2b10e8817e147cf610bfabc4b1cb7891ee8eaf5bf80d4ad514a34c4fab0a + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": version: 1.22.6 resolution: "resolve@patch:resolve@npm%3A1.22.6#optional!builtin::version=1.22.6&hash=c3c19d" @@ -16435,6 +17874,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/fd342cad25e52cd6f4f3d1716e189717f2522bfd6641109fe7aa372f32b5714a296ed7c238ddbe7ebb0c1ddfe0b7f71c9984171024c97cf1b2073e3e40ff71a8 + languageName: node + linkType: hard + "responselike@npm:^2.0.0": version: 2.0.1 resolution: "responselike@npm:2.0.1" @@ -16716,6 +18168,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.2": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10/26bdc6d58b29528f4142d29afb8526bc335f4fc04c4a10f2b98b217f277a031c66736bf82d3d3bb354a2f6a3ae50f18fd62b053c4ac3f294a3d10a61f5075b75 + languageName: node + linkType: hard + "seq-queue@npm:^0.0.5": version: 0.0.5 resolution: "seq-queue@npm:0.0.5" @@ -17191,6 +18652,13 @@ __metadata: languageName: node linkType: hard +"shimmer@npm:^1.2.1": + version: 1.2.1 + resolution: "shimmer@npm:1.2.1" + checksum: 10/aa0d6252ad1c682a4fdfda69e541be987f7a265ac7b00b1208e5e48cc68dc55f293955346ea4c71a169b7324b82c70f8400b3d3d2d60b2a7519f0a3522423250 + languageName: node + linkType: hard + "side-channel@npm:^1.0.4": version: 1.0.4 resolution: "side-channel@npm:1.0.4" @@ -17843,6 +19311,12 @@ __metadata: dependencies: "@aws-sdk/client-lambda": "npm:3.540.0" "@aws-sdk/client-sqs": "npm:3.540.0" + "@opentelemetry/api": "npm:1.9.0" + "@opentelemetry/auto-instrumentations-node": "npm:0.56.1" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.57.2" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-node": "npm:0.57.2" + "@opentelemetry/sdk-trace-node": "npm:1.30.1" "@types/better-sqlite3": "npm:7.6.12" "@types/jest": "npm:29.5.4" "@types/lodash": "npm:4.14.199" @@ -18967,6 +20441,17 @@ __metadata: "@hathor/healthcheck-lib": "npm:0.1.0" "@middy/core": "npm:2.5.7" "@middy/http-cors": "npm:2.5.7" + "@opentelemetry/api": "npm:1.9.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.57.2" + "@opentelemetry/instrumentation-aws-lambda": "npm:0.50.3" + "@opentelemetry/instrumentation-aws-sdk": "npm:0.48.0" + "@opentelemetry/instrumentation-http": "npm:0.57.2" + "@opentelemetry/instrumentation-mysql": "npm:0.43.0" + "@opentelemetry/instrumentation-redis": "npm:0.44.0" + "@opentelemetry/instrumentation-winston": "npm:0.43.0" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-node": "npm:0.57.2" + "@opentelemetry/sdk-trace-node": "npm:1.30.1" "@types/aws-lambda": "npm:8.10.95" "@types/jest": "npm:29.5.13" "@types/joi": "npm:17.2.3" From 79a6ee86ce1c9a7821cfa44fd60df6fef7df578b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 30 Mar 2026 10:23:32 -0300 Subject: [PATCH 02/10] feat(wallet-service): add OpenTelemetry instrumentation for Lambdas Manual SDK init (RFC Option B) with cherry-picked instrumentations to stay under Lambda's 250MB unzipped size limit (+39MB vs +120MB for the full auto-instrumentations-node meta-package). Instrumentations: aws-lambda, aws-sdk, http, mysql, redis, winston. Webpack entries prepended with tracing.ts for auto-instrumentation. - OTEL_SDK_DISABLED=true to skip all OTel module loading (kill switch) - OTEL_CONSOLE_DEBUG=true to print spans to CloudWatch logs - OTEL_EXPORTER_OTLP_ENDPOINT to enable OTLP export Cold start benchmark (A/B, n=7 each on getLatestBlock): - Init overhead: +706ms (1164ms -> 1870ms) - Warm handler median overhead: +6.3ms - Warm handler p95: periodic 60-200ms spikes from BatchSpanProcessor Co-Authored-By: Claude Opus 4.6 (1M context) --- .../docs/otel-cold-start-benchmark.md | 119 ++++++++++++++++++ packages/wallet-service/package.json | 11 ++ packages/wallet-service/serverless.yml | 4 + packages/wallet-service/src/tracing.ts | 63 ++++++++++ packages/wallet-service/webpack.config.js | 12 +- 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 packages/wallet-service/docs/otel-cold-start-benchmark.md create mode 100644 packages/wallet-service/src/tracing.ts diff --git a/packages/wallet-service/docs/otel-cold-start-benchmark.md b/packages/wallet-service/docs/otel-cold-start-benchmark.md new file mode 100644 index 00000000..0770834a --- /dev/null +++ b/packages/wallet-service/docs/otel-cold-start-benchmark.md @@ -0,0 +1,119 @@ +# OpenTelemetry Cold Start Benchmark Report + +**Date:** 2026-03-26 +**Environment:** dev-testnet (eu-central-1) +**Function:** `hathor-wallet-service-dev-testnet-getLatestBlock` +**Runtime:** Node.js 22.x, 256 MB memory +**Method:** A/B test — forced cold starts by updating function config between invocations, toggling `OTEL_SDK_DISABLED` env var + +## Instrumentation Config + +Cherry-picked instrumentations (not the full `auto-instrumentations-node` meta-package): +- `@opentelemetry/instrumentation-aws-lambda` +- `@opentelemetry/instrumentation-aws-sdk` +- `@opentelemetry/instrumentation-http` +- `@opentelemetry/instrumentation-mysql` +- `@opentelemetry/instrumentation-redis` +- `@opentelemetry/instrumentation-winston` + +## Test 1: Cold Start Overhead + +Console exporter enabled (`OTEL_CONSOLE_DEBUG=true`), no OTLP endpoint configured. Every invocation is a forced cold start. + +### WITH OTel (n=7) + +| # | Init Duration (ms) | Cold Handler Duration (ms) | +|---|-------------------:|---------------------------:| +| 1 | 1979 | 1231 | +| 2 | 1846 | 1297 | +| 3 | 1829 | 1246 | +| 4 | 1868 | 1256 | +| 5 | 1874 | 1241 | +| 6 | 1822 | 1261 | +| 7 | 1872 | 1321 | + +### WITHOUT OTel (n=7) + +| # | Init Duration (ms) | Cold Handler Duration (ms) | +|---|-------------------:|---------------------------:| +| 1 | 1143 | 591 | +| 2 | 1170 | 615 | +| 3 | 1133 | 927 | +| 4 | 1142 | 949 | +| 5 | 1151 | 918 | +| 6 | 1163 | 610 | +| 7 | 1242 | 634 | + +> **Note on Cold Handler Duration:** These are the first invocation after a cold start, so they include one-time costs like establishing the MySQL connection pool (~500-900ms). This is **not** representative of warm handler performance. The difference between OTel ON (avg 1265ms) and OTel OFF (avg 749ms) in this column is inflated by the synchronous console exporter (`OTEL_CONSOLE_DEBUG=true`), which serializes spans to stdout on every call. + +### Cold Start Results + +| Metric | WITHOUT OTel | WITH OTel | Delta | +|--------|------------:|----------:|------:| +| **Avg Init** | 1164 ms | 1870 ms | **+706 ms (+60.7%)** | +| Min Init | 1133 ms | 1822 ms | +689 ms | +| Max Init | 1242 ms | 1979 ms | +737 ms | + +## Test 2: Warm Handler Duration (console debug OFF, production-like) + +OTel enabled, `OTEL_CONSOLE_DEBUG` off, no OTLP endpoint — simulates production config where spans are created but not exported. 10 warm invocations per phase. + +### OTel ON (n=10) + +| # | Duration (ms) | +|---|-------------:| +| 1 | 24.2 | +| 2 | 10.5 | +| 3 | 9.5 | +| 4 | 11.5 | +| 5 | 199.9 | +| 6 | 9.7 | +| 7 | 16.7 | +| 8 | 18.3 | +| 9 | 15.2 | +| 10 | 64.0 | + +### OTel OFF (n=10) + +| # | Duration (ms) | +|---|-------------:| +| 1 | 10.9 | +| 2 | 10.4 | +| 3 | 9.5 | +| 4 | 9.3 | +| 5 | 9.7 | +| 6 | 8.9 | +| 7 | 9.2 | +| 8 | 16.1 | +| 9 | 9.7 | +| 10 | 8.7 | + +### Warm Handler Results + +| Metric | OTel OFF | OTel ON | Delta | +|--------|--------:|-------:|------:| +| **Median** | 9.6 ms | 15.9 ms | **+6.3 ms** | +| Avg | 10.2 ms | 38.0 ms | +27.7 ms (skewed by spikes) | +| p95 | 16.1 ms | 199.9 ms | +184 ms | +| Min | 8.7 ms | 9.5 ms | +0.8 ms | +| Max | 16.1 ms | 199.9 ms | +184 ms | + +The median overhead is ~6ms, but there are periodic spikes (64ms, 200ms) likely caused by the `BatchSpanProcessor` doing internal bookkeeping even without an exporter. In production with an OTLP exporter, the batch flush would also involve network I/O, though it runs asynchronously. + +## Key Takeaways + +1. **Cold start overhead is ~700ms**, within the RFC's estimated 200-800ms range. Results are highly consistent (tight min/max spread). + +2. **Warm invocation median overhead is ~6ms** — negligible for most use cases. However, periodic spikes of 60-200ms occur from internal `BatchSpanProcessor` activity. + +3. **The console exporter is expensive.** `OTEL_CONSOLE_DEBUG=true` adds ~500ms to every invocation due to synchronous span serialization. Use only for short debugging sessions, never in production. + +4. The warmup plugin keeps 13 functions hot with 5-minute pings, so most real user requests will not experience cold starts. + +## Recommendations + +- **Accept the cold start cost** — 700ms is manageable given warmup coverage. Most users will never hit a cold start. +- **Disable `OTEL_CONSOLE_DEBUG`** except when debugging — it adds ~500ms to every invocation. +- **Use `OTEL_SDK_DISABLED=true`** as a kill switch if issues arise in production. +- **Monitor p95 latency after enabling** — the periodic BatchSpanProcessor spikes could affect tail latency on latency-sensitive endpoints. +- **Consider provisioned concurrency** for latency-critical endpoints if the +700ms cold start is unacceptable. diff --git a/packages/wallet-service/package.json b/packages/wallet-service/package.json index d121ed60..76431263 100644 --- a/packages/wallet-service/package.json +++ b/packages/wallet-service/package.json @@ -21,6 +21,17 @@ "@hathor/healthcheck-lib": "0.1.0", "@middy/core": "2.5.7", "@middy/http-cors": "2.5.7", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/exporter-trace-otlp-http": "0.57.2", + "@opentelemetry/instrumentation-aws-lambda": "0.50.3", + "@opentelemetry/instrumentation-aws-sdk": "0.48.0", + "@opentelemetry/instrumentation-http": "0.57.2", + "@opentelemetry/instrumentation-mysql": "0.43.0", + "@opentelemetry/instrumentation-redis": "0.44.0", + "@opentelemetry/instrumentation-winston": "0.43.0", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-node": "0.57.2", + "@opentelemetry/sdk-trace-node": "1.30.1", "@types/redis": "2.8.28", "aws-lambda": "1.0.7", "axios": "0.21.1", diff --git a/packages/wallet-service/serverless.yml b/packages/wallet-service/serverless.yml index 6106d3eb..9963a930 100644 --- a/packages/wallet-service/serverless.yml +++ b/packages/wallet-service/serverless.yml @@ -211,6 +211,10 @@ provider: ALERT_MANAGER_REGION: ${env:ALERT_MANAGER_REGION} ALERT_MANAGER_TOPIC: ${env:ALERT_MANAGER_TOPIC} ALERT_MANAGER_ACCOUNT_ID: ${env:ALERT_MANAGER_ACCOUNT_ID} + # OpenTelemetry — manual SDK init (RFC Option B) + # OTEL_EXPORTER_OTLP_ENDPOINT must be set per-function (env budget is tight). + # Set OTEL_CONSOLE_DEBUG=true to print spans to CloudWatch logs. + OTEL_CONSOLE_DEBUG: ${env:OTEL_CONSOLE_DEBUG, ""} functions: getLatestBlock: diff --git a/packages/wallet-service/src/tracing.ts b/packages/wallet-service/src/tracing.ts new file mode 100644 index 00000000..8614d23e --- /dev/null +++ b/packages/wallet-service/src/tracing.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Skip all OTel initialization when disabled — avoids module loading cost. +if (process.env.OTEL_SDK_DISABLED !== 'true') { + // Use require() so that imports are fully skipped when disabled. + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { NodeSDK } = require('@opentelemetry/sdk-node'); + const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); + const { BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-node'); + const { Resource } = require('@opentelemetry/resources'); + const { AwsLambdaInstrumentation } = require('@opentelemetry/instrumentation-aws-lambda'); + const { AwsInstrumentation } = require('@opentelemetry/instrumentation-aws-sdk'); + const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); + const { MySQLInstrumentation } = require('@opentelemetry/instrumentation-mysql'); + const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis'); + const { WinstonInstrumentation } = require('@opentelemetry/instrumentation-winston'); + + const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; + const consoleDebug = process.env.OTEL_CONSOLE_DEBUG === 'true'; + + const spanProcessors: any[] = []; + + if (endpoint) { + spanProcessors.push(new BatchSpanProcessor( + new OTLPTraceExporter(), + { + maxQueueSize: 2048, + maxExportBatchSize: 512, + scheduledDelayMillis: 5000, + exportTimeoutMillis: 5000, + }, + )); + } + + if (consoleDebug) { + spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter())); + } + + const sdk = new NodeSDK({ + resource: new Resource({ + 'service.name': process.env.OTEL_SERVICE_NAME || 'wallet-service-lambda', + 'deployment.environment': process.env.STAGE || 'local', + }), + ...(spanProcessors.length && { spanProcessors }), + instrumentations: [ + new AwsLambdaInstrumentation({ + disableAwsContextPropagation: true, + }), + new AwsInstrumentation(), + new HttpInstrumentation(), + new MySQLInstrumentation(), + new RedisInstrumentation(), + new WinstonInstrumentation(), + ], + }); + + sdk.start(); +} diff --git a/packages/wallet-service/webpack.config.js b/packages/wallet-service/webpack.config.js index b4a4a990..bb51fbb8 100644 --- a/packages/wallet-service/webpack.config.js +++ b/packages/wallet-service/webpack.config.js @@ -3,10 +3,20 @@ const path = require('path'); const slsw = require('serverless-webpack'); const nodeExternals = require('webpack-node-externals'); +// Prepend OTel tracing init to every handler entry so auto-instrumentation +// patches libraries (mysql, redis, http, aws-sdk) before they are imported. +const tracingEntry = path.resolve(__dirname, './src/tracing.ts'); +const entries = Object.fromEntries( + Object.entries(slsw.lib.entries).map(([key, val]) => [ + key, + [tracingEntry, val], + ]), +); + module.exports = { context: __dirname, mode: slsw.lib.webpack.isLocal ? 'development' : 'production', - entry: slsw.lib.entries, + entry: entries, devtool: slsw.lib.webpack.isLocal ? 'eval-cheap-module-source-map' : 'source-map', resolve: { extensions: ['.js', '.mjs', '.json', '.ts'], From 44c6a46e653cf7aabd71c7126841700b718e9422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 6 Apr 2026 11:57:23 -0300 Subject: [PATCH 03/10] fix(daemon): remove hardcoded dynasty default from FULLNODE_HOST FULLNODE_HOST should be explicitly set, not default to hathor-dynasty.network. Co-Authored-By: Claude Opus 4.6 --- packages/daemon/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/daemon/package.json b/packages/daemon/package.json index e3e4068c..a37989cf 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -17,7 +17,7 @@ "dev:down": "docker compose -f docker-compose.dev.yml down", "dev:destroy": "docker compose -f docker-compose.dev.yml down -v", "dev:migrate": "cd ../.. && DB_ENDPOINT=localhost DB_NAME=wallet_service DB_USER=hathor DB_PASS=hathor DB_PORT=3306 npx sequelize-cli db:migrate && DB_ENDPOINT=localhost DB_NAME=wallet_service DB_USER=hathor DB_PASS=hathor DB_PORT=3306 npx sequelize-cli db:seed:all", - "dev:fetch-ids": "FULLNODE_WEBSOCKET_BASEURL=${FULLNODE_HOST:-hathor-dynasty.network} node ../../scripts/fetch-fullnode-ids.js", + "dev:fetch-ids": "FULLNODE_WEBSOCKET_BASEURL=${FULLNODE_HOST} node ../../scripts/fetch-fullnode-ids.js", "replay-balance": "yarn dlx ts-node src/scripts/replay-balance.ts", "watch": "tsc -w", "test_images_up": "docker compose -f ./__tests__/integration/scripts/docker-compose.yml up -d", From adb1a2e274e5d2ebb6be8b25694dea7b550445f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 6 Apr 2026 12:01:07 -0300 Subject: [PATCH 04/10] fix(daemon): fix indentation inside startActiveSpan callbacks All startActiveSpan callback bodies were missing one level of indentation. Co-Authored-By: Claude Opus 4.6 --- packages/daemon/src/services/index.ts | 1216 ++++++++++++------------- 1 file changed, 608 insertions(+), 608 deletions(-) diff --git a/packages/daemon/src/services/index.ts b/packages/daemon/src/services/index.ts index 48ded184..0eaf0b93 100644 --- a/packages/daemon/src/services/index.ts +++ b/packages/daemon/src/services/index.ts @@ -122,124 +122,124 @@ const DUPLICATE_TX_ALERT_GRACE_PERIOD = 10; // seconds export const metadataDiff = async (_context: Context, event: Event) => { return tracer.startActiveSpan('metadataDiff', async (span) => { - const fullNodeEvent = (event as Extract).event as StandardFullNodeEvent; - const { - hash, - metadata: { voided_by, first_block, nc_execution }, - } = fullNodeEvent.event.data; - - span.setAttribute('tx.hash', hash); - - const isRetryableError = (error: any): boolean => { - const code = error?.code; - return code === 'ETIMEDOUT' - || code === 'ECONNREFUSED' - || code === 'ECONNRESET' - || code === 'PROTOCOL_CONNECTION_LOST'; - }; + const fullNodeEvent = (event as Extract).event as StandardFullNodeEvent; + const { + hash, + metadata: { voided_by, first_block, nc_execution }, + } = fullNodeEvent.event.data; - try { - return await retryWithBackoff( - async () => { - let mysql: MysqlConnection | undefined; - try { - mysql = await getDbConnection(); - const dbTx: DbTransaction | null = await withSpan('getTransactionById', () => getTransactionById(mysql!, hash)); + span.setAttribute('tx.hash', hash); + + const isRetryableError = (error: any): boolean => { + const code = error?.code; + return code === 'ETIMEDOUT' + || code === 'ECONNREFUSED' + || code === 'ECONNRESET' + || code === 'PROTOCOL_CONNECTION_LOST'; + }; + + try { + return await retryWithBackoff( + async () => { + let mysql: MysqlConnection | undefined; + try { + mysql = await getDbConnection(); + const dbTx: DbTransaction | null = await withSpan('getTransactionById', () => getTransactionById(mysql!, hash)); + + if (!dbTx) { + if (voided_by.length > 0) { + // No need to add voided transactions + return { + types: [METADATA_DIFF_EVENT_TYPES.IGNORE], + originalEvent: event, + }; + } - if (!dbTx) { - if (voided_by.length > 0) { - // No need to add voided transactions return { - types: [METADATA_DIFF_EVENT_TYPES.IGNORE], + types: [METADATA_DIFF_EVENT_TYPES.TX_NEW], originalEvent: event, }; } - return { - types: [METADATA_DIFF_EVENT_TYPES.TX_NEW], - originalEvent: event, - }; - } + // Mutually exclusive: voided/unvoided/new take priority + // Tx is voided + if (voided_by.length > 0) { + // Was it voided on the database? + if (!dbTx.voided) { + return { + types: [METADATA_DIFF_EVENT_TYPES.TX_VOIDED], + originalEvent: event, + }; + } - // Mutually exclusive: voided/unvoided/new take priority - // Tx is voided - if (voided_by.length > 0) { - // Was it voided on the database? - if (!dbTx.voided) { return { - types: [METADATA_DIFF_EVENT_TYPES.TX_VOIDED], + types: [METADATA_DIFF_EVENT_TYPES.IGNORE], originalEvent: event, }; } - return { - types: [METADATA_DIFF_EVENT_TYPES.IGNORE], - originalEvent: event, - }; - } - - // Tx was voided in the database but is not anymore - if (dbTx.voided && voided_by.length <= 0) { - return { - types: [METADATA_DIFF_EVENT_TYPES.TX_UNVOIDED], - originalEvent: event, - }; - } + // Tx was voided in the database but is not anymore + if (dbTx.voided && voided_by.length <= 0) { + return { + types: [METADATA_DIFF_EVENT_TYPES.TX_UNVOIDED], + originalEvent: event, + }; + } - // Independent changes: collect all into array - const types: string[] = []; + // Independent changes: collect all into array + const types: string[] = []; - // Check if nc_execution changed from 'success' to something else. - // If the tx has nano-created tokens in the database (tokens where token_id != tx_id), - // those tokens were created when nc_execution was 'success'. - // If nc_execution is now NOT 'success', we should delete those tokens. - if (nc_execution !== 'success') { - const tokensCreated = await getTokensCreatedByTx(mysql, hash); - const nanoTokens = tokensCreated.filter(tokenId => tokenId !== hash); + // Check if nc_execution changed from 'success' to something else. + // If the tx has nano-created tokens in the database (tokens where token_id != tx_id), + // those tokens were created when nc_execution was 'success'. + // If nc_execution is now NOT 'success', we should delete those tokens. + if (nc_execution !== 'success') { + const tokensCreated = await getTokensCreatedByTx(mysql, hash); + const nanoTokens = tokensCreated.filter(tokenId => tokenId !== hash); - if (nanoTokens.length > 0) { - types.push(METADATA_DIFF_EVENT_TYPES.NC_EXEC_VOIDED); + if (nanoTokens.length > 0) { + types.push(METADATA_DIFF_EVENT_TYPES.NC_EXEC_VOIDED); + } } - } - // Handle first_block changes (NULL -> value OR value -> NULL) - const eventFirstBlock: string | null = first_block ?? null; - const dbFirstBlock: string | null = dbTx.first_block ?? null; + // Handle first_block changes (NULL -> value OR value -> NULL) + const eventFirstBlock: string | null = first_block ?? null; + const dbFirstBlock: string | null = dbTx.first_block ?? null; - if (eventFirstBlock !== dbFirstBlock) { - types.push(METADATA_DIFF_EVENT_TYPES.TX_FIRST_BLOCK); - } + if (eventFirstBlock !== dbFirstBlock) { + types.push(METADATA_DIFF_EVENT_TYPES.TX_FIRST_BLOCK); + } - if (types.length === 0) { - types.push(METADATA_DIFF_EVENT_TYPES.IGNORE); - } + if (types.length === 0) { + types.push(METADATA_DIFF_EVENT_TYPES.IGNORE); + } - return { - types, - originalEvent: event, - }; - } finally { - if (mysql) { - mysql.destroy(); + return { + types, + originalEvent: event, + }; + } finally { + if (mysql) { + mysql.destroy(); + } } - } - }, - { - maxRetries: 5, - initialDelayMs: 1000, - maxDelayMs: 10000, - backoffMultiplier: 2, - retryableErrors: isRetryableError, - }, - ); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.error('metadataDiff error', { eventId: fullNodeEvent.event.id, error: e }); - return Promise.reject(e); - } finally { - span.end(); - } + }, + { + maxRetries: 5, + initialDelayMs: 1000, + maxDelayMs: 10000, + backoffMultiplier: 2, + retryableErrors: isRetryableError, + }, + ); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.error('metadataDiff error', { eventId: fullNodeEvent.event.id, error: e }); + return Promise.reject(e); + } finally { + span.end(); + } }); }; @@ -298,351 +298,351 @@ export function isNanoContract(headers: EventTxHeader[]) { */ export const handleVertexAccepted = async (context: Context, _event: Event) => { return tracer.startActiveSpan('handleVertexAccepted', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); - const { - NETWORK, - STAGE, - SERVERLESS_DEPLOY_PREFIX, - PUSH_NOTIFICATION_ENABLED, - } = getConfig(); + const mysql = await getDbConnection(); + await mysql.beginTransaction(); + const { + NETWORK, + STAGE, + SERVERLESS_DEPLOY_PREFIX, + PUSH_NOTIFICATION_ENABLED, + } = getConfig(); - try { - const fullNodeEvent = context.event as StandardFullNodeEvent; - const now = getUnixTimestamp(); - const blockRewardLock = context.rewardMinBlocks; + try { + const fullNodeEvent = context.event as StandardFullNodeEvent; + const now = getUnixTimestamp(); + const blockRewardLock = context.rewardMinBlocks; - if (!blockRewardLock) { - throw new Error('No block reward lock set'); - } + if (!blockRewardLock) { + throw new Error('No block reward lock set'); + } - const fullNodeData = fullNodeEvent.event.data; + const fullNodeData = fullNodeEvent.event.data; - const { - hash, - metadata, - timestamp, - version, - weight, - outputs, - inputs, - nonce, - tokens, - token_name, - token_symbol, - parents, - headers = [], - } = fullNodeData; + const { + hash, + metadata, + timestamp, + version, + weight, + outputs, + inputs, + nonce, + tokens, + token_name, + token_symbol, + parents, + headers = [], + } = fullNodeData; - span.setAttribute('tx.hash', hash); - span.setAttribute('tx.version', version); + span.setAttribute('tx.hash', hash); + span.setAttribute('tx.version', version); - const isNano = isNanoContract(headers); + const isNano = isNanoContract(headers); - const dbTx: DbTransaction | null = await withSpan('getTransactionById', () => getTransactionById(mysql, hash)); + const dbTx: DbTransaction | null = await withSpan('getTransactionById', () => getTransactionById(mysql, hash)); - if (dbTx) { - const daemonUptime = getDaemonUptime(); - // We do not log if the daemon has just started, because it's expected that - // we receive an initial duplicate transaction from the fullnode in this case. - if (daemonUptime < DUPLICATE_TX_ALERT_GRACE_PERIOD) { - return; - } + if (dbTx) { + const daemonUptime = getDaemonUptime(); + // We do not log if the daemon has just started, because it's expected that + // we receive an initial duplicate transaction from the fullnode in this case. + if (daemonUptime < DUPLICATE_TX_ALERT_GRACE_PERIOD) { + return; + } - logger.error(`Transaction ${hash} already in the database and the daemon has not been recently restarted (uptime of ${daemonUptime} seconds). This is unexpected.`); + logger.error(`Transaction ${hash} already in the database and the daemon has not been recently restarted (uptime of ${daemonUptime} seconds). This is unexpected.`); - // This might happen if the service has been recently restarted, - // so we should raise the alert and just ignore the tx - return; - } + // This might happen if the service has been recently restarted, + // so we should raise the alert and just ignore the tx + return; + } - let height: number | null = metadata.height; + let height: number | null = metadata.height; - if (!isBlock(version) && !metadata.first_block) { - height = null; - } + if (!isBlock(version) && !metadata.first_block) { + height = null; + } - const txOutputs: TxOutputWithIndex[] = prepareOutputs(outputs, tokens); - const txInputs: TxInput[] = prepareInputs(inputs, tokens); + const txOutputs: TxOutputWithIndex[] = prepareOutputs(outputs, tokens); + const txInputs: TxInput[] = prepareInputs(inputs, tokens); - let heightlock: number | null = null; - if (isBlock(version)) { - if (typeof height !== 'number' && !height) { - throw new Error('Block with no height set in metadata.'); - } + let heightlock: number | null = null; + if (isBlock(version)) { + if (typeof height !== 'number' && !height) { + throw new Error('Block with no height set in metadata.'); + } - // unlock older blocks - const utxos = await getUtxosLockedAtHeight(mysql, now, height); + // unlock older blocks + const utxos = await getUtxosLockedAtHeight(mysql, now, height); - if (utxos.length > 0) { - logger.debug(`Block transaction, unlocking ${utxos.length} locked utxos at height ${height}`); - await unlockUtxos(mysql, utxos, false); - } + if (utxos.length > 0) { + logger.debug(`Block transaction, unlocking ${utxos.length} locked utxos at height ${height}`); + await unlockUtxos(mysql, utxos, false); + } - // set heightlock - heightlock = height + blockRewardLock; + // set heightlock + heightlock = height + blockRewardLock; - // get the first output address and add miner to the miners table - // PoA blocks may not have outputs, so we need to check first - if (outputs.length > 0) { - const blockRewardOutput = outputs[0]; - if (isDecodedValid(blockRewardOutput.decoded, ['address'])) { - await addMiner(mysql, blockRewardOutput.decoded!.address, hash); + // get the first output address and add miner to the miners table + // PoA blocks may not have outputs, so we need to check first + if (outputs.length > 0) { + const blockRewardOutput = outputs[0]; + if (isDecodedValid(blockRewardOutput.decoded, ['address'])) { + await addMiner(mysql, blockRewardOutput.decoded!.address, hash); + } } - } - // here we check if we have any utxos on our database that is locked but - // has its timelock < now - // - // we've decided to do this here considering that it is acceptable to have - // a delay between the actual timelock expiration time and the next block - // (that will unlock it). This delay is only perceived on the wallet as the - // sync mechanism will unlock the timelocked utxos as soon as they are seen - // on a received transaction. - await unlockTimelockedUtxos(mysql, now); - } + // here we check if we have any utxos on our database that is locked but + // has its timelock < now + // + // we've decided to do this here considering that it is acceptable to have + // a delay between the actual timelock expiration time and the next block + // (that will unlock it). This delay is only perceived on the wallet as the + // sync mechanism will unlock the timelocked utxos as soon as they are seen + // on a received transaction. + await unlockTimelockedUtxos(mysql, now); + } - // check if any of the inputs are still marked as locked and update tables accordingly. - // See remarks on getLockedUtxoFromInputs for more explanation. It's important to perform this - // before updating the balances - const lockedInputs = await getLockedUtxoFromInputs(mysql, inputs); - await unlockUtxos(mysql, lockedInputs, true); - - // add transaction outputs to the tx_outputs table - markLockedOutputs(txOutputs, now, heightlock !== null); - - // Add the transaction - const firstBlock: string | null = metadata.first_block ?? null; - logger.debug('Will add the tx with height', height); - // TODO: add is_nanocontract to transaction table? - await withSpan('addOrUpdateTx', () => addOrUpdateTx( - mysql, - hash, - height, - timestamp, - version, - weight, - firstBlock, - )); + // check if any of the inputs are still marked as locked and update tables accordingly. + // See remarks on getLockedUtxoFromInputs for more explanation. It's important to perform this + // before updating the balances + const lockedInputs = await getLockedUtxoFromInputs(mysql, inputs); + await unlockUtxos(mysql, lockedInputs, true); + + // add transaction outputs to the tx_outputs table + markLockedOutputs(txOutputs, now, heightlock !== null); + + // Add the transaction + const firstBlock: string | null = metadata.first_block ?? null; + logger.debug('Will add the tx with height', height); + // TODO: add is_nanocontract to transaction table? + await withSpan('addOrUpdateTx', () => addOrUpdateTx( + mysql, + hash, + height, + timestamp, + version, + weight, + firstBlock, + )); - // Add utxos - await withSpan('addUtxos', () => addUtxos(mysql, hash, txOutputs, heightlock)); + // Add utxos + await withSpan('addUtxos', () => addUtxos(mysql, hash, txOutputs, heightlock)); - // Mark tx utxos as spent - await withSpan('updateTxOutputSpentBy', () => updateTxOutputSpentBy(mysql, txInputs, hash)); + // Mark tx utxos as spent + await withSpan('updateTxOutputSpentBy', () => updateTxOutputSpentBy(mysql, txInputs, hash)); - // Genesis tx has no inputs and outputs, so nothing to be updated, avoid it - // Nano contracts are a special case since they can have an address to update even without inputs/outputs - if (inputs.length > 0 || outputs.length > 0 || isNano) { - const tokenList: string[] = getTokenListFromInputsAndOutputs(txInputs, txOutputs); + // Genesis tx has no inputs and outputs, so nothing to be updated, avoid it + // Nano contracts are a special case since they can have an address to update even without inputs/outputs + if (inputs.length > 0 || outputs.length > 0 || isNano) { + const tokenList: string[] = getTokenListFromInputsAndOutputs(txInputs, txOutputs); - // Update transaction count with the new tx - await incrementTokensTxCount(mysql, tokenList); + // Update transaction count with the new tx + await incrementTokensTxCount(mysql, tokenList); - const addressBalanceMap: StringMap = getAddressBalanceMap(txInputs, txOutputs, headers); + const addressBalanceMap: StringMap = getAddressBalanceMap(txInputs, txOutputs, headers); - // update address tables (address, address_balance, address_tx_history) - await withSpan('updateAddressTablesWithTx', () => updateAddressTablesWithTx(mysql, hash, timestamp, addressBalanceMap)); + // update address tables (address, address_balance, address_tx_history) + await withSpan('updateAddressTablesWithTx', () => updateAddressTablesWithTx(mysql, hash, timestamp, addressBalanceMap)); - // for the addresses present on the tx, check if there are any wallets associated - const addressWalletMap: StringMap = await withSpan('getAddressWalletInfo', () => getAddressWalletInfo(mysql, Object.keys(addressBalanceMap))); + // for the addresses present on the tx, check if there are any wallets associated + const addressWalletMap: StringMap = await withSpan('getAddressWalletInfo', () => getAddressWalletInfo(mysql, Object.keys(addressBalanceMap))); - const addressesPerWallet = Object.entries(addressWalletMap).reduce( - (result: StringMap<{ addresses: string[], walletDetails: Wallet }>, [address, wallet]: [string, Wallet]) => { - const { walletId } = wallet; + const addressesPerWallet = Object.entries(addressWalletMap).reduce( + (result: StringMap<{ addresses: string[], walletDetails: Wallet }>, [address, wallet]: [string, Wallet]) => { + const { walletId } = wallet; - // Initialize the array if the walletId is not yet a key in result - if (!result[walletId]) { - result[walletId] = { - addresses: [], - walletDetails: wallet, + // Initialize the array if the walletId is not yet a key in result + if (!result[walletId]) { + result[walletId] = { + addresses: [], + walletDetails: wallet, + } } - } - // Add the current key to the array - result[walletId].addresses.push(address); + // Add the current key to the array + result[walletId].addresses.push(address); - return result; - }, {}); + return result; + }, {}); - const seenWallets = Object.keys(addressesPerWallet); + const seenWallets = Object.keys(addressesPerWallet); - // Convert to array format expected by getMaxIndicesForWallets - const walletDataArray = Object.entries(addressesPerWallet).map(([walletId, data]) => ({ - walletId, - addresses: data.addresses - })); + // Convert to array format expected by getMaxIndicesForWallets + const walletDataArray = Object.entries(addressesPerWallet).map(([walletId, data]) => ({ + walletId, + addresses: data.addresses + })); - // Get all max indices in a single query - const walletIndices = await getMaxIndicesForWallets(mysql, walletDataArray); + // Get all max indices in a single query + const walletIndices = await getMaxIndicesForWallets(mysql, walletDataArray); - // Process each wallet - for (const [walletId, data] of Object.entries(addressesPerWallet)) { - const { walletDetails } = data; - const indices = walletIndices.get(walletId); + // Process each wallet + for (const [walletId, data] of Object.entries(addressesPerWallet)) { + const { walletDetails } = data; + const indices = walletIndices.get(walletId); - if (!indices) { - // This is unexpected as we just queried for this wallet - logger.error('Failed to get indices for wallet', { walletId }); - continue; - } + if (!indices) { + // This is unexpected as we just queried for this wallet + logger.error('Failed to get indices for wallet', { walletId }); + continue; + } - const { maxAmongAddresses, maxWalletIndex } = indices; + const { maxAmongAddresses, maxWalletIndex } = indices; - if (!maxAmongAddresses || !maxWalletIndex) { - // Do nothing, wallet is most likely not loaded yet. - if (walletDetails.status === WalletStatus.READY) { - logger.error('[ERROR] A wallet marked as READY does not have a max wallet index or address index was not found in the database'); + if (!maxAmongAddresses || !maxWalletIndex) { + // Do nothing, wallet is most likely not loaded yet. + if (walletDetails.status === WalletStatus.READY) { + logger.error('[ERROR] A wallet marked as READY does not have a max wallet index or address index was not found in the database'); + } + continue; } - continue; - } - const diff = maxWalletIndex - maxAmongAddresses; + const diff = maxWalletIndex - maxAmongAddresses; - if (diff < walletDetails.maxGap) { - // We need to generate addresses - const addresses = await generateAddresses(NETWORK as string, walletDetails.xpubkey, maxWalletIndex + 1, walletDetails.maxGap - diff); - await addNewAddresses(mysql, walletId, addresses, maxAmongAddresses); + if (diff < walletDetails.maxGap) { + // We need to generate addresses + const addresses = await generateAddresses(NETWORK as string, walletDetails.xpubkey, maxWalletIndex + 1, walletDetails.maxGap - diff); + await addNewAddresses(mysql, walletId, addresses, maxAmongAddresses); + } } - } - - // update wallet_balance and wallet_tx_history tables - const walletBalanceMap: StringMap = getWalletBalanceMap(addressWalletMap, addressBalanceMap); - await withSpan('updateWalletTablesWithTx', () => updateWalletTablesWithTx(mysql, hash, timestamp, walletBalanceMap)); - // prepare the transaction data to be sent to the SQS queue - const txData: Transaction = { - tx_id: hash, - nonce, - timestamp, - version, - voided: metadata.voided_by.length > 0, - weight, - parents, - inputs: txInputs, - outputs: txOutputs, - headers, - height: metadata.height, - token_name, - token_symbol, - signal_bits: 0, // TODO: we should actually receive this and store in the database - }; + // update wallet_balance and wallet_tx_history tables + const walletBalanceMap: StringMap = getWalletBalanceMap(addressWalletMap, addressBalanceMap); + await withSpan('updateWalletTablesWithTx', () => updateWalletTablesWithTx(mysql, hash, timestamp, walletBalanceMap)); + + // prepare the transaction data to be sent to the SQS queue + const txData: Transaction = { + tx_id: hash, + nonce, + timestamp, + version, + voided: metadata.voided_by.length > 0, + weight, + parents, + inputs: txInputs, + outputs: txOutputs, + headers, + height: metadata.height, + token_name, + token_symbol, + signal_bits: 0, // TODO: we should actually receive this and store in the database + }; - try { - if (seenWallets.length > 0) { - await sendRealtimeTx( - Array.from(seenWallets), - txData, - ); + try { + if (seenWallets.length > 0) { + await sendRealtimeTx( + Array.from(seenWallets), + txData, + ); + } + } catch (e) { + logger.error('Failed to send transaction to SQS queue'); + logger.error(e); } - } catch (e) { - logger.error('Failed to send transaction to SQS queue'); - logger.error(e); - } - try { - if (PUSH_NOTIFICATION_ENABLED) { - const walletBalanceMap = await getWalletBalancesForTx(mysql, txData); - const { length: hasAffectWallets } = Object.keys(walletBalanceMap); - if (hasAffectWallets) { - invokeOnTxPushNotificationRequestedLambda(walletBalanceMap) - .catch((err: Error) => logger.error('Error on invokeOnTxPushNotificationRequestedLambda invocation', err)); + try { + if (PUSH_NOTIFICATION_ENABLED) { + const walletBalanceMap = await getWalletBalancesForTx(mysql, txData); + const { length: hasAffectWallets } = Object.keys(walletBalanceMap); + if (hasAffectWallets) { + invokeOnTxPushNotificationRequestedLambda(walletBalanceMap) + .catch((err: Error) => logger.error('Error on invokeOnTxPushNotificationRequestedLambda invocation', err)); + } } + } catch (e) { + logger.error('Failed to send push notification to wallet-service lambda'); + logger.error(e); } - } catch (e) { - logger.error('Failed to send push notification to wallet-service lambda'); - logger.error(e); - } - const network = new hathorLib.Network(NETWORK); + const network = new hathorLib.Network(NETWORK); - // Call to process the data for NFT handling (if applicable) - // This process is not critical, so we run it in a fire-and-forget manner, not waiting for the promise. - NftUtils.processNftEvent(fullNodeData, STAGE, SERVERLESS_DEPLOY_PREFIX, network, logger) - .catch((err: unknown) => logger.error('[ALERT] Error processing NFT event', err)); - } + // Call to process the data for NFT handling (if applicable) + // This process is not critical, so we run it in a fire-and-forget manner, not waiting for the promise. + NftUtils.processNftEvent(fullNodeData, STAGE, SERVERLESS_DEPLOY_PREFIX, network, logger) + .catch((err: unknown) => logger.error('[ALERT] Error processing NFT event', err)); + } - // Need to check if there is a nano header and update the nc_address's seqnum if needed - for (const header of headers) { - if (isNanoHeader(header)) { - const txseqnum = header.nc_seqnum; - const cachedSeqnum = await getAddressSeqnum(mysql, header.nc_address); - if (txseqnum > cachedSeqnum) { - // The tx seqnum is higher than the cached one so we need to save the tx deqnum - await setAddressSeqnum(mysql, header.nc_address, header.nc_seqnum); + // Need to check if there is a nano header and update the nc_address's seqnum if needed + for (const header of headers) { + if (isNanoHeader(header)) { + const txseqnum = header.nc_seqnum; + const cachedSeqnum = await getAddressSeqnum(mysql, header.nc_address); + if (txseqnum > cachedSeqnum) { + // The tx seqnum is higher than the cached one so we need to save the tx deqnum + await setAddressSeqnum(mysql, header.nc_address, header.nc_seqnum); + } } } - } - await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); + await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - await mysql.commit(); - } catch (e) { - await mysql.rollback(); - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.error('Error handling vertex accepted', e); + await mysql.commit(); + } catch (e) { + await mysql.rollback(); + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.error('Error handling vertex accepted', e); - throw e; - } finally { - mysql.destroy(); - span.end(); - } + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; export const handleVertexRemoved = async (context: Context, _event: Event) => { return tracer.startActiveSpan('handleVertexRemoved', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); + const mysql = await getDbConnection(); + await mysql.beginTransaction(); - try { - const fullNodeEvent = context.event as StandardFullNodeEvent; + try { + const fullNodeEvent = context.event as StandardFullNodeEvent; + + const { + hash, + outputs, + inputs, + tokens, + headers = [], + version, + } = fullNodeEvent.event.data; - const { - hash, - outputs, - inputs, - tokens, - headers = [], - version, - } = fullNodeEvent.event.data; + span.setAttribute('tx.hash', hash); - span.setAttribute('tx.hash', hash); + const dbTx: DbTransaction | null = await getTransactionById(mysql, hash); - const dbTx: DbTransaction | null = await getTransactionById(mysql, hash); + if (!dbTx) { + throw new Error(`VERTEX_REMOVED event received, but transaction ${hash} was not in the database.`); + } - if (!dbTx) { - throw new Error(`VERTEX_REMOVED event received, but transaction ${hash} was not in the database.`); - } + logger.info(`[VertexRemoved] Voiding tx: ${hash}`); - logger.info(`[VertexRemoved] Voiding tx: ${hash}`); + await voidTx( + mysql, + hash, + inputs, + outputs, + tokens, + headers, + version, + ); - await voidTx( - mysql, - hash, - inputs, - outputs, - tokens, - headers, - version, - ); + logger.info(`[VertexRemoved] Removing tx from database: ${hash}`); + await cleanupVoidedTx(mysql, hash); + await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); + await mysql.commit(); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.debug(e); + await mysql.rollback(); - logger.info(`[VertexRemoved] Removing tx from database: ${hash}`); - await cleanupVoidedTx(mysql, hash); - await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - await mysql.commit(); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.debug(e); - await mysql.rollback(); - - throw e; - } finally { - mysql.destroy(); - span.end(); - } + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; @@ -794,122 +794,122 @@ export const voidTx = async ( export const handleVoidedTx = async (context: Context) => { return tracer.startActiveSpan('handleVoidedTx', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); - - try { - const fullNodeEvent = context.event as StandardFullNodeEvent; + const mysql = await getDbConnection(); + await mysql.beginTransaction(); - const { - hash, - outputs, - inputs, - tokens, - headers = [], - version, - } = fullNodeEvent.event.data; + try { + const fullNodeEvent = context.event as StandardFullNodeEvent; + + const { + hash, + outputs, + inputs, + tokens, + headers = [], + version, + } = fullNodeEvent.event.data; + + span.setAttribute('tx.hash', hash); + logger.debug(`Will handle voided tx for ${hash}`); + await voidTx( + mysql, + hash, + inputs, + outputs, + tokens, + headers, + version, + ); + logger.debug(`Voided tx ${hash}`); + await mysql.commit(); + await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.debug(e); + await mysql.rollback(); - span.setAttribute('tx.hash', hash); - logger.debug(`Will handle voided tx for ${hash}`); - await voidTx( - mysql, - hash, - inputs, - outputs, - tokens, - headers, - version, - ); - logger.debug(`Voided tx ${hash}`); - await mysql.commit(); - await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.debug(e); - await mysql.rollback(); - - throw e; - } finally { - mysql.destroy(); - span.end(); - } + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; export const handleUnvoidedTx = async (context: Context) => { return tracer.startActiveSpan('handleUnvoidedTx', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); + const mysql = await getDbConnection(); + await mysql.beginTransaction(); - try { - const fullNodeEvent = context.event as StandardFullNodeEvent; + try { + const fullNodeEvent = context.event as StandardFullNodeEvent; - const { hash } = fullNodeEvent.event.data; + const { hash } = fullNodeEvent.event.data; - span.setAttribute('tx.hash', hash); - logger.debug(`Tx ${hash} got unvoided, cleaning up the database.`); + span.setAttribute('tx.hash', hash); + logger.debug(`Tx ${hash} got unvoided, cleaning up the database.`); - await cleanupVoidedTx(mysql, hash); + await cleanupVoidedTx(mysql, hash); - logger.debug(`Unvoided tx ${hash}`); + logger.debug(`Unvoided tx ${hash}`); - await mysql.commit(); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.debug(e); - await mysql.rollback(); + await mysql.commit(); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.debug(e); + await mysql.rollback(); - throw e; - } finally { - mysql.destroy(); - span.end(); - } + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; export const handleTxFirstBlock = async (context: Context) => { return tracer.startActiveSpan('handleTxFirstBlock', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); + const mysql = await getDbConnection(); + await mysql.beginTransaction(); - try { - const fullNodeEvent = context.event as StandardFullNodeEvent; + try { + const fullNodeEvent = context.event as StandardFullNodeEvent; - const { - hash, - metadata, - timestamp, - version, - weight, - } = fullNodeEvent.event.data; + const { + hash, + metadata, + timestamp, + version, + weight, + } = fullNodeEvent.event.data; - span.setAttribute('tx.hash', hash); - const firstBlock: string | null = metadata.first_block ?? null; - // When first_block is null, height should also be null (tx back in mempool) - const height: number | null = firstBlock ? metadata.height : null; + span.setAttribute('tx.hash', hash); + const firstBlock: string | null = metadata.first_block ?? null; + // When first_block is null, height should also be null (tx back in mempool) + const height: number | null = firstBlock ? metadata.height : null; - await addOrUpdateTx(mysql, hash, height, timestamp, version, weight, firstBlock); - await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); + await addOrUpdateTx(mysql, hash, height, timestamp, version, weight, firstBlock); + await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - if (firstBlock) { - logger.debug(`Confirmed tx ${hash} in block ${firstBlock}: ${fullNodeEvent.event.id}`); - } else { - logger.debug(`Tx ${hash} back to mempool (first_block=null): ${fullNodeEvent.event.id}`); - } + if (firstBlock) { + logger.debug(`Confirmed tx ${hash} in block ${firstBlock}: ${fullNodeEvent.event.id}`); + } else { + logger.debug(`Tx ${hash} back to mempool (first_block=null): ${fullNodeEvent.event.id}`); + } - await mysql.commit(); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.error('E: ', e); - await mysql.rollback(); - throw e; - } finally { - mysql.destroy(); - span.end(); - } + await mysql.commit(); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.error('E: ', e); + await mysql.rollback(); + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; @@ -927,41 +927,41 @@ export const handleTxFirstBlock = async (context: Context) => { */ export const handleNcExecVoided = async (context: Context) => { return tracer.startActiveSpan('handleNcExecVoided', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); + const mysql = await getDbConnection(); + await mysql.beginTransaction(); - try { - const fullNodeEvent = context.event as StandardFullNodeEvent; - const { hash } = fullNodeEvent.event.data; + try { + const fullNodeEvent = context.event as StandardFullNodeEvent; + const { hash } = fullNodeEvent.event.data; - span.setAttribute('tx.hash', hash); + span.setAttribute('tx.hash', hash); - // Get all tokens created by this transaction - const tokensCreated = await getTokensCreatedByTx(mysql, hash); + // Get all tokens created by this transaction + const tokensCreated = await getTokensCreatedByTx(mysql, hash); - if (tokensCreated.length > 0) { - // Filter out traditional CREATE_TOKEN_TX tokens (where token_id = tx_id) - // These should NOT be deleted because they're inherent to the transaction - const nanoTokens = tokensCreated.filter(tokenId => tokenId !== hash); + if (tokensCreated.length > 0) { + // Filter out traditional CREATE_TOKEN_TX tokens (where token_id = tx_id) + // These should NOT be deleted because they're inherent to the transaction + const nanoTokens = tokensCreated.filter(tokenId => tokenId !== hash); - if (nanoTokens.length > 0) { - logger.debug(`NC execution voided for tx ${hash}, deleting ${nanoTokens.length} nano-created tokens`); - await deleteTokens(mysql, nanoTokens); + if (nanoTokens.length > 0) { + logger.debug(`NC execution voided for tx ${hash}, deleting ${nanoTokens.length} nano-created tokens`); + await deleteTokens(mysql, nanoTokens); + } } - } - await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - await mysql.commit(); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.error('handleNcExecVoided error: ', e); - await mysql.rollback(); - throw e; - } finally { - mysql.destroy(); - span.end(); - } + await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); + await mysql.commit(); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.error('handleNcExecVoided error: ', e); + await mysql.rollback(); + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; @@ -1022,143 +1022,143 @@ export const fetchInitialState = async () => { export const handleReorgStarted = async (context: Context): Promise => { return tracer.startActiveSpan('handleReorgStarted', async (span) => { - try { - if (!context.event) { - throw new Error('No event in context'); - } + try { + if (!context.event) { + throw new Error('No event in context'); + } - const fullNodeEvent = context.event; - if (fullNodeEvent.event.type !== FullNodeEventTypes.REORG_STARTED) { - throw new Error('Invalid event type for REORG_STARTED'); - } + const fullNodeEvent = context.event; + if (fullNodeEvent.event.type !== FullNodeEventTypes.REORG_STARTED) { + throw new Error('Invalid event type for REORG_STARTED'); + } - const { reorg_size, previous_best_block, new_best_block, common_block } = fullNodeEvent.event.data; + const { reorg_size, previous_best_block, new_best_block, common_block } = fullNodeEvent.event.data; - span.setAttribute('reorg.size', reorg_size); - const { REORG_SIZE_INFO, REORG_SIZE_MINOR, REORG_SIZE_MAJOR, REORG_SIZE_CRITICAL } = getConfig(); + span.setAttribute('reorg.size', reorg_size); + const { REORG_SIZE_INFO, REORG_SIZE_MINOR, REORG_SIZE_MAJOR, REORG_SIZE_CRITICAL } = getConfig(); - const metadata = { - reorg_size, - previous_best_block, - new_best_block, - common_block, - }; + const metadata = { + reorg_size, + previous_best_block, + new_best_block, + common_block, + }; - if (reorg_size >= REORG_SIZE_CRITICAL) { - await addAlert( - 'Critical Reorg Detected', - `A critical reorg of size ${reorg_size} has occurred.`, - Severity.CRITICAL, - metadata, - logger, - ); - } else if (reorg_size >= REORG_SIZE_MAJOR) { - await addAlert( - 'Major Reorg Detected', - `A major reorg of size ${reorg_size} has occurred.`, - Severity.MAJOR, - metadata, - logger, - ); - } else if (reorg_size >= REORG_SIZE_MINOR) { - await addAlert( - 'Minor Reorg Detected', - `A minor reorg of size ${reorg_size} has occurred.`, - Severity.MINOR, - metadata, - logger, - ); - } else if (reorg_size >= REORG_SIZE_INFO) { - await addAlert( - 'Reorg Detected', - `A reorg of size ${reorg_size} has occurred.`, - Severity.INFO, - metadata, - logger, - ); + if (reorg_size >= REORG_SIZE_CRITICAL) { + await addAlert( + 'Critical Reorg Detected', + `A critical reorg of size ${reorg_size} has occurred.`, + Severity.CRITICAL, + metadata, + logger, + ); + } else if (reorg_size >= REORG_SIZE_MAJOR) { + await addAlert( + 'Major Reorg Detected', + `A major reorg of size ${reorg_size} has occurred.`, + Severity.MAJOR, + metadata, + logger, + ); + } else if (reorg_size >= REORG_SIZE_MINOR) { + await addAlert( + 'Minor Reorg Detected', + `A minor reorg of size ${reorg_size} has occurred.`, + Severity.MINOR, + metadata, + logger, + ); + } else if (reorg_size >= REORG_SIZE_INFO) { + await addAlert( + 'Reorg Detected', + `A reorg of size ${reorg_size} has occurred.`, + Severity.INFO, + metadata, + logger, + ); + } + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + throw e; + } finally { + span.end(); } - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - throw e; - } finally { - span.end(); - } }); }; export const handleTokenCreated = async (context: Context) => { return tracer.startActiveSpan('handleTokenCreated', async (span) => { - const mysql = await getDbConnection(); - await mysql.beginTransaction(); + const mysql = await getDbConnection(); + await mysql.beginTransaction(); - try { - const fullNodeEvent = context.event; - if (!fullNodeEvent) { - throw new Error('No event in context'); - } + try { + const fullNodeEvent = context.event; + if (!fullNodeEvent) { + throw new Error('No event in context'); + } - if (fullNodeEvent.event.type !== FullNodeEventTypes.TOKEN_CREATED) { - throw new Error('Invalid event type for TOKEN_CREATED'); - } + if (fullNodeEvent.event.type !== FullNodeEventTypes.TOKEN_CREATED) { + throw new Error('Invalid event type for TOKEN_CREATED'); + } - const { - token_uid, - token_name, - token_symbol, - token_version, - nc_exec_info, - } = fullNodeEvent.event.data; + const { + token_uid, + token_name, + token_symbol, + token_version, + nc_exec_info, + } = fullNodeEvent.event.data; + + span.setAttribute('token.uid', token_uid); + + logger.debug(`Handling TOKEN_CREATED event for token ${token_uid}: ${token_name} (${token_symbol}) v${token_version}`); + + // Store the mapping between token and the transaction that created it + // For regular CREATE_TOKEN_TX: nc_exec_info is null, token_uid equals tx_id + // For nano contract tokens: nc_exec_info.nc_tx contains the transaction hash + const txId = nc_exec_info?.nc_tx ?? token_uid; + const firstBlock = nc_exec_info?.nc_block ?? null; + + /** + * Handle reorg scenario: first_block changed + * + * When a nano contract re-executes in a different block during a reorg, + * the token_id might change even though tx_id stays the same. + * Delete tokens with old first_block before inserting the new one. + */ + const tokensWithOldBlock = await getReexecNanoTokens(mysql, txId, firstBlock); + if (tokensWithOldBlock.length > 0) { + logger.debug(`First block changed for tx ${txId}, deleting ${tokensWithOldBlock.length} tokens with old first_block`); + await deleteTokens(mysql, tokensWithOldBlock); + } - span.setAttribute('token.uid', token_uid); - - logger.debug(`Handling TOKEN_CREATED event for token ${token_uid}: ${token_name} (${token_symbol}) v${token_version}`); - - // Store the mapping between token and the transaction that created it - // For regular CREATE_TOKEN_TX: nc_exec_info is null, token_uid equals tx_id - // For nano contract tokens: nc_exec_info.nc_tx contains the transaction hash - const txId = nc_exec_info?.nc_tx ?? token_uid; - const firstBlock = nc_exec_info?.nc_block ?? null; - - /** - * Handle reorg scenario: first_block changed - * - * When a nano contract re-executes in a different block during a reorg, - * the token_id might change even though tx_id stays the same. - * Delete tokens with old first_block before inserting the new one. - */ - const tokensWithOldBlock = await getReexecNanoTokens(mysql, txId, firstBlock); - if (tokensWithOldBlock.length > 0) { - logger.debug(`First block changed for tx ${txId}, deleting ${tokensWithOldBlock.length} tokens with old first_block`); - await deleteTokens(mysql, tokensWithOldBlock); - } + // Check if this exact token already exists + const existingToken = await getTokenInformation(mysql, token_uid); - // Check if this exact token already exists - const existingToken = await getTokenInformation(mysql, token_uid); + if (!existingToken) { + // Insert the new token + await storeTokenInformation(mysql, token_uid, token_name, token_symbol, token_version); + await insertTokenCreation(mysql, token_uid, txId, firstBlock); + logger.debug(`Inserted new token ${token_uid} with first_block=${firstBlock}, version=${token_version}`); + } else { + logger.debug(`Token ${token_uid} already exists, skipping insertion`); + } - if (!existingToken) { - // Insert the new token - await storeTokenInformation(mysql, token_uid, token_name, token_symbol, token_version); - await insertTokenCreation(mysql, token_uid, txId, firstBlock); - logger.debug(`Inserted new token ${token_uid} with first_block=${firstBlock}, version=${token_version}`); - } else { - logger.debug(`Token ${token_uid} already exists, skipping insertion`); - } + await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - await dbUpdateLastSyncedEvent(mysql, fullNodeEvent.event.id); - - await mysql.commit(); - logger.debug(`Successfully stored token ${token_uid} created by tx ${txId}`); - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - logger.error('Error handling TOKEN_CREATED event', e); - await mysql.rollback(); - throw e; - } finally { - mysql.destroy(); - span.end(); - } + await mysql.commit(); + logger.debug(`Successfully stored token ${token_uid} created by tx ${txId}`); + } catch (e) { + span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); + span.recordException(e as Error); + logger.error('Error handling TOKEN_CREATED event', e); + await mysql.rollback(); + throw e; + } finally { + mysql.destroy(); + span.end(); + } }); }; From 76c2607506469c244053ce5717b2c46a9f253b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 8 Apr 2026 11:15:32 -0300 Subject: [PATCH 05/10] fix(wallet-service): enable AWS context propagation for Lambda tracing Allow Lambdas to pick up parent trace context from callers (e.g. daemon invoking Lambdas via AWS SDK), enabling end-to-end distributed traces. Co-Authored-By: Claude Opus 4.6 --- packages/wallet-service/src/tracing.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/wallet-service/src/tracing.ts b/packages/wallet-service/src/tracing.ts index 8614d23e..d944ea41 100644 --- a/packages/wallet-service/src/tracing.ts +++ b/packages/wallet-service/src/tracing.ts @@ -48,9 +48,7 @@ if (process.env.OTEL_SDK_DISABLED !== 'true') { }), ...(spanProcessors.length && { spanProcessors }), instrumentations: [ - new AwsLambdaInstrumentation({ - disableAwsContextPropagation: true, - }), + new AwsLambdaInstrumentation(), new AwsInstrumentation(), new HttpInstrumentation(), new MySQLInstrumentation(), From 2f2d8ef04c5a2c89d327da3449da981a283e8abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 8 Apr 2026 11:16:01 -0300 Subject: [PATCH 06/10] fix(daemon): remove duplicate tracedService wrapper from SyncMachine Each service function already creates its own span via startActiveSpan internally, so the tracedService wrapper in SyncMachine produced duplicate spans for every event. Register services directly instead. Co-Authored-By: Claude Opus 4.6 --- packages/daemon/src/machines/SyncMachine.ts | 45 +++++---------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/packages/daemon/src/machines/SyncMachine.ts b/packages/daemon/src/machines/SyncMachine.ts index acd88e39..addbf5ac 100644 --- a/packages/daemon/src/machines/SyncMachine.ts +++ b/packages/daemon/src/machines/SyncMachine.ts @@ -10,7 +10,6 @@ import { assign, spawn, } from 'xstate'; -import { trace, SpanStatusCode } from '@opentelemetry/api'; import { LRU } from '../utils'; import { WebSocketActor, HealthCheckActor, MonitoringActor } from '../actors'; import { @@ -32,32 +31,6 @@ import { checkForMissedEvents, } from '../services'; -const tracer = trace.getTracer('wallet-service-daemon'); - -/** - * Wraps an xstate service function with a root OTel span, so the span context - * is active for the entire event processing lifecycle (including child spans - * created inside the service). - */ -function tracedService( - name: string, - fn: (...args: any[]) => Promise, -): (...args: any[]) => Promise { - return (...args: any[]) => { - return tracer.startActiveSpan(`event.processing.${name}`, async (span) => { - try { - const result = await fn(...args); - return result; - } catch (e) { - span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) }); - span.recordException(e as Error); - throw e; - } finally { - span.end(); - } - }); - }; -} import { hasNextChange, metadataChanged, @@ -420,16 +393,16 @@ export const SyncMachine = Machine({ }, }, { services: { - handleVertexAccepted: tracedService('handleVertexAccepted', handleVertexAccepted), - handleVertexRemoved: tracedService('handleVertexRemoved', handleVertexRemoved), - handleVoidedTx: tracedService('handleVoidedTx', handleVoidedTx), - handleTxFirstBlock: tracedService('handleTxFirstBlock', handleTxFirstBlock), - handleNcExecVoided: tracedService('handleNcExecVoided', handleNcExecVoided), - handleUnvoidedTx: tracedService('handleUnvoidedTx', handleUnvoidedTx), - handleReorgStarted: tracedService('handleReorgStarted', handleReorgStarted), - handleTokenCreated: tracedService('handleTokenCreated', handleTokenCreated), + handleVertexAccepted, + handleVertexRemoved, + handleVoidedTx, + handleTxFirstBlock, + handleNcExecVoided, + handleUnvoidedTx, + handleReorgStarted, + handleTokenCreated, fetchInitialState, - metadataDiff: tracedService('metadataDiff', metadataDiff), + metadataDiff, updateLastSyncedEvent, checkForMissedEvents, }, From 70ea8b41e1ede962a7c2390b2fb4f5ab1b1b144c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 8 Apr 2026 11:16:23 -0300 Subject: [PATCH 07/10] fix(daemon): replace auto-instrumentations with handpicked ones Use only HTTP, MySQL, and Winston instrumentations instead of loading all available auto-instrumentations. Reduces memory footprint and avoids instrumenting unused libraries. Co-Authored-By: Claude Opus 4.6 --- packages/daemon/src/tracing.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/daemon/src/tracing.ts b/packages/daemon/src/tracing.ts index 14a70fea..a675739c 100644 --- a/packages/daemon/src/tracing.ts +++ b/packages/daemon/src/tracing.ts @@ -6,10 +6,12 @@ */ import { NodeSDK } from '@opentelemetry/sdk-node'; -import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'; import { Resource } from '@opentelemetry/resources'; +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql'; +import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston'; const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; @@ -33,10 +35,9 @@ const sdk = new NodeSDK({ }), ...(spanProcessor && { spanProcessor }), instrumentations: [ - getNodeAutoInstrumentations({ - '@opentelemetry/instrumentation-fs': { enabled: false }, - '@opentelemetry/instrumentation-dns': { enabled: false }, - }), + new HttpInstrumentation(), + new MySQLInstrumentation(), + new WinstonInstrumentation(), ], }); From 17fe66f06e8811d1ef0a293ca7ebbd5b6421f620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 8 Apr 2026 11:16:38 -0300 Subject: [PATCH 08/10] fix(daemon): use package.json version as fallback for service.version Read the version from package.json at startup instead of defaulting to 'unknown', so traces always have a meaningful version without requiring Kubernetes manifest changes. Co-Authored-By: Claude Opus 4.6 --- packages/daemon/src/tracing.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/daemon/src/tracing.ts b/packages/daemon/src/tracing.ts index a675739c..abf2a676 100644 --- a/packages/daemon/src/tracing.ts +++ b/packages/daemon/src/tracing.ts @@ -30,7 +30,8 @@ const spanProcessor = endpoint const sdk = new NodeSDK({ resource: new Resource({ 'service.name': process.env.OTEL_SERVICE_NAME || 'wallet-service-daemon', - 'service.version': process.env.SERVICE_VERSION || 'unknown', + // eslint-disable-next-line @typescript-eslint/no-var-requires + 'service.version': process.env.SERVICE_VERSION || require('../../package.json').version, 'deployment.environment': process.env.STAGE || 'local', }), ...(spanProcessor && { spanProcessor }), From 30f9c6309eaa1b7d4ff400fa060c201e6c71c940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 8 Apr 2026 11:16:48 -0300 Subject: [PATCH 09/10] fix(daemon): handle SIGINT for OTel SDK graceful shutdown Add SIGINT handler alongside SIGTERM so that spans in the export queue are flushed when the process is interrupted (e.g. Ctrl+C in dev). Co-Authored-By: Claude Opus 4.6 --- packages/daemon/src/tracing.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/daemon/src/tracing.ts b/packages/daemon/src/tracing.ts index abf2a676..281f054f 100644 --- a/packages/daemon/src/tracing.ts +++ b/packages/daemon/src/tracing.ts @@ -44,7 +44,7 @@ const sdk = new NodeSDK({ sdk.start(); -process.once('SIGTERM', async () => { +const shutdown = async () => { try { await sdk.shutdown(); process.exit(0); @@ -52,4 +52,7 @@ process.once('SIGTERM', async () => { console.error('OTel SDK shutdown error:', err); process.exit(1); } -}); +}; + +process.once('SIGTERM', shutdown); +process.once('SIGINT', shutdown); From 3b019e4bda46323744211f133db880d7c16f94fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 8 Apr 2026 11:17:18 -0300 Subject: [PATCH 10/10] feat(daemon): add OTEL_SDK_DISABLED feature flag for daemon tracing Wrap the entire OTel initialization in an OTEL_SDK_DISABLED check, matching the Lambda tracing pattern. Uses require() so all OTel modules are fully skipped when tracing is disabled. Co-Authored-By: Claude Opus 4.6 --- packages/daemon/src/tracing.ts | 95 ++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/packages/daemon/src/tracing.ts b/packages/daemon/src/tracing.ts index 281f054f..b598d151 100644 --- a/packages/daemon/src/tracing.ts +++ b/packages/daemon/src/tracing.ts @@ -5,54 +5,59 @@ * LICENSE file in the root directory of this source tree. */ -import { NodeSDK } from '@opentelemetry/sdk-node'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'; -import { Resource } from '@opentelemetry/resources'; -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql'; -import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston'; +// Skip all OTel initialization when disabled — avoids module loading cost. +if (process.env.OTEL_SDK_DISABLED !== 'true') { + // Use require() so that imports are fully skipped when disabled. + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { NodeSDK } = require('@opentelemetry/sdk-node'); + const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); + const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-node'); + const { Resource } = require('@opentelemetry/resources'); + const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); + const { MySQLInstrumentation } = require('@opentelemetry/instrumentation-mysql'); + const { WinstonInstrumentation } = require('@opentelemetry/instrumentation-winston'); -const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; + const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; -const spanProcessor = endpoint - ? new BatchSpanProcessor( - new OTLPTraceExporter(), - { - maxQueueSize: 2048, - maxExportBatchSize: 512, - scheduledDelayMillis: 5000, - exportTimeoutMillis: 5000, - }, - ) - : undefined; + const spanProcessor = endpoint + ? new BatchSpanProcessor( + new OTLPTraceExporter(), + { + maxQueueSize: 2048, + maxExportBatchSize: 512, + scheduledDelayMillis: 5000, + exportTimeoutMillis: 5000, + }, + ) + : undefined; -const sdk = new NodeSDK({ - resource: new Resource({ - 'service.name': process.env.OTEL_SERVICE_NAME || 'wallet-service-daemon', - // eslint-disable-next-line @typescript-eslint/no-var-requires - 'service.version': process.env.SERVICE_VERSION || require('../../package.json').version, - 'deployment.environment': process.env.STAGE || 'local', - }), - ...(spanProcessor && { spanProcessor }), - instrumentations: [ - new HttpInstrumentation(), - new MySQLInstrumentation(), - new WinstonInstrumentation(), - ], -}); + const sdk = new NodeSDK({ + resource: new Resource({ + 'service.name': process.env.OTEL_SERVICE_NAME || 'wallet-service-daemon', + // eslint-disable-next-line @typescript-eslint/no-var-requires + 'service.version': process.env.SERVICE_VERSION || require('../../package.json').version, + 'deployment.environment': process.env.STAGE || 'local', + }), + ...(spanProcessor && { spanProcessor }), + instrumentations: [ + new HttpInstrumentation(), + new MySQLInstrumentation(), + new WinstonInstrumentation(), + ], + }); -sdk.start(); + sdk.start(); -const shutdown = async () => { - try { - await sdk.shutdown(); - process.exit(0); - } catch (err) { - console.error('OTel SDK shutdown error:', err); - process.exit(1); - } -}; + const shutdown = async () => { + try { + await sdk.shutdown(); + process.exit(0); + } catch (err) { + console.error('OTel SDK shutdown error:', err); + process.exit(1); + } + }; -process.once('SIGTERM', shutdown); -process.once('SIGINT', shutdown); + process.once('SIGTERM', shutdown); + process.once('SIGINT', shutdown); +}