Browse Source

Adds usage telemetry

main
Eric Amodio 2 years ago
parent
commit
141200afe3
14 changed files with 564 additions and 46 deletions
  1. +12
    -1
      package.json
  2. +3
    -0
      src/config.ts
  3. +13
    -1
      src/container.ts
  4. +21
    -0
      src/extension.ts
  5. +37
    -6
      src/git/gitProviderService.ts
  6. +3
    -0
      src/git/remotes/remoteProviderConnections.ts
  7. +52
    -4
      src/plus/subscription/subscriptionService.ts
  8. +11
    -2
      src/system/command.ts
  9. +42
    -28
      src/system/object.ts
  10. +91
    -0
      src/telemetry/openTelemetryProvider.ts
  11. +191
    -0
      src/telemetry/telemetry.ts
  12. +7
    -2
      src/usageTracker.ts
  13. +4
    -1
      webpack.config.js
  14. +77
    -1
      yarn.lock

+ 12
- 1
package.json View File

@ -3310,6 +3310,13 @@
"scope": "resource",
"order": 0
},
"gitlens.telemetry.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to allow the collection of product usage telemetry",
"scope": "window",
"order": 1
},
"gitlens.advanced.messages": {
"type": "object",
"default": {
@ -3403,7 +3410,7 @@
"additionalProperties": false,
"markdownDescription": "Specifies which messages should be suppressed",
"scope": "window",
"order": 1
"order": 5
},
"gitlens.advanced.repositorySearchDepth": {
"type": "number",
@ -12660,6 +12667,9 @@
"@microsoft/fast-element": "1.11.0",
"@microsoft/fast-react-wrapper": "0.3.15",
"@octokit/core": "4.1.0",
"@opentelemetry/api": "1.3.0",
"@opentelemetry/exporter-trace-otlp-http": "0.34.0",
"@opentelemetry/sdk-trace-base": "1.8.0",
"@vscode/codicons": "0.0.32",
"@vscode/webview-ui-toolkit": "1.1.0",
"ansi-regex": "6.0.1",
@ -12671,6 +12681,7 @@
"lodash-es": "4.17.21",
"md5.js": "1.3.5",
"node-fetch": "2.6.7",
"os-browserify": "0.3.0",
"path-browserify": "1.0.1",
"react": "16.8.4",
"react-dom": "16.8.4",

+ 3
- 0
src/config.ts View File

@ -154,6 +154,9 @@ export interface Config {
};
};
};
telemetry: {
enabled: boolean;
};
terminal: {
overrideGitEditor: boolean;
};

+ 13
- 1
src/container.ts View File

