n8n/packages/cli/src/telemetry/telemetry-event-relay.service.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

501 lines
14 KiB
TypeScript
Raw Normal View History

import { Service } from 'typedi';
import { EventService } from '@/eventbus/event.service';
import type { Event } from '@/eventbus/event.types';
import { Telemetry } from '.';
import config from '@/config';
import os from 'node:os';
import { License } from '@/License';
import { GlobalConfig } from '@n8n/config';
import { N8N_VERSION } from '@/constants';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
@Service()
export class TelemetryEventRelay {
constructor(
private readonly eventService: EventService,
private readonly telemetry: Telemetry,
private readonly license: License,
private readonly globalConfig: GlobalConfig,
private readonly workflowRepository: WorkflowRepository,
) {}
async init() {
if (!config.getEnv('diagnostics.enabled')) return;
await this.telemetry.init();
this.setupHandlers();
}
private setupHandlers() {
this.eventService.on('server-started', async () => await this.serverStarted());
this.eventService.on('team-project-updated', (event) => this.teamProjectUpdated(event));
this.eventService.on('team-project-deleted', (event) => this.teamProjectDeleted(event));
this.eventService.on('team-project-created', (event) => this.teamProjectCreated(event));
this.eventService.on('source-control-settings-updated', (event) =>
this.sourceControlSettingsUpdated(event),
);
this.eventService.on('source-control-user-started-pull-ui', (event) =>
this.sourceControlUserStartedPullUi(event),
);
this.eventService.on('source-control-user-finished-pull-ui', (event) =>
this.sourceControlUserFinishedPullUi(event),
);
this.eventService.on('source-control-user-pulled-api', (event) =>
this.sourceControlUserPulledApi(event),
);
this.eventService.on('source-control-user-started-push-ui', (event) =>
this.sourceControlUserStartedPushUi(event),
);
this.eventService.on('source-control-user-finished-push-ui', (event) =>
this.sourceControlUserFinishedPushUi(event),
);
this.eventService.on('license-renewal-attempted', (event) => {
this.licenseRenewalAttempted(event);
});
this.eventService.on('variable-created', () => this.variableCreated());
this.eventService.on('external-secrets-provider-settings-saved', (event) => {
this.externalSecretsProviderSettingsSaved(event);
});
this.eventService.on('public-api-invoked', (event) => {
this.publicApiInvoked(event);
});
this.eventService.on('public-api-key-created', (event) => {
this.publicApiKeyCreated(event);
});
this.eventService.on('public-api-key-deleted', (event) => {
this.publicApiKeyDeleted(event);
});
this.eventService.on('community-package-installed', (event) => {
this.communityPackageInstalled(event);
});
this.eventService.on('community-package-updated', (event) => {
this.communityPackageUpdated(event);
});
this.eventService.on('community-package-deleted', (event) => {
this.communityPackageDeleted(event);
});
this.eventService.on('credentials-created', (event) => {
this.credentialsCreated(event);
});
this.eventService.on('credentials-shared', (event) => {
this.credentialsShared(event);
});
this.eventService.on('credentials-updated', (event) => {
this.credentialsUpdated(event);
});
this.eventService.on('credentials-deleted', (event) => {
this.credentialsDeleted(event);
});
this.eventService.on('ldap-general-sync-finished', (event) => {
this.ldapGeneralSyncFinished(event);
});
this.eventService.on('ldap-settings-updated', (event) => {
this.ldapSettingsUpdated(event);
});
this.eventService.on('ldap-login-sync-failed', (event) => {
this.ldapLoginSyncFailed(event);
});
this.eventService.on('login-failed-due-to-ldap-disabled', (event) => {
this.loginFailedDueToLdapDisabled(event);
});
}
private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) {
void this.telemetry.track('Project settings updated', {
user_id: userId,
role,
// eslint-disable-next-line @typescript-eslint/no-shadow
members: members.map(({ userId: user_id, role }) => ({ user_id, role })),
project_id: projectId,
});
}
private teamProjectDeleted({
userId,
role,
projectId,
removalType,
targetProjectId,
}: Event['team-project-deleted']) {
void this.telemetry.track('User deleted project', {
user_id: userId,
role,
project_id: projectId,
removal_type: removalType,
target_project_id: targetProjectId,
});
}
private teamProjectCreated({ userId, role }: Event['team-project-created']) {
void this.telemetry.track('User created project', {
user_id: userId,
role,
});
}
private sourceControlSettingsUpdated({
branchName,
readOnlyInstance,
repoType,
connected,
}: Event['source-control-settings-updated']) {
void this.telemetry.track('User updated source control settings', {
branch_name: branchName,
read_only_instance: readOnlyInstance,
repo_type: repoType,
connected,
});
}
private sourceControlUserStartedPullUi({
workflowUpdates,
workflowConflicts,
credConflicts,
}: Event['source-control-user-started-pull-ui']) {
void this.telemetry.track('User started pull via UI', {
workflow_updates: workflowUpdates,
workflow_conflicts: workflowConflicts,
cred_conflicts: credConflicts,
});
}
private sourceControlUserFinishedPullUi({
workflowUpdates,
}: Event['source-control-user-finished-pull-ui']) {
void this.telemetry.track('User finished pull via UI', {
workflow_updates: workflowUpdates,
});
}
private sourceControlUserPulledApi({
workflowUpdates,
forced,
}: Event['source-control-user-pulled-api']) {
console.log('source-control-user-pulled-api', {
workflow_updates: workflowUpdates,
forced,
});
void this.telemetry.track('User pulled via API', {
workflow_updates: workflowUpdates,
forced,
});
}
private sourceControlUserStartedPushUi({
workflowsEligible,
workflowsEligibleWithConflicts,
credsEligible,
credsEligibleWithConflicts,
variablesEligible,
}: Event['source-control-user-started-push-ui']) {
void this.telemetry.track('User started push via UI', {
workflows_eligible: workflowsEligible,
workflows_eligible_with_conflicts: workflowsEligibleWithConflicts,
creds_eligible: credsEligible,
creds_eligible_with_conflicts: credsEligibleWithConflicts,
variables_eligible: variablesEligible,
});
}
private sourceControlUserFinishedPushUi({
workflowsEligible,
workflowsPushed,
credsPushed,
variablesPushed,
}: Event['source-control-user-finished-push-ui']) {
void this.telemetry.track('User finished push via UI', {
workflows_eligible: workflowsEligible,
workflows_pushed: workflowsPushed,
creds_pushed: credsPushed,
variables_pushed: variablesPushed,
});
}
private licenseRenewalAttempted({ success }: Event['license-renewal-attempted']) {
void this.telemetry.track('Instance attempted to refresh license', {
success,
});
}
private variableCreated() {
void this.telemetry.track('User created variable');
}
private externalSecretsProviderSettingsSaved({
userId,
vaultType,
isValid,
isNew,
errorMessage,
}: Event['external-secrets-provider-settings-saved']) {
void this.telemetry.track('User updated external secrets settings', {
user_id: userId,
vault_type: vaultType,
is_valid: isValid,
is_new: isNew,
error_message: errorMessage,
});
}
private publicApiInvoked({ userId, path, method, apiVersion }: Event['public-api-invoked']) {
void this.telemetry.track('User invoked API', {
user_id: userId,
path,
method,
api_version: apiVersion,
});
}
private publicApiKeyCreated(event: Event['public-api-key-created']) {
const { user, publicApi } = event;
void this.telemetry.track('API key created', {
user_id: user.id,
public_api: publicApi,
});
}
private publicApiKeyDeleted(event: Event['public-api-key-deleted']) {
const { user, publicApi } = event;
void this.telemetry.track('API key deleted', {
user_id: user.id,
public_api: publicApi,
});
}
private communityPackageInstalled({
user,
inputString,
packageName,
success,
packageVersion,
packageNodeNames,
packageAuthor,
packageAuthorEmail,
failureReason,
}: Event['community-package-installed']) {
void this.telemetry.track('cnr package install finished', {
user_id: user.id,
input_string: inputString,
package_name: packageName,
success,
package_version: packageVersion,
package_node_names: packageNodeNames,
package_author: packageAuthor,
package_author_email: packageAuthorEmail,
failure_reason: failureReason,
});
}
private communityPackageUpdated({
user,
packageName,
packageVersionCurrent,
packageVersionNew,
packageNodeNames,
packageAuthor,
packageAuthorEmail,
}: Event['community-package-updated']) {
void this.telemetry.track('cnr package updated', {
user_id: user.id,
package_name: packageName,
package_version_current: packageVersionCurrent,
package_version_new: packageVersionNew,
package_node_names: packageNodeNames,
package_author: packageAuthor,
package_author_email: packageAuthorEmail,
});
}
private communityPackageDeleted({
user,
packageName,
packageVersion,
packageNodeNames,
packageAuthor,
packageAuthorEmail,
}: Event['community-package-deleted']) {
void this.telemetry.track('cnr package deleted', {
user_id: user.id,
package_name: packageName,
package_version: packageVersion,
package_node_names: packageNodeNames,
package_author: packageAuthor,
package_author_email: packageAuthorEmail,
});
}
private credentialsCreated({
user,
credentialType,
credentialId,
projectId,
projectType,
}: Event['credentials-created']) {
void this.telemetry.track('User created credentials', {
user_id: user.id,
credential_type: credentialType,
credential_id: credentialId,
project_id: projectId,
project_type: projectType,
});
}
private credentialsShared({
user,
credentialType,
credentialId,
userIdSharer,
userIdsShareesAdded,
shareesRemoved,
}: Event['credentials-shared']) {
void this.telemetry.track('User updated cred sharing', {
user_id: user.id,
credential_type: credentialType,
credential_id: credentialId,
user_id_sharer: userIdSharer,
user_ids_sharees_added: userIdsShareesAdded,
sharees_removed: shareesRemoved,
});
}
private credentialsUpdated({ user, credentialId, credentialType }: Event['credentials-updated']) {
void this.telemetry.track('User updated credentials', {
user_id: user.id,
credential_type: credentialType,
credential_id: credentialId,
});
}
private credentialsDeleted({ user, credentialId, credentialType }: Event['credentials-deleted']) {
void this.telemetry.track('User deleted credentials', {
user_id: user.id,
credential_type: credentialType,
credential_id: credentialId,
});
}
private ldapGeneralSyncFinished({
type,
succeeded,
usersSynced,
error,
}: Event['ldap-general-sync-finished']) {
void this.telemetry.track('Ldap general sync finished', {
type,
succeeded,
users_synced: usersSynced,
error,
});
}
private ldapSettingsUpdated({
userId,
loginIdAttribute,
firstNameAttribute,
lastNameAttribute,
emailAttribute,
ldapIdAttribute,
searchPageSize,
searchTimeout,
synchronizationEnabled,
synchronizationInterval,
loginLabel,
loginEnabled,
}: Event['ldap-settings-updated']) {
void this.telemetry.track('User updated Ldap settings', {
user_id: userId,
loginIdAttribute,
firstNameAttribute,
lastNameAttribute,
emailAttribute,
ldapIdAttribute,
searchPageSize,
searchTimeout,
synchronizationEnabled,
synchronizationInterval,
loginLabel,
loginEnabled,
});
}
private ldapLoginSyncFailed({ error }: Event['ldap-login-sync-failed']) {
void this.telemetry.track('Ldap login sync failed', { error });
}
private loginFailedDueToLdapDisabled({ userId }: Event['login-failed-due-to-ldap-disabled']) {
void this.telemetry.track('User login failed since ldap disabled', { user_ud: userId });
}
private async serverStarted() {
const cpus = os.cpus();
const binaryDataConfig = config.getEnv('binaryDataManager');
const isS3Selected = config.getEnv('binaryDataManager.mode') === 's3';
const isS3Available = config.getEnv('binaryDataManager.availableModes').includes('s3');
const isS3Licensed = this.license.isBinaryDataS3Licensed();
const authenticationMethod = config.getEnv('userManagement.authenticationMethod');
const info = {
version_cli: N8N_VERSION,
db_type: this.globalConfig.database.type,
n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled,
n8n_disable_production_main_process: config.getEnv(
'endpoints.disableProductionWebhooksOnMainProcess',
),
system_info: {
os: {
type: os.type(),
version: os.version(),
},
memory: os.totalmem() / 1024,
cpus: {
count: cpus.length,
model: cpus[0].model,
speed: cpus[0].speed,
},
},
execution_variables: {
executions_mode: config.getEnv('executions.mode'),
executions_timeout: config.getEnv('executions.timeout'),
executions_timeout_max: config.getEnv('executions.maxTimeout'),
executions_data_save_on_error: config.getEnv('executions.saveDataOnError'),
executions_data_save_on_success: config.getEnv('executions.saveDataOnSuccess'),
executions_data_save_on_progress: config.getEnv('executions.saveExecutionProgress'),
executions_data_save_manual_executions: config.getEnv(
'executions.saveDataManualExecutions',
),
executions_data_prune: config.getEnv('executions.pruneData'),
executions_data_max_age: config.getEnv('executions.pruneDataMaxAge'),
},
n8n_deployment_type: config.getEnv('deployment.type'),
n8n_binary_data_mode: binaryDataConfig.mode,
smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp',
ldap_allowed: authenticationMethod === 'ldap',
saml_enabled: authenticationMethod === 'saml',
license_plan_name: this.license.getPlanName(),
license_tenant_id: config.getEnv('license.tenantId'),
binary_data_s3: isS3Available && isS3Selected && isS3Licensed,
multi_main_setup_enabled: config.getEnv('multiMainSetup.enabled'),
};
const firstWorkflow = await this.workflowRepository.findOne({
select: ['createdAt'],
order: { createdAt: 'ASC' },
where: {},
});
void Promise.all([
this.telemetry.identify(info),
this.telemetry.track('Instance started', {
...info,
earliest_workflow_created: firstWorkflow?.createdAt,
}),
]);
}
}