From 30fedcfb3ca0ffc4ffd9b5a38426e2cd0d93e963 Mon Sep 17 00:00:00 2001 From: spypsy Date: Mon, 9 Feb 2026 17:22:11 +0000 Subject: [PATCH 1/2] fix: stringify all bigints in pino-logger --- .../src/log/gcloud-logger-config.ts | 26 ++++++ .../foundation/src/log/pino-logger.test.ts | 85 +++++++++++++++++++ .../foundation/src/log/pino-logger.ts | 26 ++++++ .../slasher/src/slash_offenses_collector.ts | 6 +- 4 files changed, 138 insertions(+), 5 deletions(-) diff --git a/yarn-project/foundation/src/log/gcloud-logger-config.ts b/yarn-project/foundation/src/log/gcloud-logger-config.ts index db3b331141df..7bff7a334240 100644 --- a/yarn-project/foundation/src/log/gcloud-logger-config.ts +++ b/yarn-project/foundation/src/log/gcloud-logger-config.ts @@ -6,6 +6,29 @@ const GOOGLE_CLOUD_TRACE_ID = 'logging.googleapis.com/trace'; const GOOGLE_CLOUD_SPAN_ID = 'logging.googleapis.com/spanId'; const GOOGLE_CLOUD_TRACE_SAMPLED = 'logging.googleapis.com/trace_sampled'; +/** + * Converts bigint values to strings recursively in a log object to avoid serialization issues. + */ +function convertBigintsToStrings(obj: unknown): unknown { + if (typeof obj === 'bigint') { + return String(obj); + } + + if (Array.isArray(obj)) { + return obj.map(item => convertBigintsToStrings(item)); + } + + if (obj !== null && typeof obj === 'object') { + const result: Record = {}; + for (const key in obj) { + result[key] = convertBigintsToStrings((obj as Record)[key]); + } + return result; + } + + return obj; +} + /** * Pino configuration for google cloud observability. Tweaks message and timestamp, * adds trace context attributes, and injects severity level. @@ -15,6 +38,9 @@ export const GoogleCloudLoggerConfig = { messageKey: 'message', formatters: { log(object: Record): Record { + // Convert bigints to strings recursively to avoid serialization issues + convertBigintsToStrings(object) as Record; + // Add trace context attributes following Cloud Logging structured log format described // in https://cloud.google.com/logging/docs/structured-logging#special-payload-fields const { trace_id, span_id, trace_flags, ...rest } = object; diff --git a/yarn-project/foundation/src/log/pino-logger.test.ts b/yarn-project/foundation/src/log/pino-logger.test.ts index 1efdcf038f18..9881535d4f58 100644 --- a/yarn-project/foundation/src/log/pino-logger.test.ts +++ b/yarn-project/foundation/src/log/pino-logger.test.ts @@ -188,6 +188,91 @@ describe('pino-logger', () => { }); }); + it('converts bigints to strings recursively ', () => { + const testLogger = createLogger('bigint-test'); + capturingStream.clear(); + + testLogger.info('comprehensive bigint conversion', { + // Top-level bigints + amount: 123456789012345678901234n, + slot: 42n, + // Nested objects + nested: { + value: 999999999999999999n, + deepNested: { + id: 12345678901234567890n, + }, + }, + // Arrays with bigints + array: [1n, 2n, 3n], + mixedArray: [{ id: 999n }, { id: 888n }], + // Mixed types + numberValue: 123, + stringValue: 'test', + boolValue: true, + nullValue: null, + }); + + const entries = capturingStream.getJsonLines(); + expect(entries).toHaveLength(1); + expect(entries[0]).toMatchObject({ + module: 'bigint-test', + msg: 'comprehensive bigint conversion', + // All bigints converted to strings + amount: '123456789012345678901234', + slot: '42', + nested: { + value: '999999999999999999', + deepNested: { + id: '12345678901234567890', + }, + }, + array: ['1', '2', '3'], + mixedArray: [{ id: '999' }, { id: '888' }], + // Other types preserved + numberValue: 123, + stringValue: 'test', + boolValue: true, + nullValue: null, + }); + }); + + it('does not mutate the original log data object', () => { + const testLogger = createLogger('mutation-test'); + capturingStream.clear(); + + const originalData = { + amount: 123456789012345678901234n, + nested: { + value: 999n, + }, + array: [1n, 2n, 3n], + }; + + // Keep references to verify mutation + const originalAmount = originalData.amount; + const originalNestedValue = originalData.nested.value; + const originalArrayItem = originalData.array[0]; + + testLogger.info('mutation test', originalData); + + // Verify the original object was NOT mutated + expect(originalData.amount).toBe(originalAmount); + expect(typeof originalData.amount).toBe('bigint'); + expect(originalData.nested.value).toBe(originalNestedValue); + expect(typeof originalData.nested.value).toBe('bigint'); + expect(originalData.array[0]).toBe(originalArrayItem); + expect(typeof originalData.array[0]).toBe('bigint'); + + // But the logged version should have strings + const entries = capturingStream.getJsonLines(); + expect(entries[0]).toMatchObject({ + amount: '123456789012345678901234', + nested: { value: '999' }, + array: ['1', '2', '3'], + }); + }); + it('returns bindings via getBindings', () => { const testLogger = createLogger('bindings-test', { actor: 'main', instanceId: 'id-123' }); const bindings = testLogger.getBindings(); diff --git a/yarn-project/foundation/src/log/pino-logger.ts b/yarn-project/foundation/src/log/pino-logger.ts index 2a41d44bf117..bff4deca0c49 100644 --- a/yarn-project/foundation/src/log/pino-logger.ts +++ b/yarn-project/foundation/src/log/pino-logger.ts @@ -134,6 +134,29 @@ const customLevels = { verbose: 25 }; // Global pino options, tweaked for google cloud if running there. const useGcloudLogging = parseBooleanEnv(process.env['USE_GCLOUD_LOGGING' satisfies EnvVar]); +/** + * Converts bigint values to strings recursively in a log object to avoid serialization issues. + */ +function convertBigintsToStrings(obj: unknown): unknown { + if (typeof obj === 'bigint') { + return String(obj); + } + + if (Array.isArray(obj)) { + return obj.map(item => convertBigintsToStrings(item)); + } + + if (obj !== null && typeof obj === 'object') { + const result: Record = {}; + for (const key in obj) { + result[key] = convertBigintsToStrings((obj as Record)[key]); + } + return result; + } + + return obj; +} + const redactedPaths = [ 'validatorPrivateKeys', // for both the validator and the prover @@ -165,6 +188,9 @@ const pinoOpts: pino.LoggerOptions = { ...redactedPaths.map(p => `opts.${p}`), ], }, + formatters: { + log: obj => convertBigintsToStrings(obj) as Record, + }, ...(useGcloudLogging ? GoogleCloudLoggerConfig : {}), }; diff --git a/yarn-project/slasher/src/slash_offenses_collector.ts b/yarn-project/slasher/src/slash_offenses_collector.ts index 274357a97c6a..551f868ccec3 100644 --- a/yarn-project/slasher/src/slash_offenses_collector.ts +++ b/yarn-project/slasher/src/slash_offenses_collector.ts @@ -85,11 +85,7 @@ export class SlashOffensesCollector { } } - this.log.info(`Adding pending offense for validator ${arg.validator}`, { - ...pendingOffense, - epochOrSlot: pendingOffense.epochOrSlot.toString(), - amount: pendingOffense.amount.toString(), - }); + this.log.info(`Adding pending offense for validator ${arg.validator}`, pendingOffense); await this.offensesStore.addPendingOffense(pendingOffense); } } From 9f0518efa889100b1cb03c04df247ca28e4672f1 Mon Sep 17 00:00:00 2001 From: spypsy Date: Wed, 11 Feb 2026 17:52:32 +0000 Subject: [PATCH 2/2] remove duplicate function --- .../foundation/src/log/bigint-utils.ts | 22 +++++++++++++++ .../src/log/gcloud-logger-config.ts | 27 +++---------------- .../foundation/src/log/pino-logger.ts | 24 +---------------- 3 files changed, 26 insertions(+), 47 deletions(-) create mode 100644 yarn-project/foundation/src/log/bigint-utils.ts diff --git a/yarn-project/foundation/src/log/bigint-utils.ts b/yarn-project/foundation/src/log/bigint-utils.ts new file mode 100644 index 000000000000..6cc94101ac2f --- /dev/null +++ b/yarn-project/foundation/src/log/bigint-utils.ts @@ -0,0 +1,22 @@ +/** + * Converts bigint values to strings recursively in a log object to avoid serialization issues. + */ +export function convertBigintsToStrings(obj: unknown): unknown { + if (typeof obj === 'bigint') { + return String(obj); + } + + if (Array.isArray(obj)) { + return obj.map(item => convertBigintsToStrings(item)); + } + + if (obj !== null && typeof obj === 'object') { + const result: Record = {}; + for (const key in obj) { + result[key] = convertBigintsToStrings((obj as Record)[key]); + } + return result; + } + + return obj; +} diff --git a/yarn-project/foundation/src/log/gcloud-logger-config.ts b/yarn-project/foundation/src/log/gcloud-logger-config.ts index 7bff7a334240..2e036212af71 100644 --- a/yarn-project/foundation/src/log/gcloud-logger-config.ts +++ b/yarn-project/foundation/src/log/gcloud-logger-config.ts @@ -1,34 +1,13 @@ import type { pino } from 'pino'; +import { convertBigintsToStrings } from './bigint-utils.js'; + /* eslint-disable camelcase */ const GOOGLE_CLOUD_TRACE_ID = 'logging.googleapis.com/trace'; const GOOGLE_CLOUD_SPAN_ID = 'logging.googleapis.com/spanId'; const GOOGLE_CLOUD_TRACE_SAMPLED = 'logging.googleapis.com/trace_sampled'; -/** - * Converts bigint values to strings recursively in a log object to avoid serialization issues. - */ -function convertBigintsToStrings(obj: unknown): unknown { - if (typeof obj === 'bigint') { - return String(obj); - } - - if (Array.isArray(obj)) { - return obj.map(item => convertBigintsToStrings(item)); - } - - if (obj !== null && typeof obj === 'object') { - const result: Record = {}; - for (const key in obj) { - result[key] = convertBigintsToStrings((obj as Record)[key]); - } - return result; - } - - return obj; -} - /** * Pino configuration for google cloud observability. Tweaks message and timestamp, * adds trace context attributes, and injects severity level. @@ -39,7 +18,7 @@ export const GoogleCloudLoggerConfig = { formatters: { log(object: Record): Record { // Convert bigints to strings recursively to avoid serialization issues - convertBigintsToStrings(object) as Record; + object = convertBigintsToStrings(object) as Record; // Add trace context attributes following Cloud Logging structured log format described // in https://cloud.google.com/logging/docs/structured-logging#special-payload-fields diff --git a/yarn-project/foundation/src/log/pino-logger.ts b/yarn-project/foundation/src/log/pino-logger.ts index bff4deca0c49..2395cc908ec0 100644 --- a/yarn-project/foundation/src/log/pino-logger.ts +++ b/yarn-project/foundation/src/log/pino-logger.ts @@ -7,6 +7,7 @@ import { inspect } from 'util'; import { compactArray } from '../collection/array.js'; import type { EnvVar } from '../config/index.js'; import { parseBooleanEnv } from '../config/parse-env.js'; +import { convertBigintsToStrings } from './bigint-utils.js'; import { GoogleCloudLoggerConfig } from './gcloud-logger-config.js'; import { getLogLevelFromFilters, parseEnv } from './log-filters.js'; import type { LogLevel } from './log-levels.js'; @@ -134,29 +135,6 @@ const customLevels = { verbose: 25 }; // Global pino options, tweaked for google cloud if running there. const useGcloudLogging = parseBooleanEnv(process.env['USE_GCLOUD_LOGGING' satisfies EnvVar]); -/** - * Converts bigint values to strings recursively in a log object to avoid serialization issues. - */ -function convertBigintsToStrings(obj: unknown): unknown { - if (typeof obj === 'bigint') { - return String(obj); - } - - if (Array.isArray(obj)) { - return obj.map(item => convertBigintsToStrings(item)); - } - - if (obj !== null && typeof obj === 'object') { - const result: Record = {}; - for (const key in obj) { - result[key] = convertBigintsToStrings((obj as Record)[key]); - } - return result; - } - - return obj; -} - const redactedPaths = [ 'validatorPrivateKeys', // for both the validator and the prover