Skip to content

Commit

Permalink
fix(mongo): rewrite Buffer as ? during serialization (#14071)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe authored Nov 12, 2024
1 parent 188ece7 commit 6d1a251
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@ describe('MongoDB experimental Test', () => {
'db.connection_string': expect.any(String),
'net.peer.name': expect.any(String),
'net.peer.port': expect.any(Number),
'db.statement':
'{"title":"?","_id":{"_bsontype":"?","id":{"0":"?","1":"?","2":"?","3":"?","4":"?","5":"?","6":"?","7":"?","8":"?","9":"?","10":"?","11":"?"}}}',
'db.statement': '{"title":"?","_id":{"_bsontype":"?","id":"?"}}',
'otel.kind': 'CLIENT',
},
description:
'{"title":"?","_id":{"_bsontype":"?","id":{"0":"?","1":"?","2":"?","3":"?","4":"?","5":"?","6":"?","7":"?","8":"?","9":"?","10":"?","11":"?"}}}',
description: '{"title":"?","_id":{"_bsontype":"?","id":"?"}}',
op: 'db',
origin: 'auto.db.otel.mongo',
}),
Expand Down Expand Up @@ -162,12 +160,10 @@ describe('MongoDB experimental Test', () => {
'db.connection_string': expect.any(String),
'net.peer.name': expect.any(String),
'net.peer.port': expect.any(Number),
'db.statement':
'{"endSessions":[{"id":{"_bsontype":"?","sub_type":"?","position":"?","buffer":{"0":"?","1":"?","2":"?","3":"?","4":"?","5":"?","6":"?","7":"?","8":"?","9":"?","10":"?","11":"?","12":"?","13":"?","14":"?","15":"?"}}}]}',
'db.statement': '{"endSessions":[{"id":{"_bsontype":"?","sub_type":"?","position":"?","buffer":"?"}}]}',
'otel.kind': 'CLIENT',
},
description:
'{"endSessions":[{"id":{"_bsontype":"?","sub_type":"?","position":"?","buffer":{"0":"?","1":"?","2":"?","3":"?","4":"?","5":"?","6":"?","7":"?","8":"?","9":"?","10":"?","11":"?","12":"?","13":"?","14":"?","15":"?"}}}]}',
description: '{"endSessions":[{"id":{"_bsontype":"?","sub_type":"?","position":"?","buffer":"?"}}]}',
op: 'db',
origin: 'auto.db.otel.mongo',
}),
Expand Down
46 changes: 46 additions & 0 deletions packages/node/src/integrations/tracing/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,58 @@ export const instrumentMongo = generateInstrumentOnce(
INTEGRATION_NAME,
() =>
new MongoDBInstrumentation({
dbStatementSerializer: _defaultDbStatementSerializer,
responseHook(span) {
addOriginToSpan(span, 'auto.db.otel.mongo');
},
}),
);

/**
* Replaces values in document with '?', hiding PII and helping grouping.
*/
export function _defaultDbStatementSerializer(commandObj: Record<string, unknown>): string {
const resultObj = _scrubStatement(commandObj);
return JSON.stringify(resultObj);
}

function _scrubStatement(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(element => _scrubStatement(element));
}

if (isCommandObj(value)) {
const initial: Record<string, unknown> = {};
return Object.entries(value)
.map(([key, element]) => [key, _scrubStatement(element)])
.reduce((prev, current) => {
if (isCommandEntry(current)) {
prev[current[0]] = current[1];
}
return prev;
}, initial);
}

// A value like string or number, possible contains PII, scrub it
return '?';
}

function isCommandObj(value: Record<string, unknown> | unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !isBuffer(value);
}

function isBuffer(value: unknown): boolean {
let isBuffer = false;
if (typeof Buffer !== 'undefined') {
isBuffer = Buffer.isBuffer(value);
}
return isBuffer;
}

function isCommandEntry(value: [string, unknown] | unknown): value is [string, unknown] {
return Array.isArray(value);
}

const _mongoIntegration = (() => {
return {
name: INTEGRATION_NAME,
Expand Down
72 changes: 72 additions & 0 deletions packages/node/test/integrations/tracing/mongo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';

import {
_defaultDbStatementSerializer,
instrumentMongo,
mongoIntegration,
} from '../../../src/integrations/tracing/mongo';
import { INSTRUMENTED } from '../../../src/otel/instrument';

jest.mock('@opentelemetry/instrumentation-mongodb');

describe('Mongo', () => {
beforeEach(() => {
jest.clearAllMocks();
delete INSTRUMENTED.Mongo;

(MongoDBInstrumentation as unknown as jest.SpyInstance).mockImplementation(() => {
return {
setTracerProvider: () => undefined,
setMeterProvider: () => undefined,
getConfig: () => ({}),
setConfig: () => ({}),
enable: () => undefined,
};
});
});

it('defaults are correct for instrumentMongo', () => {
instrumentMongo();

expect(MongoDBInstrumentation).toHaveBeenCalledTimes(1);
expect(MongoDBInstrumentation).toHaveBeenCalledWith({
dbStatementSerializer: expect.any(Function),
responseHook: expect.any(Function),
});
});

it('defaults are correct for mongoIntegration', () => {
mongoIntegration().setupOnce!();

expect(MongoDBInstrumentation).toHaveBeenCalledTimes(1);
expect(MongoDBInstrumentation).toHaveBeenCalledWith({
responseHook: expect.any(Function),
dbStatementSerializer: expect.any(Function),
});
});

describe('_defaultDbStatementSerializer', () => {
it('rewrites strings as ?', () => {
const serialized = _defaultDbStatementSerializer({
find: 'foo',
});
expect(JSON.parse(serialized).find).toBe('?');
});

it('rewrites nested strings as ?', () => {
const serialized = _defaultDbStatementSerializer({
find: {
inner: 'foo',
},
});
expect(JSON.parse(serialized).find.inner).toBe('?');
});

it('rewrites Buffer as ?', () => {
const serialized = _defaultDbStatementSerializer({
find: Buffer.from('foo', 'utf8'),
});
expect(JSON.parse(serialized).find).toBe('?');
});
});
});

0 comments on commit 6d1a251

Please sign in to comment.