@theia/telemetry 1.74.0-next.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +66 -0
  2. package/lib/browser/browser-telemetry-service.d.ts +16 -0
  3. package/lib/browser/browser-telemetry-service.d.ts.map +1 -0
  4. package/lib/browser/browser-telemetry-service.js +68 -0
  5. package/lib/browser/browser-telemetry-service.js.map +1 -0
  6. package/lib/browser/browser-telemetry-service.spec.d.ts +2 -0
  7. package/lib/browser/browser-telemetry-service.spec.d.ts.map +1 -0
  8. package/lib/browser/browser-telemetry-service.spec.js +163 -0
  9. package/lib/browser/browser-telemetry-service.spec.js.map +1 -0
  10. package/lib/browser/index.d.ts +2 -0
  11. package/lib/browser/index.d.ts.map +1 -0
  12. package/lib/browser/index.js +20 -0
  13. package/lib/browser/index.js.map +1 -0
  14. package/lib/browser/telemetry-frontend-module.d.ts +4 -0
  15. package/lib/browser/telemetry-frontend-module.d.ts.map +1 -0
  16. package/lib/browser/telemetry-frontend-module.js +35 -0
  17. package/lib/browser/telemetry-frontend-module.js.map +1 -0
  18. package/lib/browser-only/browser-only-telemetry-service.d.ts +5 -0
  19. package/lib/browser-only/browser-only-telemetry-service.d.ts.map +1 -0
  20. package/lib/browser-only/browser-only-telemetry-service.js +28 -0
  21. package/lib/browser-only/browser-only-telemetry-service.js.map +1 -0
  22. package/lib/browser-only/browser-only-telemetry-service.spec.d.ts +2 -0
  23. package/lib/browser-only/browser-only-telemetry-service.spec.d.ts.map +1 -0
  24. package/lib/browser-only/browser-only-telemetry-service.spec.js +42 -0
  25. package/lib/browser-only/browser-only-telemetry-service.spec.js.map +1 -0
  26. package/lib/browser-only/telemetry-frontend-only-module.d.ts +4 -0
  27. package/lib/browser-only/telemetry-frontend-only-module.d.ts.map +1 -0
  28. package/lib/browser-only/telemetry-frontend-only-module.js +30 -0
  29. package/lib/browser-only/telemetry-frontend-only-module.js.map +1 -0
  30. package/lib/common/index.d.ts +7 -0
  31. package/lib/common/index.d.ts.map +1 -0
  32. package/lib/common/index.js +25 -0
  33. package/lib/common/index.js.map +1 -0
  34. package/lib/common/telemetry-consent-provider.d.ts +24 -0
  35. package/lib/common/telemetry-consent-provider.d.ts.map +1 -0
  36. package/lib/common/telemetry-consent-provider.js +73 -0
  37. package/lib/common/telemetry-consent-provider.js.map +1 -0
  38. package/lib/common/telemetry-consent-provider.spec.d.ts +2 -0
  39. package/lib/common/telemetry-consent-provider.spec.d.ts.map +1 -0
  40. package/lib/common/telemetry-consent-provider.spec.js +87 -0
  41. package/lib/common/telemetry-consent-provider.spec.js.map +1 -0
  42. package/lib/common/telemetry-preferences.d.ts +29 -0
  43. package/lib/common/telemetry-preferences.d.ts.map +1 -0
  44. package/lib/common/telemetry-preferences.js +77 -0
  45. package/lib/common/telemetry-preferences.js.map +1 -0
  46. package/lib/common/telemetry-preferences.spec.d.ts +2 -0
  47. package/lib/common/telemetry-preferences.spec.d.ts.map +1 -0
  48. package/lib/common/telemetry-preferences.spec.js +50 -0
  49. package/lib/common/telemetry-preferences.spec.js.map +1 -0
  50. package/lib/common/telemetry-protocol.d.ts +30 -0
  51. package/lib/common/telemetry-protocol.d.ts.map +1 -0
  52. package/lib/common/telemetry-protocol.js +78 -0
  53. package/lib/common/telemetry-protocol.js.map +1 -0
  54. package/lib/common/telemetry-service.d.ts +28 -0
  55. package/lib/common/telemetry-service.d.ts.map +1 -0
  56. package/lib/common/telemetry-service.js +65 -0
  57. package/lib/common/telemetry-service.js.map +1 -0
  58. package/lib/common/telemetry-service.spec.d.ts +2 -0
  59. package/lib/common/telemetry-service.spec.d.ts.map +1 -0
  60. package/lib/common/telemetry-service.spec.js +128 -0
  61. package/lib/common/telemetry-service.spec.js.map +1 -0
  62. package/lib/common/telemetry-topic.d.ts +9 -0
  63. package/lib/common/telemetry-topic.d.ts.map +1 -0
  64. package/lib/common/telemetry-topic.js +61 -0
  65. package/lib/common/telemetry-topic.js.map +1 -0
  66. package/lib/common/telemetry-topic.spec.d.ts +2 -0
  67. package/lib/common/telemetry-topic.spec.d.ts.map +1 -0
  68. package/lib/common/telemetry-topic.spec.js +67 -0
  69. package/lib/common/telemetry-topic.spec.js.map +1 -0
  70. package/lib/common/telemetry-types.d.ts +5 -0
  71. package/lib/common/telemetry-types.d.ts.map +1 -0
  72. package/lib/common/telemetry-types.js +21 -0
  73. package/lib/common/telemetry-types.js.map +1 -0
  74. package/lib/common/test/recording-logger.d.ts +9 -0
  75. package/lib/common/test/recording-logger.d.ts.map +1 -0
  76. package/lib/common/test/recording-logger.js +36 -0
  77. package/lib/common/test/recording-logger.js.map +1 -0
  78. package/lib/node/index.d.ts +3 -0
  79. package/lib/node/index.d.ts.map +1 -0
  80. package/lib/node/index.js +21 -0
  81. package/lib/node/index.js.map +1 -0
  82. package/lib/node/telemetry-backend-module.d.ts +4 -0
  83. package/lib/node/telemetry-backend-module.d.ts.map +1 -0
  84. package/lib/node/telemetry-backend-module.js +37 -0
  85. package/lib/node/telemetry-backend-module.js.map +1 -0
  86. package/lib/node/telemetry-service-impl.d.ts +36 -0
  87. package/lib/node/telemetry-service-impl.d.ts.map +1 -0
  88. package/lib/node/telemetry-service-impl.js +208 -0
  89. package/lib/node/telemetry-service-impl.js.map +1 -0
  90. package/lib/node/telemetry-service-impl.spec.d.ts +2 -0
  91. package/lib/node/telemetry-service-impl.spec.d.ts.map +1 -0
  92. package/lib/node/telemetry-service-impl.spec.js +411 -0
  93. package/lib/node/telemetry-service-impl.spec.js.map +1 -0
  94. package/lib/node/telemetry-sink.d.ts +31 -0
  95. package/lib/node/telemetry-sink.d.ts.map +1 -0
  96. package/lib/node/telemetry-sink.js +21 -0
  97. package/lib/node/telemetry-sink.js.map +1 -0
  98. package/package.json +52 -0
  99. package/src/browser/browser-telemetry-service.spec.ts +193 -0
  100. package/src/browser/browser-telemetry-service.ts +69 -0
  101. package/src/browser/index.ts +17 -0
  102. package/src/browser/telemetry-frontend-module.ts +34 -0
  103. package/src/browser-only/browser-only-telemetry-service.spec.ts +44 -0
  104. package/src/browser-only/browser-only-telemetry-service.ts +23 -0
  105. package/src/browser-only/telemetry-frontend-only-module.ts +28 -0
  106. package/src/common/index.ts +22 -0
  107. package/src/common/telemetry-consent-provider.spec.ts +95 -0
  108. package/src/common/telemetry-consent-provider.ts +72 -0
  109. package/src/common/telemetry-preferences.spec.ts +56 -0
  110. package/src/common/telemetry-preferences.ts +91 -0
  111. package/src/common/telemetry-protocol.ts +100 -0
  112. package/src/common/telemetry-service.spec.ts +152 -0
  113. package/src/common/telemetry-service.ts +90 -0
  114. package/src/common/telemetry-topic.spec.ts +78 -0
  115. package/src/common/telemetry-topic.ts +60 -0
  116. package/src/common/telemetry-types.ts +21 -0
  117. package/src/common/test/recording-logger.ts +33 -0
  118. package/src/node/index.ts +18 -0
  119. package/src/node/telemetry-backend-module.ts +40 -0
  120. package/src/node/telemetry-service-impl.spec.ts +500 -0
  121. package/src/node/telemetry-service-impl.ts +214 -0
  122. package/src/node/telemetry-sink.ts +48 -0