@ -30,6 +30,7 @@ import type { Storage } from './storage';
import { executeCommand } from './system/command';
import { log } from './system/decorators/log';
import { memoize } from './system/decorators/memoize';
import { TelemetryService } from './telemetry/telemetry';
import { GitTerminalLinkProvider } from './terminal/linkProvider';
import { GitDocumentTracker } from './trackers/gitDocumentTracker';
import { GitLineTracker } from './trackers/gitLineTracker';
@ -159,7 +160,8 @@ export class Container {
this.ensureModeApplied();
context.subscriptions.push((this._storage = storage));
context.subscriptions.push((this._usage = new UsageTracker(storage)));
context.subscriptions.push((this._telemetry = new TelemetryService(this)));
context.subscriptions.push((this._usage = new UsageTracker(this, storage)));
context.subscriptions.push(configuration.onWillChange(this.onConfigurationChanging, this));
@ -409,6 +411,11 @@ export class Container {
return this._homeView;
}
@memoize()
get id() {
return this._context.extension.id;
}
private _integrationAuthentication: IntegrationAuthenticationService | undefined;
get integrationAuthentication() {
if (this._integrationAuthentication == null) {
@ -546,6 +553,11 @@ export class Container {
return this._tagsView;
}
private readonly _telemetry: TelemetryService;
get telemetry(): TelemetryService {
return this._telemetry;
}
private _timelineView: TimelineWebviewView;
get timelineView() {
return this._timelineView;

+ 21
- 0
src/extension.ts View File

@ -23,6 +23,7 @@ import { Storage, SyncedStorageKeys } from './storage';
import { executeCommand, executeCoreCommand, registerCommands } from './system/command';
import { setDefaultDateLocales } from './system/date';
import { once } from './system/event';
import { flatten } from './system/object';
import { Stopwatch } from './system/stopwatch';
import { compare, fromString, satisfies } from './system/version';
import { isViewNode } from './views/nodes/viewNode';
@ -176,6 +177,26 @@ export async function activate(context: ExtensionContext): Promise
}
const mode = container.mode;
const data = {
'activation.elapsed': sw.elapsed(),
'activation.previousVersion': previousVersion,
'workspace.isTrusted': workspace.isTrusted,
};
queueMicrotask(() => {
container.telemetry.setGlobalAttributes({
debugging: container.debugging,
insiders: insiders,
prerelease: prerelease,
});
container.telemetry.sendEvent('activate', data);
setTimeout(() => {
const data = flatten(configuration.getAll(true), { prefix: 'config', skipNulls: true, stringify: true });
// TODO@eamodio do we want to capture any vscode settings that are relevant to GitLens?
container.telemetry.sendEvent('config', data);
}, 5000);
});
sw.stop({
message: ` activated${exitMessage != null ? `, ${exitMessage}` : ''}${
mode != null ? `, mode: ${mode.name}` : ''

+ 37
- 6
src/git/gitProviderService.ts View File

@ -28,7 +28,7 @@ import { isSubscriptionPaidPlan, SubscriptionPlanId } from '../subscription';
import { groupByFilterMap, groupByMap } from '../system/array';
import { gate } from '../system/decorators/gate';
import { debug, getLogScope, log } from '../system/decorators/log';
import { count, filter, first, flatMap, map, some } from '../system/iterable';
import { count, filter, first, flatMap, join, map, some } from '../system/iterable';
import { getBestPath, getScheme, isAbsolute, maybeUri, normalizePath } from '../system/path';
import { cancellable, fastestSettled, getSettledValue, isPromise, PromiseCancelledError } from '../system/promise';
import { VisitedPathsTrie } from '../system/trie';
@ -115,6 +115,14 @@ export class GitProviderService implements Disposable {
this._etag = Date.now();
this._onDidChangeProviders.fire({ added: added ?? [], removed: removed ?? [], etag: this._etag });
this.container.telemetry.sendEvent('providers/changed', {
'providers.count': this._providers.size,
'providers.ids': join(this._providers.keys(), ','),
'providers.added': added?.length ?? 0,
'providers.removed': removed?.length ?? 0,
'repositories.count': this.openRepositoryCount,
});
}
private _onDidChangeRepositories = new EventEmitter<RepositoriesChangeEvent>();
@ -130,6 +138,14 @@ export class GitProviderService implements Disposable {
this._visibilityCache.clear();
}
this._onDidChangeRepositories.fire({ added: added ?? [], removed: removed ?? [], etag: this._etag });
this.container.telemetry.sendEvent('repositories/changed', {
'repositories.count': this.openRepositoryCount,
'repositories.added': added?.length ?? 0,
'repositories.removed': removed?.length ?? 0,
'providers.count': this._providers.size,
'providers.ids': join(this._providers.keys(), ','),
});
}
private readonly _onDidChangeRepository = new EventEmitter<RepositoryChangeEvent>();
@ -423,12 +439,17 @@ export class GitProviderService implements Disposable {
this.updateContext();
}
const autoRepositoryDetection = configuration.getAny<boolean | 'subFolders' | 'openEditors'>(
CoreGitConfiguration.AutoRepositoryDetection,
);
this.container.telemetry.sendEvent('providers/registrationComplete', {
'folders.count': workspaceFolders?.length ?? 0,
'folders.schemes': workspaceFolders?.map(f => f.uri.scheme).join(', '),
'config.git.autoRepositoryDetection': autoRepositoryDetection,
});
if (scope != null) {
scope.exitDetails = ` ${GlyphChars.Dot} workspaceFolders=${
workspaceFolders?.length
}, git.autoRepositoryDetection=${configuration.getAny<boolean | 'subFolders' | 'openEditors'>(
CoreGitConfiguration.AutoRepositoryDetection,
)}`;
scope.exitDetails = ` ${GlyphChars.Dot} workspaceFolders=${workspaceFolders?.length}, git.autoRepositoryDetection=${autoRepositoryDetection}`;
}
}
@ -651,6 +672,16 @@ export class GitProviderService implements Disposable {
let visibility = this._visibilityCache.get(undefined);
if (visibility == null) {
visibility = this.visibilityCore();
void visibility.then(v =>
queueMicrotask(() => {
this.container.telemetry.sendEvent('repositories/visibility', {
'repositories.count': this.openRepositoryCount,
'repositories.visibility': v,
'providers.count': this._providers.size,
'providers.ids': join(this._providers.keys(), ','),
});
}),
);
this._visibilityCache.set(undefined, visibility);
}
return visibility;

+ 3
- 0
src/git/remotes/remoteProviderConnections.ts View File

@ -1,5 +1,6 @@
import { EventEmitter } from 'vscode';
import type { Event } from 'vscode';
import { Container } from '../../container';
export interface ConnectionStateChangeEvent {
key: string;
@ -16,6 +17,7 @@ export namespace RichRemoteProviders {
if (_connectedCache.has(key)) return;
_connectedCache.add(key);
Container.instance.telemetry.sendEvent('remoteProviders/connected', { 'remoteProviders.key': key });
_onDidChangeConnectionState.fire({ key: key, reason: 'connected' });
}
@ -24,6 +26,7 @@ export namespace RichRemoteProviders {
// Probably shouldn't bother to fire the event if we don't already think we are connected, but better to be safe
// if (!_connectedCache.has(key)) return;
_connectedCache.delete(key);
Container.instance.telemetry.sendEvent('remoteProviders/disconnected', { 'remoteProviders.key': key });
_onDidChangeConnectionState.fire({ key: key, reason: 'disconnected' });
}

+ 52
- 4
src/plus/subscription/subscriptionService.ts View File

@ -48,6 +48,7 @@ import { gate } from '../../system/decorators/gate';
import { debug, getLogScope, log } from '../../system/decorators/log';
import { memoize } from '../../system/decorators/memoize';
import { once } from '../../system/function';
import { flatten } from '../../system/object';
import { pluralize } from '../../system/string';
import { openWalkthrough } from '../../system/utils';
import { satisfies } from '../../system/version';
@ -552,6 +553,8 @@ export class SubscriptionService implements Disposable {
id: session.account.id,
platform: getPlatform(),
gitlensVersion: this.container.version,
machineId: env.machineId,
sessionId: env.sessionId,
vscodeEdition: env.appName,
vscodeHost: env.appHost,
vscodeVersion: codeVersion,
@ -841,12 +844,57 @@ export class SubscriptionService implements Disposable {
subscription.state = computeSubscriptionState(subscription);
assertSubscriptionState(subscription);
const previous = this._subscription; // Can be undefined here, since we call this in the constructor
const previous = this._subscription as typeof this._subscription | undefined; // Can be undefined here, since we call this in the constructor
// Check the previous and new subscriptions are exactly the same
const matches = previous != null && JSON.stringify(previous) === JSON.stringify(subscription);
queueMicrotask(() => {
this.container.telemetry.setGlobalAttributes({
'account.id': subscription!.account?.id,
'account.verified': subscription!.account?.verified,
'subscription.actual.id': subscription!.plan.actual.id,
'subscription.actual.startedOn': subscription!.plan.actual.startedOn,
'subscription.actual.expiresOn': subscription!.plan.actual.expiresOn,
'subscription.effective.id': subscription!.plan.effective.id,
'subscription.effective.startedOn': subscription!.plan.effective.startedOn,
'subscription.effective.expiresOn': subscription!.plan.effective.expiresOn,
'subscription.state': subscription!.state,
});
const data = {
'account.id': subscription!.account?.id,
'account.verified': subscription!.account?.verified,
...flatten(subscription!.plan, { prefix: 'subscription', skipNulls: true, stringify: true }),
...flatten(subscription!.previewTrial, {
prefix: 'subscription.previewTrial',
skipNulls: true,
stringify: true,
}),
'subscription.state': subscription!.state,
...(!matches && previous != null
? {
'previous.account.id': previous.account?.id,
'previous.account.verified': previous.account?.verified,
...flatten(previous.plan, {
prefix: 'previous.subscription',
skipNulls: true,
stringify: true,
}),
...flatten(previous.previewTrial, {
prefix: 'previous.subscription.previewTrial',
skipNulls: true,
stringify: true,
}),
'previous.subscription.state': previous.state,
}
: {}),
};
this.container.telemetry.sendEvent(previous == null ? 'subscription' : 'subscription/changed', data);
});
// If the previous and new subscriptions are exactly the same, kick out
if (previous != null && JSON.stringify(previous) === JSON.stringify(subscription)) {
return;
}
if (matches) return;
void this.storeSubscription(subscription);

+ 11
- 2
src/system/command.ts View File

@ -4,7 +4,7 @@ import type { Action, ActionContext } from '../api/gitlens';
import type { Command } from '../commands/base';
import type { CoreCommands, CoreGitCommands } from '../constants';
import { Commands } from '../constants';
import type { Container } from '../container';
import { Container } from '../container';
interface CommandConstructor {
new (container: Container): Command;
@ -18,7 +18,14 @@ export function command(): ClassDecorator {
}
export function registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable {
return commands.registerCommand(command, callback, thisArg);
return commands.registerCommand(
command,
function (this: any, ...args) {
Container.instance.telemetry.sendEvent('command', { command: command });
callback.call(this, ...args);
},
thisArg,
);
}
export function registerCommands(container: Container): Disposable[] {
@ -59,6 +66,7 @@ export function executeCoreCommand(
command: CoreCommands,
...args: T
): Thenable<U> {
Container.instance.telemetry.sendEvent('command', { command: command });
return commands.executeCommand<U>(command, ...args);
}
@ -72,6 +80,7 @@ export function executeCoreGitCommand(
command: CoreGitCommands,
...args: T
): Thenable<U> {
Container.instance.telemetry.sendEvent('command', { command: command });
return commands.executeCommand<U>(command, ...args);
}

+ 42
- 28
src/system/object.ts View File

@ -1,43 +1,57 @@
// eslint-disable-next-line no-restricted-imports
export { isEqual as areEqual } from 'lodash-es';
export function flatten(o: any, prefix: string = '', stringify: boolean = false): Record<string, any> {
const flattened = Object.create(null) as Record<string, any>;
_flatten(flattened, prefix, o, stringify);
return flattened;
}
export function flatten(o: any, options: { prefix?: string; skipNulls: true; stringify: true }): Record<string, string>;
export function flatten(
o: any,
options: { prefix?: string; skipNulls: true; stringify?: false },
): Record<string, NonNullable<any>>;
export function flatten(
o: any,
options: { prefix?: string; skipNulls?: false; stringify: true },
): Record<string, string | null>;
export function flatten(
o: any,
options?: { prefix?: string; skipNulls?: boolean; stringify?: boolean },
): Record<string, any>;
export function flatten(
o: any,
options?: { prefix?: string; skipNulls?: boolean; stringify?: boolean },
): Record<string, any> {
const skipNulls = options?.skipNulls ?? false;
const stringify = options?.stringify ?? false;
function _flatten(flattened: Record<string, any>, key: string, value: any, stringify: boolean = false) {
if (Object(value) !== value) {
if (stringify) {
function flattenCore(flattened: Record<string, any>, key: string, value: any) {
if (Object(value) !== value) {
if (value == null) {
flattened[key] = null;
if (skipNulls) return;
flattened[key] = stringify ? value ?? null : value;
} else if (typeof value === 'string') {
flattened[key] = value;
} else {
flattened[key] = JSON.stringify(value);
flattened[key] = stringify ? JSON.stringify(value) : value;
}
} else if (Array.isArray(value)) {
const len = value.length;
if (len === 0) return;
for (let i = 0; i < len; i++) {
flattenCore(flattened, `${key}[${i}]`, value[i]);
}
} else {
flattened[key] = value;
}
} else if (Array.isArray(value)) {
const len = value.length;
for (let i = 0; i < len; i++) {
_flatten(flattened, `${key}[${i}]`, value[i], stringify);
}
if (len === 0) {
flattened[key] = null;
}
} else {
let isEmpty = true;
for (const p in value) {
isEmpty = false;
_flatten(flattened, key ? `${key}.${p}` : p, value[p], stringify);
}
if (isEmpty && key) {
flattened[key] = null;
const entries = Object.entries(value);
if (entries.length === 0) return;
for (const [k, v] of entries) {
flattenCore(flattened, key ? `${key}.${k}` : k, v);
}
}
}
const flattened: Record<string, any> = Object.create(null);
flattenCore(flattened, options?.prefix ?? '', o);
return flattened;
}
export function paths(o: Record<string, any>, path?: string): string[] {

+ 91
- 0
src/telemetry/openTelemetryProvider.ts View File

@ -0,0 +1,91 @@
import type { AttributeValue, Span, Tracer } from '@opentelemetry/api';
import { diag, DiagConsoleLogger, trace } from '@opentelemetry/api';
import { DiagLogLevel } from '@opentelemetry/api/build/src/diag/types';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import {
BasicTracerProvider,
BatchSpanProcessor,
ConsoleSpanExporter,
SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import type { HttpsProxyAgent } from 'https-proxy-agent';
import type { TelemetryContext, TelemetryProvider } from './telemetry';
export class OpenTelemetryProvider implements TelemetryProvider {
private _globalAttributes: Record<string, AttributeValue> = {};
private readonly tracer: Tracer;
constructor(context: TelemetryContext, agent?: HttpsProxyAgent, debugging?: boolean) {
const provider = new BasicTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'gitlens',
[SemanticResourceAttributes.SERVICE_VERSION]: context.extensionVersion,
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: context.env,
[SemanticResourceAttributes.DEVICE_ID]: context.machineId,
[SemanticResourceAttributes.OS_TYPE]: context.platform,
'extension.id': context.extensionId,
'session.id': context.sessionId,
language: context.language,
'vscode.edition': context.vscodeEdition,
'vscode.version': context.vscodeVersion,
'vscode.host': context.vscodeHost,
}) as any,
});
if (debugging) {
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.VERBOSE);
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
}
const exporter = new OTLPTraceExporter({
url: 'https://otel.gitkraken.com:4318/v1/traces',
compression: 'gzip' as any,
httpAgentOptions: agent?.options,
});
provider.addSpanProcessor(debugging ? new SimpleSpanProcessor(exporter) : new BatchSpanProcessor(exporter));
provider.register();
this.tracer = trace.getTracer(context.extensionId);
}
dispose(): void {
trace.disable();
}
sendEvent(name: string, data?: Record<string, AttributeValue>): void {
const span = this.tracer.startSpan(name, { startTime: Date.now() });
span.setAttributes(this._globalAttributes);
if (data != null) {
span.setAttributes(data);
}
span.end();
}
startEvent(name: string, data?: Record<string, AttributeValue>): Span {
const span = this.tracer.startSpan(name, { startTime: Date.now() });
span.setAttributes(this._globalAttributes);
if (data != null) {
span.setAttributes(data);
}
return span;
}
// sendErrorEvent(
// name: string,
// data?: Record<string, string>,
// ): void {
// }
// sendException(
// error: Error | unknown,
// data?: Record<string, string>,
// ): void {
// }
setGlobalAttributes(attributes: Map<string, AttributeValue>): void {
this._globalAttributes = Object.fromEntries(attributes);
}
}

+ 191
- 0
src/telemetry/telemetry.ts View File

@ -0,0 +1,191 @@
import type { AttributeValue, Span } from '@opentelemetry/api';
import type { Disposable } from 'vscode';
import { version as codeVersion, env } from 'vscode';
import { getProxyAgent } from '@env/fetch';
import { getPlatform } from '@env/platform';
import { configuration } from '../configuration';
import type { Container } from '../container';
export interface TelemetryContext {
env: string;
extensionId: string;
extensionVersion: string;
machineId: string;
sessionId: string;
language: string;
platform: string;
vscodeEdition: string;
vscodeHost: string;
vscodeVersion: string;
}
export interface TelemetryProvider extends Disposable {
sendEvent(name: string, data?: Record<string, AttributeValue>): void;
startEvent(name: string, data?: Record<string, AttributeValue>): Span;
setGlobalAttributes(attributes: Map<string, AttributeValue>): void;
}
interface QueuedEvent {
type: 'sendEvent';
name: string;
data?: Record<string, AttributeValue>;
}
export class TelemetryService implements Disposable {
private provider: TelemetryProvider | undefined;
private enabled: boolean = false;
private globalAttributes = new Map<string, AttributeValue>();
private eventQueue: QueuedEvent[] = [];
constructor(private readonly container: Container) {
container.context.subscriptions.push(
configuration.onDidChange(e => {
if (!e.affectsConfiguration('telemetry.enabled')) return;
this.ensureTelemetry(container);
}),
env.onDidChangeTelemetryEnabled(() => this.ensureTelemetry(container)),
);
this.ensureTelemetry(container);
}
dispose(): void {
this.provider?.dispose();
this.provider = undefined;
}
private _initializationTimer: ReturnType<typeof setTimeout> | undefined;
private ensureTelemetry(container: Container): void {
this.enabled = env.isTelemetryEnabled && configuration.get('telemetry.enabled', undefined, true);
if (!this.enabled) {
if (this._initializationTimer != null) {
clearTimeout(this._initializationTimer);
this._initializationTimer = undefined;
}
this.eventQueue.length = 0;
this.provider?.dispose();
this.provider = undefined;
return;
}
if (this._initializationTimer != null) return;
this._initializationTimer = setTimeout(() => this.initializeTelemetry(container), 7500);
}
private async initializeTelemetry(container: Container) {
if (this._initializationTimer != null) {
clearTimeout(this._initializationTimer);
this._initializationTimer = undefined;
}
this.provider = new (
await import(/* webpackChunkName: "telemetry" */ './openTelemetryProvider')
).OpenTelemetryProvider(
{
env: container.env,
extensionId: container.id,
extensionVersion: container.version,
machineId: env.machineId,
sessionId: env.sessionId,
language: env.language,
platform: getPlatform(),
vscodeEdition: env.appName,
vscodeHost: env.appHost,
vscodeVersion: codeVersion,
},
getProxyAgent(),
container.debugging,
);
this.provider.setGlobalAttributes(this.globalAttributes);
if (this.eventQueue.length) {
const queue = [...this.eventQueue];
this.eventQueue.length = 0;
for (const { type, name, data } of queue) {
if (type === 'sendEvent') {
this.provider.sendEvent(name, data);
}
}
}
}
sendEvent(name: string, data?: Record<string, AttributeValue | null | undefined>): void {
if (!this.enabled) return;
const attributes = stripNullOrUndefinedAttributes(data);
if (this.provider == null) {
this.eventQueue.push({ type: 'sendEvent', name: name, data: attributes });
return;
}
this.provider.sendEvent(name, attributes);
}
async startEvent(
name: string,
data?: Record<string, AttributeValue | null | undefined>,
): Promise<Span | undefined> {
if (!this.enabled) return;
if (this.provider == null) {
await this.initializeTelemetry(this.container);
}
const attributes = stripNullOrUndefinedAttributes(data);
return this.provider!.startEvent(name, attributes);
}
// sendErrorEvent(
// name: string,
// data?: Record<string, string>,
// ): void {
// }
// sendException(
// error: Error | unknown,
// data?: Record<string, string>,
// ): void {
// }
setGlobalAttribute(key: string, value: AttributeValue | null | undefined): void {
if (value == null) {
this.globalAttributes.delete(key);
} else {
this.globalAttributes.set(key, value);
}
this.provider?.setGlobalAttributes(this.globalAttributes);
}
setGlobalAttributes(attributes: Record<string, AttributeValue | null | undefined>): void {
for (const [key, value] of Object.entries(attributes)) {
if (value == null) {
this.globalAttributes.delete(key);
} else {
this.globalAttributes.set(key, value);
}
}
this.provider?.setGlobalAttributes(this.globalAttributes);
}
deleteGlobalAttribute(key: string): void {
this.globalAttributes.delete(key);
this.provider?.setGlobalAttributes(this.globalAttributes);
}
}
function stripNullOrUndefinedAttributes(data: Record<string, AttributeValue | null | undefined> | undefined) {
let attributes: Record<string, AttributeValue> | undefined;
if (data != null) {
attributes = Object.create(null);
for (const [key, value] of Object.entries(data)) {
if (value == null) continue;
attributes![key] = value;
}
}
return attributes;
}

+ 7
- 2
src/usageTracker.ts View File

@ -1,5 +1,6 @@
import type { Disposable, Event } from 'vscode';
import { EventEmitter } from 'vscode';
import type { Container } from './container';
import type { Storage } from './storage';
import { updateRecordValue } from './system/object';
@ -44,7 +45,7 @@ export class UsageTracker implements Disposable {
return this._onDidChange.event;
}
constructor(private readonly storage: Storage) {}
constructor(private readonly container: Container, private readonly storage: Storage) {}
dispose(): void {}
@ -86,10 +87,14 @@ export class UsageTracker implements Disposable {
};
usages[key] = usage;
} else {
usage.count++;
if (usage.count !== Number.MAX_SAFE_INTEGER) {
usage.count++;
}
usage.lastUsedAt = usedAt;
}
this.container.telemetry.sendEvent('usage/track', { 'usage.key': key, 'usage.count': usage.count });
await this.storage.store('usages', usages);
this._onDidChange.fire({ key: key, usage: usage });

+ 4
- 1
webpack.config.js View File

@ -232,7 +232,10 @@ function getExtensionConfig(target, mode, env) {
// This dependency is very large, and isn't needed for our use-case
tr46: path.resolve(__dirname, 'patches', 'tr46.js'),
},
fallback: target === 'webworker' ? { path: require.resolve('path-browserify') } : undefined,
fallback:
target === 'webworker'
? { path: require.resolve('path-browserify'), os: require.resolve('os-browserify/browser') }
: undefined,
mainFields: target === 'webworker' ? ['browser', 'module', 'main'] : ['module', 'main'],
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
},

+ 77
- 1
yarn.lock View File

@ -345,6 +345,77 @@
dependencies:
"@octokit/openapi-types" "^14.0.0"
"@opentelemetry/api@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.3.0.tgz#27c6f776ac3c1c616651e506a89f438a0ed6a055"
integrity sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ==
"@opentelemetry/core@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.8.0.tgz#cca18594dd48ded6dc0d08c7e789c79af0315934"
integrity sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==
dependencies:
"@opentelemetry/semantic-conventions" "1.8.0"
"@opentelemetry/exporter-trace-otlp-http@0.34.0":
version "0.34.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.34.0.tgz#baca4cebb1666ed801288e24215d96a65f2e8ae5"
integrity sha512-MBtUwMvgpdoRo9iqK2eDJ8SP2xKYWeBCSu99s4cc1kg4HKKOpenXLE/6daGsSZ+QTPwd8j+9xMSd+hhBg+Bvzw==
dependencies:
"@opentelemetry/core" "1.8.0"
"@opentelemetry/otlp-exporter-base" "0.34.0"
"@opentelemetry/otlp-transformer" "0.34.0"
"@opentelemetry/resources" "1.8.0"
"@opentelemetry/sdk-trace-base" "1.8.0"
"@opentelemetry/otlp-exporter-base@0.34.0":
version "0.34.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.34.0.tgz#c6020b63590d4b8ac3833eda345a6f582fa014b1"
integrity sha512-xVNvQm7oXeQogeI21iTZRnBrBYS0OVekPutEJgb7jQtHg7x2GWuCBQK9sDo84FRWNXBpNOgSYqsf8/+PxIJ2vA==
dependencies:
"@opentelemetry/core" "1.8.0"
"@opentelemetry/otlp-transformer@0.34.0":
version "0.34.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.34.0.tgz#71023706233c7bc6c3cdcf954c749fea9338084c"
integrity sha512-NghPJvn3pVoWBuhWyBe1n/nWIrj1D1EFUH/bIkWEp0CMVWFLux6R+BkRPZQo5klTcj8xFhCZZIZsL/ubkYPryg==
dependencies:
"@opentelemetry/core" "1.8.0"
"@opentelemetry/resources" "1.8.0"
"@opentelemetry/sdk-metrics" "1.8.0"
"@opentelemetry/sdk-trace-base" "1.8.0"
"@opentelemetry/resources@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.8.0.tgz#260be9742cf7bceccc0db928d8ca8d64391acfe3"
integrity sha512-KSyMH6Jvss/PFDy16z5qkCK0ERlpyqixb1xwb73wLMvVq+j7i89lobDjw3JkpCcd1Ws0J6jAI4fw28Zufj2ssg==
dependencies:
"@opentelemetry/core" "1.8.0"
"@opentelemetry/semantic-conventions" "1.8.0"
"@opentelemetry/sdk-metrics@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.8.0.tgz#d061060f03861ab3f345d0f924922bc1a6396157"
integrity sha512-+KYb+uj0vHhl8xzJO+oChS4oP1e+/2Wl3SXoHoIdcEjd1TQfDV+lxOm4oqxWq6wykXvI35/JHyejxSoT+qxGmg==
dependencies:
"@opentelemetry/core" "1.8.0"
"@opentelemetry/resources" "1.8.0"
lodash.merge "4.6.2"
"@opentelemetry/sdk-trace-base@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.8.0.tgz#70713aab90978a16dea188c8335209f857be7384"
integrity sha512-iH41m0UTddnCKJzZx3M85vlhKzRcmT48pUeBbnzsGrq4nIay1oWVHKM5nhB5r8qRDGvd/n7f/YLCXClxwM0tvA==
dependencies:
"@opentelemetry/core" "1.8.0"
"@opentelemetry/resources" "1.8.0"
"@opentelemetry/semantic-conventions" "1.8.0"
"@opentelemetry/semantic-conventions@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz#fe2aa90e6df050a11cd57f5c0f47b0641fd2cad3"
integrity sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==
"@pkgr/utils@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03"
@ -4289,7 +4360,7 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
lodash.merge@^4.6.2:
lodash.merge@4.6.2, lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
@ -4951,6 +5022,11 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
os-browserify@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"

Loading…
Cancel
Save