mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-07 19:34:17 +00:00
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:
parent
43bf7a504e
commit
e00687408c
@ -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([
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -156,7 +156,6 @@ export const provision = async (
|
||||
opts: KeyExchangeOpts,
|
||||
) => {
|
||||
await config.initialized();
|
||||
await eventTracker.initialized();
|
||||
|
||||
let device: Device | null = null;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user