From eb40de79416f475dc7934fa5e650bdcfde066b69 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Sat, 23 May 2026 18:05:23 +0200 Subject: [PATCH 1/3] feat(gs): add ALL_TRACES template for generic log queries Adds a new /gs/debug/logs template that returns all trace entries in the requested time window without a messageFilter. Default limit 500. Self-emitted audit lines from /gs/debug/logs (prefix "[GsService] Log query by ") are filtered at the template level to prevent the audit feedback loop that would otherwise dominate the result for any high-frequency caller (e.g. the upcoming all-logs dashboard view in DFXswiss/services). --- src/subdomains/generic/gs/dto/gs.dto.ts | 13 +++++++++++++ src/subdomains/generic/gs/dto/log-query.dto.ts | 1 + 2 files changed, 14 insertions(+) diff --git a/src/subdomains/generic/gs/dto/gs.dto.ts b/src/subdomains/generic/gs/dto/gs.dto.ts index 62afe96b02..6727a396af 100644 --- a/src/subdomains/generic/gs/dto/gs.dto.ts +++ b/src/subdomains/generic/gs/dto/gs.dto.ts @@ -182,6 +182,19 @@ export const DebugLogQueryTemplates: Record< requiredParams: ['eventName'], defaultLimit: 500, }, + [LogQueryTemplate.ALL_TRACES]: { + // Returns all trace entries in the given window. Self-emitted audit lines + // from /gs/debug/logs (start with "[GsService] Log query by ") are filtered + // out at the source so they don't crowd the result for high-frequency + // dashboard callers. + kql: `traces +| where timestamp > ago({hours}h) +| where not(message startswith "[GsService] Log query by ") +| project timestamp, severityLevel, message, operation_Id +| order by timestamp desc`, + requiredParams: [], + defaultLimit: 500, + }, }; // Support endpoint diff --git a/src/subdomains/generic/gs/dto/log-query.dto.ts b/src/subdomains/generic/gs/dto/log-query.dto.ts index 5067f783d4..eaa5e9ef9e 100644 --- a/src/subdomains/generic/gs/dto/log-query.dto.ts +++ b/src/subdomains/generic/gs/dto/log-query.dto.ts @@ -7,6 +7,7 @@ export enum LogQueryTemplate { REQUEST_FAILURES = 'request-failures', DEPENDENCIES_SLOW = 'dependencies-slow', CUSTOM_EVENTS = 'custom-events', + ALL_TRACES = 'all-traces', } export class LogQueryDto { From 50670edcba40460e455973d600ed8bcf7024041d Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Sat, 23 May 2026 18:47:58 +0200 Subject: [PATCH 2/3] refactor(gs): extract LogQueryAuditPrefix to break implicit string contract The TRACES self-filter in the new ALL_TRACES template hardcoded the audit-log prefix emitted by gs.service.executeLogQuery. A refactor of either string would have silently broken the filter. Both sides now reference a shared LogQueryAuditPrefix constant from gs.dto.ts. The new spec verifies that the audit log and the template KQL stay in sync. --- .../generic/gs/__tests__/gs.service.spec.ts | 30 ++++++++++++++++++- src/subdomains/generic/gs/dto/gs.dto.ts | 19 +++++++++--- src/subdomains/generic/gs/gs.service.ts | 7 +++-- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/subdomains/generic/gs/__tests__/gs.service.spec.ts b/src/subdomains/generic/gs/__tests__/gs.service.spec.ts index 099248ccf9..331ded6eef 100644 --- a/src/subdomains/generic/gs/__tests__/gs.service.spec.ts +++ b/src/subdomains/generic/gs/__tests__/gs.service.spec.ts @@ -2,7 +2,10 @@ import { BadRequestException } from '@nestjs/common'; import { createMock } from '@golevelup/ts-jest'; import { DataSource } from 'typeorm'; import { AppInsightsQueryService } from 'src/integration/infrastructure/app-insights-query.service'; +import { DfxLogger } from 'src/shared/services/dfx-logger'; import { GsService } from '../gs.service'; +import { DebugLogQueryTemplates, LogQueryAuditPrefix } from '../dto/gs.dto'; +import { LogQueryTemplate } from '../dto/log-query.dto'; import { UserDataService } from '../../user/models/user-data/user-data.service'; import { UserService } from '../../user/models/user/user.service'; import { BuyService } from 'src/subdomains/core/buy-crypto/routes/buy/buy.service'; @@ -27,12 +30,14 @@ import { VirtualIbanService } from 'src/subdomains/supporting/bank/virtual-iban/ describe('GsService', () => { let service: GsService; let dataSource: DataSource; + let appInsightsQueryService: AppInsightsQueryService; beforeEach(() => { dataSource = createMock(); + appInsightsQueryService = createMock(); service = new GsService( - createMock(), + appInsightsQueryService, createMock(), createMock(), createMock(), @@ -192,4 +197,27 @@ describe('GsService', () => { }); }); }); + + describe('LogQueryAuditPrefix sync', () => { + it('ALL_TRACES template excludes the exact audit prefix that gs.service emits', async () => { + const verboseSpy = jest.spyOn(DfxLogger.prototype, 'verbose').mockImplementation(() => undefined); + jest.spyOn(appInsightsQueryService, 'query').mockResolvedValue({ tables: [{ columns: [], rows: [] }] } as never); + + await service.executeLogQuery({ template: LogQueryTemplate.ALL_TRACES, hours: 1 }, '0xtester'); + + // 1) The service emits an audit log that starts with LogQueryAuditPrefix + const emitted = verboseSpy.mock.calls.map((args) => String(args[0])).join('\n'); + expect(emitted).toContain(`${LogQueryAuditPrefix}0xtester`); + expect(emitted.startsWith(LogQueryAuditPrefix)).toBe(true); + + // 2) The ALL_TRACES template KQL excludes lines with that exact prefix + // (after DfxLogger's "[GsService] " class-context prefix). This binds + // service and template via the shared constant — refactoring the + // constant will update both sides at once. + const kql = DebugLogQueryTemplates[LogQueryTemplate.ALL_TRACES].kql; + expect(kql).toContain(`[GsService] ${LogQueryAuditPrefix}`); + + verboseSpy.mockRestore(); + }); + }); }); diff --git a/src/subdomains/generic/gs/dto/gs.dto.ts b/src/subdomains/generic/gs/dto/gs.dto.ts index 6727a396af..ab9f6898c3 100644 --- a/src/subdomains/generic/gs/dto/gs.dto.ts +++ b/src/subdomains/generic/gs/dto/gs.dto.ts @@ -7,6 +7,14 @@ export const GsRestrictedColumns: Record = { asset: ['ikna'], }; +/** + * Prefix of the verbose audit message emitted by `gs.service.executeLogQuery` + * (`[GsService] Log query by ...`). The ALL_TRACES template excludes lines + * with this prefix to prevent recursive self-match for high-frequency + * callers. Keep service and template in sync via this constant. + */ +export const LogQueryAuditPrefix = 'Log query by '; + // Debug endpoint export const DebugMaxResults = 10000; export const DebugBlockedSchemas = ['sys', 'information_schema', 'master', 'msdb', 'tempdb', 'pg_catalog', 'pg_toast']; @@ -184,12 +192,15 @@ export const DebugLogQueryTemplates: Record< }, [LogQueryTemplate.ALL_TRACES]: { // Returns all trace entries in the given window. Self-emitted audit lines - // from /gs/debug/logs (start with "[GsService] Log query by ") are filtered - // out at the source so they don't crowd the result for high-frequency - // dashboard callers. + // from /gs/debug/logs (start with "[GsService] " + LogQueryAuditPrefix) + // are filtered out at the source so they don't crowd the result for + // high-frequency dashboard callers. The "[GsService] " prefix is added by + // DfxLogger's class-context; LogQueryAuditPrefix is the message prefix + // emitted by gs.service.executeLogQuery — both sides reference the same + // constant to keep service and template in sync. kql: `traces | where timestamp > ago({hours}h) -| where not(message startswith "[GsService] Log query by ") +| where not(message startswith "[GsService] ${LogQueryAuditPrefix}") | project timestamp, severityLevel, message, operation_Id | order by timestamp desc`, requiredParams: [], diff --git a/src/subdomains/generic/gs/gs.service.ts b/src/subdomains/generic/gs/gs.service.ts index 8a334dec72..ccf2610590 100644 --- a/src/subdomains/generic/gs/gs.service.ts +++ b/src/subdomains/generic/gs/gs.service.ts @@ -37,6 +37,7 @@ import { DebugMaxResults, GsRestrictedColumns, GsRestrictedMarker, + LogQueryAuditPrefix, SupportTable, } from './dto/gs.dto'; import { LogQueryDto, LogQueryResult } from './dto/log-query.dto'; @@ -285,7 +286,9 @@ 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( + `${LogQueryAuditPrefix}${userIdentifier}: template=${dto.template}, params=${JSON.stringify(dto)}`, + ); // Execute const timespan = `PT${dto.hours ?? 1}H`; @@ -302,7 +305,7 @@ export class GsService { rows: response.tables[0].rows, }; } catch (e) { - this.logger.info(`Log query by ${userIdentifier} failed: ${e.message}`); + this.logger.info(`${LogQueryAuditPrefix}${userIdentifier} failed: ${e.message}`); throw new BadRequestException('Query execution failed'); } } From 11fbaec3cbe6b2b1ea3276433a83fa1629a733d2 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Sat, 23 May 2026 18:52:44 +0200 Subject: [PATCH 3/3] docs(gs): note class-name coupling in LogQueryAuditPrefix --- src/subdomains/generic/gs/dto/gs.dto.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/subdomains/generic/gs/dto/gs.dto.ts b/src/subdomains/generic/gs/dto/gs.dto.ts index ab9f6898c3..83a819becf 100644 --- a/src/subdomains/generic/gs/dto/gs.dto.ts +++ b/src/subdomains/generic/gs/dto/gs.dto.ts @@ -12,6 +12,10 @@ export const GsRestrictedColumns: Record = { * (`[GsService] Log query by ...`). The ALL_TRACES template excludes lines * with this prefix to prevent recursive self-match for high-frequency * callers. Keep service and template in sync via this constant. + * + * Note: the leading `[GsService] ` is prepended by `DfxLogger` from the + * `GsService` class name, not from this constant. Renaming the service + * class would break the KQL filter silently — no test covers that path. */ export const LogQueryAuditPrefix = 'Log query by ';