@@ -0,0 +1,214 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 STMicroelectronics and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { ContributionProvider, ILogger } from '@theia/core/lib/common';
18
+ import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
19
+ import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
20
+ import { TelemetryConsentProvider, isKindAllowedByLevel } from '../common/telemetry-consent-provider';
21
+ import { TELEMETRY_FILTERS, TelemetryPreferences } from '../common/telemetry-preferences';
22
+ import {
23
+ TelemetryEvent, TelemetryRpc, createTelemetryEvent, describeTelemetryEventTopic, isValidTelemetryEvent, snapshotTelemetryEvent
24
+ } from '../common/telemetry-protocol';
25
+ import { TelemetryData, TelemetryReportOptions, TelemetryService } from '../common/telemetry-service';
26
+ import { isValidTelemetrySinkId, isValidTelemetryTopicPattern, matchesTelemetryTopic } from '../common/telemetry-topic';
27
+ import { BACKEND_TELEMETRY_SESSION } from '../common/telemetry-types';
28
+ import { TelemetrySink } from './telemetry-sink';
29
+
30
+ const MAX_PENDING_EVENTS = 1_000;
31
+
32
+ interface ValidatedTelemetrySink {
33
+ readonly sink: TelemetrySink;
34
+ readonly id: string;
35
+ readonly interests: readonly string[];
36
+ readonly scope: 'local' | 'remote';
37
+ }
38
+
39
+ @injectable()
40
+ export class TelemetryServiceImpl implements TelemetryService, TelemetryRpc, BackendApplicationContribution {
41
+
42
+ protected sinks: readonly ValidatedTelemetrySink[] | undefined;
43
+ protected filters: ReadonlyMap<string, readonly string[]> | undefined;
44
+ protected readinessState: 'pending' | 'ready' | 'failed' = 'pending';
45
+ protected pendingEvents: TelemetryEvent[] = [];
46
+ protected pendingQueueOverflowWarned = false;
47
+
48
+ @inject(TelemetryConsentProvider)
49
+ protected readonly consentProvider: TelemetryConsentProvider;
50
+
51
+ @inject(TelemetryPreferences)
52
+ protected readonly preferences: TelemetryPreferences;
53
+
54
+ @inject(ContributionProvider) @named(TelemetrySink)
55
+ protected readonly sinkProvider: ContributionProvider<TelemetrySink>;
56
+
57
+ @inject(ILogger) @named('telemetry:TelemetryServiceImpl')
58
+ protected readonly logger: ILogger;
59
+
60
+ @postConstruct()
61
+ protected init(): void {
62
+ this.preferences.onPreferenceChanged(change => {
63
+ if (change.preferenceName === TELEMETRY_FILTERS) {
64
+ this.filters = undefined;
65
+ }
66
+ });
67
+ this.preferences.ready.then(
68
+ () => {
69
+ this.readinessState = 'ready';
70
+ const pendingEvents = this.pendingEvents;
71
+ this.pendingEvents = [];
72
+ this.pendingQueueOverflowWarned = false;
73
+ pendingEvents.forEach(event => this.doDispatch(event));
74
+ },
75
+ () => {
76
+ this.readinessState = 'failed';
77
+ const pendingEvents = this.pendingEvents;
78
+ this.pendingEvents = [];
79
+ this.pendingQueueOverflowWarned = false;
80
+ this.logger.error('Telemetry preferences failed to become ready; remote telemetry is disabled.');
81
+ pendingEvents.forEach(event => this.doDispatch(event));
82
+ }
83
+ );
84
+ }
85
+
86
+ report<T extends object>(topic: string, data?: TelemetryData<T>, options?: TelemetryReportOptions): void {
87
+ this.dispatch(createTelemetryEvent(topic, BACKEND_TELEMETRY_SESSION, data, options));
88
+ }
89
+
90
+ notifyEvent(event: unknown): void {
91
+ this.dispatch(event, true);
92
+ }
93
+
94
+ async getLocalSinkInterests(): Promise<string[]> {
95
+ return [...new Set(this.getSinks()
96
+ .filter(sink => sink.scope === 'local')
97
+ .flatMap(sink => sink.interests))];
98
+ }
99
+
100
+ async onStop(): Promise<void> {
101
+ await Promise.all(this.getSinks().map(async validatedSink => {
102
+ try {
103
+ await validatedSink.sink.flush?.();
104
+ } catch {
105
+ this.logger.error(`Telemetry sink '${validatedSink.id}' failed while flushing.`);
106
+ }
107
+ }));
108
+ }
109
+
110
+ protected dispatch(event: unknown, rpcOrigin = false): void {
111
+ if (!isValidTelemetryEvent(event)) {
112
+ this.logger.warn(`Ignoring malformed telemetry event for topic '${describeTelemetryEventTopic(event)}'.`);
113
+ return;
114
+ }
115
+ const snapshot = snapshotTelemetryEvent(rpcOrigin ? { ...event, session: `frontend/${event.session}` } : event);
116
+ if (this.readinessState === 'ready' || this.readinessState === 'failed') {
117
+ this.doDispatch(snapshot);
118
+ } else if (this.readinessState === 'pending') {
119
+ if (this.pendingEvents.length >= MAX_PENDING_EVENTS) {
120
+ this.pendingEvents.shift();
121
+ if (!this.pendingQueueOverflowWarned) {
122
+ this.pendingQueueOverflowWarned = true;
123
+ this.logger.warn('Telemetry pending event queue is full; dropping oldest event.');
124
+ }
125
+ }
126
+ this.pendingEvents.push(snapshot);
127
+ }
128
+ }
129
+
130
+ protected doDispatch(event: TelemetryEvent): void {
131
+ const filters = this.getFilters();
132
+ for (const validatedSink of this.getSinks()) {
133
+ if (this.readinessState === 'failed' && validatedSink.scope === 'remote') {
134
+ continue;
135
+ }
136
+ const filterPatterns = filters.get(validatedSink.id);
137
+ if ((filterPatterns && !filterPatterns.some(pattern => matchesTelemetryTopic(pattern, event.topic)))
138
+ || !validatedSink.interests.some(pattern => matchesTelemetryTopic(pattern, event.topic))
139
+ || (validatedSink.scope === 'remote' && !isKindAllowedByLevel(this.consentProvider.level, event.kind))) {
140
+ continue;
141
+ }
142
+ try {
143
+ Promise.resolve(validatedSink.sink.handle(event)).catch(() => this.logSinkFailure(validatedSink.id, event.topic));
144
+ } catch {
145
+ this.logSinkFailure(validatedSink.id, event.topic);
146
+ }
147
+ }
148
+ }
149
+
150
+ protected getSinks(): readonly ValidatedTelemetrySink[] {
151
+ if (this.sinks) {
152
+ return this.sinks;
153
+ }
154
+ const contributions = this.sinkProvider.getContributions();
155
+ const idCounts = new Map<string, number>();
156
+ for (const sink of contributions) {
157
+ if (isValidTelemetrySinkId(sink.id)) {
158
+ idCounts.set(sink.id, (idCounts.get(sink.id) ?? 0) + 1);
159
+ }
160
+ }
161
+ this.sinks = contributions.flatMap(sink => {
162
+ const id = sink.id;
163
+ const interests = sink.interests;
164
+ if (!isValidTelemetrySinkId(id)) {
165
+ this.logger.error(`Ignoring telemetry sink with invalid ID '${String(id)}'.`);
166
+ return [];
167
+ }
168
+ if (idCounts.get(id) !== 1) {
169
+ this.logger.error(`Ignoring duplicate telemetry sink ID '${id}'.`);
170
+ return [];
171
+ }
172
+ if (!Array.isArray(interests) || interests.length === 0
173
+ || !interests.every(interest => isValidTelemetryTopicPattern(interest))) {
174
+ this.logger.error(`Ignoring telemetry sink '${id}' with invalid interests.`);
175
+ return [];
176
+ }
177
+ const scope = sink.scope ?? 'remote';
178
+ if (scope !== 'local' && scope !== 'remote') {
179
+ this.logger.error(`Ignoring telemetry sink '${id}' with invalid scope.`);
180
+ return [];
181
+ }
182
+ return [{ sink, id, interests: Object.freeze([...interests]), scope }];
183
+ });
184
+ return this.sinks;
185
+ }
186
+
187
+ protected getFilters(): ReadonlyMap<string, readonly string[]> {
188
+ if (this.filters) {
189
+ return this.filters;
190
+ }
191
+ const filters = new Map<string, readonly string[]>();
192
+ for (const [sinkId, patterns] of Object.entries(this.preferences[TELEMETRY_FILTERS] ?? {})) {
193
+ if (!Array.isArray(patterns)) {
194
+ this.logger.warn(`Ignoring invalid telemetry filters for sink '${sinkId}'.`);
195
+ filters.set(sinkId, Object.freeze([]));
196
+ continue;
197
+ }
198
+ const validPatterns = patterns.filter(pattern => {
199
+ if (isValidTelemetryTopicPattern(pattern)) {
200
+ return true;
201
+ }
202
+ this.logger.warn(`Ignoring invalid telemetry filter pattern for sink '${sinkId}'.`);
203
+ return false;
204
+ });
205
+ filters.set(sinkId, Object.freeze(validPatterns));
206
+ }
207
+ this.filters = filters;
208
+ return filters;
209
+ }
210
+
211
+ protected logSinkFailure(sinkId: string, topic: string): void {
212
+ this.logger.error(`Telemetry sink '${sinkId}' failed while handling topic '${topic}'.`);
213
+ }
214
+ }
@@ -0,0 +1,48 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 STMicroelectronics and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { MaybePromise } from '@theia/core/lib/common';
18
+ import { TelemetryEvent } from '../common/telemetry-protocol';
19
+
20
+ /** @experimental */
21
+ export const TelemetrySink = Symbol('TelemetrySink');
22
+
23
+ /** @experimental */
24
+ export interface TelemetrySink {
25
+ /**
26
+ * A unique sink ID containing at least two slash-separated segments.
27
+ * @experimental
28
+ */
29
+ readonly id: string;
30
+ /**
31
+ * A non-empty list of valid telemetry topic patterns.
32
+ * @experimental
33
+ */
34
+ readonly interests: readonly string[];
35
+ /**
36
+ * Defaults to `remote`.
37
+ * @experimental
38
+ */
39
+ readonly scope?: 'local' | 'remote';
40
+ /**
41
+ * Must not synchronously call `TelemetryService.report`, which can cause recursive dispatch or a sink cycle.
42
+ * Defer any follow-up telemetry until after the current handler call.
43
+ * @experimental
44
+ */
45
+ handle(event: TelemetryEvent): MaybePromise<void>;
46
+ /** @experimental */
47
+ flush?(): MaybePromise<void>;
48
+ }