Disable event tracking

The supervisor used to rely on specific event reporting for identifying
issues at runtime. As the platform has grown, it has become much more
difficult to get any signal from the event noise. Recently the API side
for these events has been disabled, meaning these events only
contribute to bandwidth consumption.  This commit disables the
event reporting feature of the supervisor which will be most likely
replaced by something like Sentry in the near future.

Change-type: minor
This commit is contained in:
Felipe Lalanne 2022-07-19 12:29:13 -04:00
parent 43bf7a504e
commit e00687408c
5 changed files with 0 additions and 315 deletions

View File

@ -546,7 +546,6 @@ export let balenaApi: PinejsClientRequest | null = null;
export const initialized = _.once(async () => {
await config.initialized();
await eventTracker.initialized();
await deviceState.initialized();
const { unmanaged, apiEndpoint, currentApiKey } = await config.getMany([

View File

@ -1,18 +1,10 @@
import mask = require('json-mask');
import * as _ from 'lodash';
import * as memoizee from 'memoizee';
import * as mixpanel from 'mixpanel';
import * as config from './config';
import log from './lib/supervisor-console';
import supervisorVersion = require('./lib/supervisor-version');
export type EventTrackProperties = Dictionary<any>;
// The minimum amount of time to wait between sending
// events of the same type
const eventDebounceTime = 60000;
const mixpanelMask = [
'appId',
'delay',
@ -24,39 +16,10 @@ const mixpanelMask = [
'stateDiff/local(os_version,supervisor_version,ip_address,apps/*/services)',
].join(',');
let defaultProperties: EventTrackProperties;
// We must export this for the tests, but we make no references
// to it within the rest of the supervisor codebase
export let client: mixpanel.Mixpanel | null = null;
export const initialized = _.once(async () => {
await config.initialized();
const { unmanaged, mixpanelHost, mixpanelToken, uuid } = await config.getMany(
['unmanaged', 'mixpanelHost', 'mixpanelToken', 'uuid'],
);
defaultProperties = {
distinct_id: uuid,
uuid,
supervisorVersion,
};
if (unmanaged || mixpanelHost == null || mixpanelToken == null) {
return;
}
client = mixpanel.init(mixpanelToken, {
host: mixpanelHost.host,
path: mixpanelHost.path,
});
});
export async function track(
event: string,
properties: EventTrackProperties | Error = {},
) {
await initialized();
if (properties instanceof Error) {
properties = { error: properties };
}
@ -73,30 +36,4 @@ export async function track(
// Don't send potentially sensitive information, by using a whitelist
properties = mask(properties, mixpanelMask);
log.event('Event:', event, JSON.stringify(properties));
if (client == null) {
return;
}
properties = assignDefaultProperties(properties);
throttleddLogger(event)(properties);
}
const throttleddLogger = memoizee(
(event: string) => {
// Call this function at maximum once every minute
return _.throttle(
(properties: EventTrackProperties | Error) => {
client?.track(event, properties);
},
eventDebounceTime,
{ leading: true },
);
},
{ primitive: true },
);
function assignDefaultProperties(
properties: EventTrackProperties,
): EventTrackProperties {
return _.merge({}, properties, defaultProperties);
}

View File

@ -156,7 +156,6 @@ export const provision = async (
opts: KeyExchangeOpts,
) => {
await config.initialized();
await eventTracker.initialized();
let device: Device | null = null;

View File

@ -2,7 +2,6 @@ import * as apiBinder from './api-binder';
import * as db from './db';
import * as config from './config';
import * as deviceState from './device-state';
import * as eventTracker from './event-tracker';
import { intialiseContractRequirements } from './lib/contracts';
import { normaliseLegacyDatabase } from './lib/legacy';
import * as osRelease from './lib/os-release';
@ -36,7 +35,6 @@ export class Supervisor {
await db.initialized();
await config.initialized();
await eventTracker.initialized();
await avahi.initialized();
log.debug('Starting logging infrastructure');
await logger.initialized();

View File

@ -1,248 +0,0 @@
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
import { expect } from 'chai';
import * as mixpanel from 'mixpanel';
import log from '~/lib/supervisor-console';
import supervisorVersion = require('~/lib/supervisor-version');
import * as config from '~/src/config';
describe('EventTracker', () => {
let logEventStub: SinonStub;
before(() => {
logEventStub = stub(log, 'event');
delete require.cache[require.resolve('~/src/event-tracker')];
});
afterEach(() => {
logEventStub.reset();
});
after(() => {
logEventStub.restore();
});
describe('Unmanaged', () => {
let configStub: SinonStub;
let eventTracker: typeof import('~/src/event-tracker');
before(async () => {
configStub = stub(config, 'getMany').returns(
Promise.resolve({
unmanaged: true,
uuid: 'foobar',
mixpanelHost: { host: '', path: '' },
mixpanelToken: '',
}) as any,
);
eventTracker = await import('~/src/event-tracker');
});
after(() => {
configStub.restore();
delete require.cache[require.resolve('~/src/event-tracker')];
});
it('initializes in unmanaged mode', () => {
expect(eventTracker.initialized()).to.be.fulfilled.then(() => {
expect(eventTracker.client).to.be.null;
});
});
it('logs events in unmanaged mode, with the correct properties', async () => {
await eventTracker.track('Test event', { appId: 'someValue' });
expect(logEventStub).to.be.calledWith(
'Event:',
'Test event',
JSON.stringify({ appId: 'someValue' }),
);
});
});
describe('Init', () => {
let eventTracker: typeof import('~/src/event-tracker');
let configStub: SinonStub;
let mixpanelSpy: SinonSpy;
before(async () => {
configStub = stub(config, 'getMany').returns(
Promise.resolve({
mixpanelToken: 'someToken',
uuid: 'barbaz',
mixpanelHost: { host: '', path: '' },
unmanaged: false,
}) as any,
);
mixpanelSpy = spy(mixpanel, 'init');
eventTracker = await import('~/src/event-tracker');
});
after(() => {
configStub.restore();
mixpanelSpy.restore();
delete require.cache[require.resolve('~/src/event-tracker')];
});
it('initializes a mixpanel client when not in unmanaged mode', () => {
expect(eventTracker.initialized()).to.be.fulfilled.then(() => {
expect(mixpanel.init).to.have.been.calledWith('someToken');
// @ts-expect-error
expect(eventTracker.client.token).to.equal('someToken');
// @ts-expect-error
expect(eventTracker.client.track).to.be.a('function');
});
});
});
describe('Managed', () => {
let eventTracker: typeof import('~/src/event-tracker');
let configStub: SinonStub;
let mixpanelStub: SinonStub;
before(async () => {
configStub = stub(config, 'getMany').returns(
Promise.resolve({
mixpanelToken: 'someToken',
uuid: 'barbaz',
mixpanelHost: { host: '', path: '' },
unmanaged: false,
}) as any,
);
mixpanelStub = stub(mixpanel, 'init').returns({
token: 'someToken',
track: stub(),
} as any);
eventTracker = await import('~/src/event-tracker');
await eventTracker.initialized();
});
after(() => {
configStub.restore();
mixpanelStub.restore();
delete require.cache[require.resolve('~/src/event-tracker')];
});
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', async () => {
await eventTracker.track('Test event 2', { appId: 'someOtherValue' });
expect(logEventStub).to.be.calledWith(
'Event:',
'Test event 2',
JSON.stringify({ appId: 'someOtherValue' }),
);
// @ts-expect-error
expect(eventTracker.client.track).to.be.calledWith('Test event 2', {
appId: 'someOtherValue',
uuid: 'barbaz',
distinct_id: 'barbaz',
supervisorVersion,
});
});
it('can be passed an Error and it is added to the event properties', async () => {
const theError = new Error('something went wrong');
await eventTracker.track('Error event', theError);
// @ts-expect-error
expect(eventTracker.client.track).to.be.calledWith('Error event', {
error: {
message: theError.message,
stack: theError.stack,
},
uuid: 'barbaz',
distinct_id: 'barbaz',
supervisorVersion,
});
});
it('hides service environment variables, to avoid logging keys or secrets', async () => {
const props = {
service: {
appId: '1',
environment: {
RESIN_API_KEY: 'foo',
RESIN_SUPERVISOR_API_KEY: 'bar',
OTHER_VAR: 'hi',
},
},
};
await eventTracker.track('Some app event', props);
// @ts-expect-error
expect(eventTracker.client.track).to.be.calledWith('Some app event', {
service: { appId: '1' },
uuid: 'barbaz',
distinct_id: 'barbaz',
supervisorVersion,
});
});
it('should handle being passed no properties object', () => {
expect(eventTracker.track('no-options')).to.be.fulfilled;
});
});
describe('Rate limiting', () => {
let eventTracker: typeof import('~/src/event-tracker');
let mixpanelStub: SinonStub;
before(async () => {
mixpanelStub = stub(mixpanel, 'init').returns({
track: stub(),
} as any);
eventTracker = await import('~/src/event-tracker');
await eventTracker.initialized();
});
after(() => {
mixpanelStub.restore();
delete require.cache[require.resolve('~/src/event-tracker')];
});
it('should rate limit events of the same type', async () => {
// @ts-expect-error resetting a non-stub typed function
eventTracker.client?.track.reset();
await eventTracker.track('test', {});
await eventTracker.track('test', {});
await eventTracker.track('test', {});
await eventTracker.track('test', {});
await eventTracker.track('test', {});
expect(eventTracker.client?.track).to.have.callCount(1);
});
it('should rate limit events of the same type with different arguments', async () => {
// @ts-expect-error resetting a non-stub typed function
eventTracker.client?.track.reset();
await eventTracker.track('test2', { a: 1 });
await eventTracker.track('test2', { b: 2 });
await eventTracker.track('test2', { c: 3 });
await eventTracker.track('test2', { d: 4 });
await eventTracker.track('test2', { e: 5 });
expect(eventTracker.client?.track).to.have.callCount(1);
});
it('should not rate limit events of different types', async () => {
// @ts-expect-error resetting a non-stub typed function
eventTracker.client?.track.reset();
await eventTracker.track('test3', { a: 1 });
await eventTracker.track('test4', { b: 2 });
await eventTracker.track('test5', { c: 3 });
await eventTracker.track('test6', { d: 4 });
await eventTracker.track('test7', { e: 5 });
expect(eventTracker.client?.track).to.have.callCount(5);
});
});
});