2021-10-18 20:57:49 -07:00
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
|
|
import TelemetryClient = require('@rudderstack/rudder-sdk-node');
|
|
|
|
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
|
|
|
import config = require('../../config');
|
|
|
|
import { getLogger } from '../Logger';
|
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
type CountBufferItemKey =
|
|
|
|
| 'manual_success_count'
|
|
|
|
| 'manual_error_count'
|
|
|
|
| 'prod_success_count'
|
|
|
|
| 'prod_error_count';
|
|
|
|
|
|
|
|
type FirstExecutionItemKey =
|
|
|
|
| 'first_manual_success'
|
|
|
|
| 'first_manual_error'
|
|
|
|
| 'first_prod_success'
|
|
|
|
| 'first_prod_error';
|
|
|
|
|
|
|
|
type IExecutionCountsBufferItem = {
|
|
|
|
[key in CountBufferItemKey]: number;
|
|
|
|
};
|
2021-10-18 20:57:49 -07:00
|
|
|
|
|
|
|
interface IExecutionCountsBuffer {
|
|
|
|
[workflowId: string]: IExecutionCountsBufferItem;
|
|
|
|
}
|
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
type IFirstExecutions = {
|
|
|
|
[key in FirstExecutionItemKey]: Date | undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
interface IExecutionsBuffer {
|
|
|
|
counts: IExecutionCountsBuffer;
|
|
|
|
firstExecutions: IFirstExecutions;
|
|
|
|
}
|
|
|
|
|
2021-10-18 20:57:49 -07:00
|
|
|
export class Telemetry {
|
|
|
|
private client?: TelemetryClient;
|
|
|
|
|
|
|
|
private instanceId: string;
|
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
private versionCli: string;
|
2021-10-18 20:57:49 -07:00
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
private pulseIntervalReference: NodeJS.Timeout;
|
2021-10-18 20:57:49 -07:00
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
private executionCountsBuffer: IExecutionsBuffer = {
|
|
|
|
counts: {},
|
|
|
|
firstExecutions: {
|
|
|
|
first_manual_error: undefined,
|
|
|
|
first_manual_success: undefined,
|
|
|
|
first_prod_error: undefined,
|
|
|
|
first_prod_success: undefined,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(instanceId: string, versionCli: string) {
|
2021-10-18 20:57:49 -07:00
|
|
|
this.instanceId = instanceId;
|
2021-12-10 06:29:05 -08:00
|
|
|
this.versionCli = versionCli;
|
2021-10-18 20:57:49 -07:00
|
|
|
|
|
|
|
const enabled = config.get('diagnostics.enabled') as boolean;
|
2022-02-24 08:15:30 -08:00
|
|
|
const logLevel = config.get('logs.level') as boolean;
|
2021-10-18 20:57:49 -07:00
|
|
|
if (enabled) {
|
|
|
|
const conf = config.get('diagnostics.config.backend') as string;
|
|
|
|
const [key, url] = conf.split(';');
|
|
|
|
|
|
|
|
if (!key || !url) {
|
|
|
|
const logger = getLogger();
|
|
|
|
LoggerProxy.init(logger);
|
|
|
|
logger.warn('Diagnostics backend config is invalid');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-02-24 08:15:30 -08:00
|
|
|
this.client = new TelemetryClient(key, url, { logLevel });
|
2021-10-18 20:57:49 -07:00
|
|
|
|
|
|
|
this.pulseIntervalReference = setInterval(async () => {
|
|
|
|
void this.pulse();
|
|
|
|
}, 6 * 60 * 60 * 1000); // every 6 hours
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async pulse(): Promise<unknown> {
|
|
|
|
if (!this.client) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
const allPromises = Object.keys(this.executionCountsBuffer.counts).map(async (workflowId) => {
|
2021-10-18 20:57:49 -07:00
|
|
|
const promise = this.track('Workflow execution count', {
|
2021-12-10 06:29:05 -08:00
|
|
|
version_cli: this.versionCli,
|
2021-10-18 20:57:49 -07:00
|
|
|
workflow_id: workflowId,
|
2021-12-10 06:29:05 -08:00
|
|
|
...this.executionCountsBuffer.counts[workflowId],
|
|
|
|
...this.executionCountsBuffer.firstExecutions,
|
2021-10-18 20:57:49 -07:00
|
|
|
});
|
2021-12-10 06:29:05 -08:00
|
|
|
|
|
|
|
this.executionCountsBuffer.counts[workflowId].manual_error_count = 0;
|
|
|
|
this.executionCountsBuffer.counts[workflowId].manual_success_count = 0;
|
|
|
|
this.executionCountsBuffer.counts[workflowId].prod_error_count = 0;
|
|
|
|
this.executionCountsBuffer.counts[workflowId].prod_success_count = 0;
|
2021-10-18 20:57:49 -07:00
|
|
|
|
|
|
|
return promise;
|
|
|
|
});
|
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
allPromises.push(this.track('pulse', { version_cli: this.versionCli }));
|
2021-10-18 20:57:49 -07:00
|
|
|
return Promise.all(allPromises);
|
|
|
|
}
|
|
|
|
|
|
|
|
async trackWorkflowExecution(properties: IDataObject): Promise<void> {
|
|
|
|
if (this.client) {
|
|
|
|
const workflowId = properties.workflow_id as string;
|
2021-12-10 06:29:05 -08:00
|
|
|
this.executionCountsBuffer.counts[workflowId] = this.executionCountsBuffer.counts[
|
|
|
|
workflowId
|
|
|
|
] ?? {
|
2021-10-18 20:57:49 -07:00
|
|
|
manual_error_count: 0,
|
|
|
|
manual_success_count: 0,
|
|
|
|
prod_error_count: 0,
|
|
|
|
prod_success_count: 0,
|
|
|
|
};
|
|
|
|
|
2021-12-10 06:29:05 -08:00
|
|
|
let countKey: CountBufferItemKey;
|
|
|
|
let firstExecKey: FirstExecutionItemKey;
|
|
|
|
|
2021-10-18 20:57:49 -07:00
|
|
|
if (
|
|
|
|
properties.success === false &&
|
|
|
|
properties.error_node_type &&
|
|
|
|
(properties.error_node_type as string).startsWith('n8n-nodes-base')
|
|
|
|
) {
|
|
|
|
// errored exec
|
|
|
|
void this.track('Workflow execution errored', properties);
|
|
|
|
|
|
|
|
if (properties.is_manual) {
|
2021-12-10 06:29:05 -08:00
|
|
|
firstExecKey = 'first_manual_error';
|
|
|
|
countKey = 'manual_error_count';
|
2021-10-18 20:57:49 -07:00
|
|
|
} else {
|
2021-12-10 06:29:05 -08:00
|
|
|
firstExecKey = 'first_prod_error';
|
|
|
|
countKey = 'prod_error_count';
|
2021-10-18 20:57:49 -07:00
|
|
|
}
|
|
|
|
} else if (properties.is_manual) {
|
2021-12-10 06:29:05 -08:00
|
|
|
countKey = 'manual_success_count';
|
|
|
|
firstExecKey = 'first_manual_success';
|
2021-10-18 20:57:49 -07:00
|
|
|
} else {
|
2021-12-10 06:29:05 -08:00
|
|
|
countKey = 'prod_success_count';
|
|
|
|
firstExecKey = 'first_prod_success';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!this.executionCountsBuffer.firstExecutions[firstExecKey] &&
|
|
|
|
this.executionCountsBuffer.counts[workflowId][countKey] === 0
|
|
|
|
) {
|
|
|
|
this.executionCountsBuffer.firstExecutions[firstExecKey] = new Date();
|
2021-10-18 20:57:49 -07:00
|
|
|
}
|
2021-12-10 06:29:05 -08:00
|
|
|
|
|
|
|
this.executionCountsBuffer.counts[workflowId][countKey]++;
|
2021-10-18 20:57:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async trackN8nStop(): Promise<void> {
|
|
|
|
clearInterval(this.pulseIntervalReference);
|
|
|
|
void this.track('User instance stopped');
|
|
|
|
return new Promise<void>((resolve) => {
|
|
|
|
if (this.client) {
|
|
|
|
this.client.flush(resolve);
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async identify(traits?: IDataObject): Promise<void> {
|
|
|
|
return new Promise<void>((resolve) => {
|
|
|
|
if (this.client) {
|
|
|
|
this.client.identify(
|
|
|
|
{
|
|
|
|
userId: this.instanceId,
|
2021-11-03 02:42:54 -07:00
|
|
|
anonymousId: '000000000000',
|
2021-10-18 20:57:49 -07:00
|
|
|
traits: {
|
|
|
|
...traits,
|
|
|
|
instanceId: this.instanceId,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
resolve,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async track(eventName: string, properties?: IDataObject): Promise<void> {
|
|
|
|
return new Promise<void>((resolve) => {
|
|
|
|
if (this.client) {
|
|
|
|
this.client.track(
|
|
|
|
{
|
|
|
|
userId: this.instanceId,
|
2021-11-03 02:42:54 -07:00
|
|
|
anonymousId: '000000000000',
|
2021-10-18 20:57:49 -07:00
|
|
|
event: eventName,
|
|
|
|
properties,
|
|
|
|
},
|
|
|
|
resolve,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|