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..1bbf6a8a3b 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 './enums/container-app.enum'; 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 { appId: defaultAppId, apiKey, apps } = Config.azure.appInsights; + + // Use specified app or default to dfxApi + const appId = app ? apps[app] : defaultAppId; 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/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 5067f783d4..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', @@ -13,6 +16,10 @@ 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: [] };