Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions yarn-project/foundation/src/log/bigint-utils.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> = {};
for (const key in obj) {
result[key] = convertBigintsToStrings((obj as Record<string, unknown>)[key]);
}
return result;
}

return obj;
}
5 changes: 5 additions & 0 deletions yarn-project/foundation/src/log/gcloud-logger-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { pino } from 'pino';

import { convertBigintsToStrings } from './bigint-utils.js';

/* eslint-disable camelcase */

const GOOGLE_CLOUD_TRACE_ID = 'logging.googleapis.com/trace';
Expand All @@ -15,6 +17,9 @@ export const GoogleCloudLoggerConfig = {
messageKey: 'message',
formatters: {
log(object: Record<string, unknown>): Record<string, unknown> {
// Convert bigints to strings recursively to avoid serialization issues
object = convertBigintsToStrings(object) as Record<string, unknown>;

// 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;
Expand Down
85 changes: 85 additions & 0 deletions yarn-project/foundation/src/log/pino-logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/foundation/src/log/pino-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -165,6 +166,9 @@ const pinoOpts: pino.LoggerOptions<keyof typeof customLevels> = {
...redactedPaths.map(p => `opts.${p}`),
],
},
formatters: {
log: obj => convertBigintsToStrings(obj) as Record<string, unknown>,
},
...(useGcloudLogging ? GoogleCloudLoggerConfig : {}),
};

Expand Down
6 changes: 1 addition & 5 deletions yarn-project/slasher/src/slash_offenses_collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Loading