Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(mongo): rewrite Buffer as ? during serialization #14071

Merged
merged 7 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
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;
Comment on lines +55 to +59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary, all our node versions should support this so let's just check normally here :)

}

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('?');
});
});
});
Loading