From 7cf5657b850331ed7eec0635ae3932c68ac56c2d Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:35:31 +0100 Subject: [PATCH 1/3] feat: add multi-app Application Insights log access - Add ContainerApp enum with all container apps (JuiceDollar, dEuro, etc.) - Extend config with app-specific Application Insights IDs - Add optional 'app' parameter to LogQueryDto - Update AppInsightsQueryService to use dynamic app ID - Extend log-debug.sh script with -a/--app parameter - Document new env variables in .env.example Allows querying logs from any container app via the debug endpoint: ./scripts/log-debug.sh exceptions -a juicedollarApi ./scripts/log-debug.sh traces "error" -a deuroApi -h 24 --- .env.example | 11 +++++ scripts/log-debug.sh | 45 ++++++++++++++++--- src/config/config.ts | 9 ++++ .../app-insights-query.service.ts | 10 +++-- .../generic/gs/dto/log-query.dto.ts | 14 ++++++ src/subdomains/generic/gs/gs.service.ts | 6 ++- 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 3868340064..5f9fb369cf 100644 --- a/.env.example +++ b/.env.example @@ -239,6 +239,17 @@ AZURE_TENANT_ID= AZURE_SUBSCRIPTION_ID= AZURE_STORAGE_CONNECTION_STRING= +# Application Insights (for debug log access) +APPINSIGHTS_APP_ID= +APPINSIGHTS_API_KEY= +# Optional: Additional container apps (for multi-app log access) +APPINSIGHTS_JUICEDOLLAR_API_APP_ID= +APPINSIGHTS_DEURO_API_APP_ID= +APPINSIGHTS_DEURO_MONITORING_APP_ID= +APPINSIGHTS_JUICESWAP_PONDER_APP_ID= +APPINSIGHTS_DEURO_PONDER_APP_ID= +APPINSIGHTS_REALUNIT_PONDER_APP_ID= + FIXER_BASE_URL= FIXER_API_KEY= diff --git a/scripts/log-debug.sh b/scripts/log-debug.sh index 5e6ca86332..f941f5229d 100755 --- a/scripts/log-debug.sh +++ b/scripts/log-debug.sh @@ -13,6 +13,16 @@ # # Options: # -h, --hours Time range in hours (default: 1, max: 168) +# -a, --app Container app to query (default: dfxApi) +# +# Available apps: +# dfxApi DFX API (default) +# juicedollarApi JuiceDollar API +# deuroApi dEuro API +# deuroMonitoring dEuro Monitoring +# juiceswapPonder JuiceSwap Ponder +# deuroPonder dEuro Ponder +# realunitPonder RealUnit Ponder # # Environment: # Copy .env.db-debug.sample to .env.db-debug and fill in your credentials @@ -41,6 +51,7 @@ API_URL="${DEBUG_API_URL:-https://api.dfx.swiss/v1}" # Parse arguments HOURS=1 +APP="" COMMAND="${1:-exceptions}" shift 2>/dev/null || true @@ -51,6 +62,10 @@ while [[ $# -gt 0 ]]; do HOURS="$2" shift 2 ;; + -a|--app) + APP="$2" + shift 2 + ;; *) PARAM="$1" shift @@ -76,23 +91,26 @@ ROLE=$(echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq -r '.role' 2>/ echo "Authenticated with role: $ROLE" echo "" +# Build app suffix for display +APP_DISPLAY="${APP:-dfxApi}" + # Build request based on command case $COMMAND in exceptions|exc) TEMPLATE="exceptions-recent" BODY="{\"template\":\"$TEMPLATE\",\"hours\":$HOURS}" - echo "=== Recent Exceptions (last ${HOURS}h) ===" + echo "=== Recent Exceptions [$APP_DISPLAY] (last ${HOURS}h) ===" ;; failures|fail) TEMPLATE="request-failures" BODY="{\"template\":\"$TEMPLATE\",\"hours\":$HOURS}" - echo "=== Failed Requests (last ${HOURS}h) ===" + echo "=== Failed Requests [$APP_DISPLAY] (last ${HOURS}h) ===" ;; slow) TEMPLATE="dependencies-slow" DURATION="${PARAM:-1000}" BODY="{\"template\":\"$TEMPLATE\",\"hours\":$HOURS,\"durationMs\":$DURATION}" - echo "=== Slow Dependencies >${DURATION}ms (last ${HOURS}h) ===" + echo "=== Slow Dependencies >${DURATION}ms [$APP_DISPLAY] (last ${HOURS}h) ===" ;; traces|trace) if [ -z "$PARAM" ]; then @@ -102,7 +120,7 @@ case $COMMAND in fi TEMPLATE="traces-by-message" BODY="{\"template\":\"$TEMPLATE\",\"hours\":$HOURS,\"messageFilter\":\"$PARAM\"}" - echo "=== Traces containing '$PARAM' (last ${HOURS}h) ===" + echo "=== Traces containing '$PARAM' [$APP_DISPLAY] (last ${HOURS}h) ===" ;; operation|op) if [ -z "$PARAM" ]; then @@ -112,7 +130,7 @@ case $COMMAND in fi TEMPLATE="traces-by-operation" BODY="{\"template\":\"$TEMPLATE\",\"hours\":$HOURS,\"operationId\":\"$PARAM\"}" - echo "=== Traces for operation $PARAM (last ${HOURS}h) ===" + echo "=== Traces for operation $PARAM [$APP_DISPLAY] (last ${HOURS}h) ===" ;; events|event) if [ -z "$PARAM" ]; then @@ -122,7 +140,7 @@ case $COMMAND in fi TEMPLATE="custom-events" BODY="{\"template\":\"$TEMPLATE\",\"hours\":$HOURS,\"eventName\":\"$PARAM\"}" - echo "=== Custom Events '$PARAM' (last ${HOURS}h) ===" + echo "=== Custom Events '$PARAM' [$APP_DISPLAY] (last ${HOURS}h) ===" ;; *) echo "Unknown command: $COMMAND" @@ -134,10 +152,25 @@ case $COMMAND in echo " traces Search trace messages" echo " operation Traces by operation GUID" echo " events Custom events by name" + echo "" + echo "Available apps (-a, --app):" + echo " dfxApi DFX API (default)" + echo " juicedollarApi JuiceDollar API" + echo " deuroApi dEuro API" + echo " deuroMonitoring dEuro Monitoring" + echo " juiceswapPonder JuiceSwap Ponder" + echo " deuroPonder dEuro Ponder" + echo " realunitPonder RealUnit Ponder" exit 1 ;; esac +# Add app parameter to body if specified +if [ -n "$APP" ]; then + # Remove trailing } and add app parameter + BODY="${BODY%\}},\"app\":\"$APP\"}" +fi + echo "" # Execute log query diff --git a/src/config/config.ts b/src/config/config.ts index 654c72fee4..c3c045cb11 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1018,6 +1018,15 @@ export class Configuration { appInsights: { appId: process.env.APPINSIGHTS_APP_ID, apiKey: process.env.APPINSIGHTS_API_KEY, + apps: { + dfxApi: process.env.APPINSIGHTS_APP_ID, + juicedollarApi: process.env.APPINSIGHTS_JUICEDOLLAR_API_APP_ID, + deuroApi: process.env.APPINSIGHTS_DEURO_API_APP_ID, + deuroMonitoring: process.env.APPINSIGHTS_DEURO_MONITORING_APP_ID, + juiceswapPonder: process.env.APPINSIGHTS_JUICESWAP_PONDER_APP_ID, + deuroPonder: process.env.APPINSIGHTS_DEURO_PONDER_APP_ID, + realunitPonder: process.env.APPINSIGHTS_REALUNIT_PONDER_APP_ID, + }, }, }; diff --git a/src/integration/infrastructure/app-insights-query.service.ts b/src/integration/infrastructure/app-insights-query.service.ts index b44ccaaee1..47ad13cd08 100644 --- a/src/integration/infrastructure/app-insights-query.service.ts +++ b/src/integration/infrastructure/app-insights-query.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Config } from 'src/config/config'; import { HttpService } from 'src/shared/services/http.service'; +import { ContainerApp } from 'src/subdomains/generic/gs/dto/log-query.dto'; interface AppInsightsQueryResponse { tables: { @@ -16,11 +17,14 @@ export class AppInsightsQueryService { constructor(private readonly http: HttpService) {} - async query(kql: string, timespan?: string): Promise { - const { appId, apiKey } = Config.azure.appInsights; + async query(kql: string, timespan?: string, app?: ContainerApp): Promise { + const { apiKey, apps } = Config.azure.appInsights; + + // Use specified app or default to dfxApi + const appId = app ? apps[app] : apps.dfxApi ?? Config.azure.appInsights.appId; if (!appId || !apiKey) { - throw new Error('App insights config missing'); + throw new Error(app ? `App insights config missing for ${app}` : 'App insights config missing'); } const body: { query: string; timespan?: string } = { query: kql }; diff --git a/src/subdomains/generic/gs/dto/log-query.dto.ts b/src/subdomains/generic/gs/dto/log-query.dto.ts index 5067f783d4..ccde966482 100644 --- a/src/subdomains/generic/gs/dto/log-query.dto.ts +++ b/src/subdomains/generic/gs/dto/log-query.dto.ts @@ -9,10 +9,24 @@ export enum LogQueryTemplate { CUSTOM_EVENTS = 'custom-events', } +export enum ContainerApp { + DFX_API = 'dfxApi', + JUICEDOLLAR_API = 'juicedollarApi', + DEURO_API = 'deuroApi', + DEURO_MONITORING = 'deuroMonitoring', + JUICESWAP_PONDER = 'juiceswapPonder', + DEURO_PONDER = 'deuroPonder', + REALUNIT_PONDER = 'realunitPonder', +} + export class LogQueryDto { @IsEnum(LogQueryTemplate) template: LogQueryTemplate; + @IsOptional() + @IsEnum(ContainerApp) + app?: ContainerApp; + @IsOptional() @IsString() @Matches(/^[a-f0-9-]{36}$/i, { message: 'operationId must be a valid GUID' }) diff --git a/src/subdomains/generic/gs/gs.service.ts b/src/subdomains/generic/gs/gs.service.ts index 6dc700faf6..b60aa3ba14 100644 --- a/src/subdomains/generic/gs/gs.service.ts +++ b/src/subdomains/generic/gs/gs.service.ts @@ -285,13 +285,15 @@ export class GsService { kql += `\n| take ${template.defaultLimit}`; // Log for audit - this.logger.verbose(`Log query by ${userIdentifier}: template=${dto.template}, params=${JSON.stringify(dto)}`); + this.logger.verbose( + `Log query by ${userIdentifier}: template=${dto.template}, app=${dto.app ?? 'dfxApi'}, params=${JSON.stringify(dto)}`, + ); // Execute const timespan = `PT${dto.hours ?? 1}H`; try { - const response = await this.appInsightsQueryService.query(kql, timespan); + const response = await this.appInsightsQueryService.query(kql, timespan, dto.app); if (!response.tables?.length) { return { columns: [], rows: [] }; From fa6a6252b88afbc3bfe49b3b2c7a81313bb94000 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:40:05 +0100 Subject: [PATCH 2/3] refactor: move ContainerApp enum to integration layer Move ContainerApp enum from gs/dto to integration/infrastructure/enums to respect layer architecture (integration should not import from domain). The DTO re-exports the enum for backward compatibility. --- .../infrastructure/app-insights-query.service.ts | 2 +- .../infrastructure/enums/container-app.enum.ts | 9 +++++++++ src/subdomains/generic/gs/dto/log-query.dto.ts | 13 +++---------- 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 src/integration/infrastructure/enums/container-app.enum.ts diff --git a/src/integration/infrastructure/app-insights-query.service.ts b/src/integration/infrastructure/app-insights-query.service.ts index 47ad13cd08..b99087d0ab 100644 --- a/src/integration/infrastructure/app-insights-query.service.ts +++ b/src/integration/infrastructure/app-insights-query.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Config } from 'src/config/config'; import { HttpService } from 'src/shared/services/http.service'; -import { ContainerApp } from 'src/subdomains/generic/gs/dto/log-query.dto'; +import { ContainerApp } from './enums/container-app.enum'; interface AppInsightsQueryResponse { tables: { diff --git a/src/integration/infrastructure/enums/container-app.enum.ts b/src/integration/infrastructure/enums/container-app.enum.ts new file mode 100644 index 0000000000..b33c1889b0 --- /dev/null +++ b/src/integration/infrastructure/enums/container-app.enum.ts @@ -0,0 +1,9 @@ +export enum ContainerApp { + DFX_API = 'dfxApi', + JUICEDOLLAR_API = 'juicedollarApi', + DEURO_API = 'deuroApi', + DEURO_MONITORING = 'deuroMonitoring', + JUICESWAP_PONDER = 'juiceswapPonder', + DEURO_PONDER = 'deuroPonder', + REALUNIT_PONDER = 'realunitPonder', +} diff --git a/src/subdomains/generic/gs/dto/log-query.dto.ts b/src/subdomains/generic/gs/dto/log-query.dto.ts index ccde966482..d157d1196d 100644 --- a/src/subdomains/generic/gs/dto/log-query.dto.ts +++ b/src/subdomains/generic/gs/dto/log-query.dto.ts @@ -1,4 +1,7 @@ import { IsEnum, IsInt, IsOptional, IsString, Matches, Max, Min } from 'class-validator'; +import { ContainerApp } from 'src/integration/infrastructure/enums/container-app.enum'; + +export { ContainerApp }; export enum LogQueryTemplate { TRACES_BY_OPERATION = 'traces-by-operation', @@ -9,16 +12,6 @@ export enum LogQueryTemplate { CUSTOM_EVENTS = 'custom-events', } -export enum ContainerApp { - DFX_API = 'dfxApi', - JUICEDOLLAR_API = 'juicedollarApi', - DEURO_API = 'deuroApi', - DEURO_MONITORING = 'deuroMonitoring', - JUICESWAP_PONDER = 'juiceswapPonder', - DEURO_PONDER = 'deuroPonder', - REALUNIT_PONDER = 'realunitPonder', -} - export class LogQueryDto { @IsEnum(LogQueryTemplate) template: LogQueryTemplate; From f873c299e260fa1cfe691e8b3c5e19399c807e1c Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:45:25 +0100 Subject: [PATCH 3/3] refactor: simplify appId fallback logic Remove redundant fallback - apps.dfxApi and appId are identical. --- src/integration/infrastructure/app-insights-query.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/integration/infrastructure/app-insights-query.service.ts b/src/integration/infrastructure/app-insights-query.service.ts index b99087d0ab..1bbf6a8a3b 100644 --- a/src/integration/infrastructure/app-insights-query.service.ts +++ b/src/integration/infrastructure/app-insights-query.service.ts @@ -18,10 +18,10 @@ export class AppInsightsQueryService { constructor(private readonly http: HttpService) {} async query(kql: string, timespan?: string, app?: ContainerApp): Promise { - const { apiKey, apps } = Config.azure.appInsights; + const { appId: defaultAppId, apiKey, apps } = Config.azure.appInsights; // Use specified app or default to dfxApi - const appId = app ? apps[app] : apps.dfxApi ?? Config.azure.appInsights.appId; + const appId = app ? apps[app] : defaultAppId; if (!appId || !apiKey) { throw new Error(app ? `App insights config missing for ${app}` : 'App insights config missing');