Skip to content

Commit

Permalink
feat: add telemetry service
Browse files Browse the repository at this point in the history
@W-16507728@
  • Loading branch information
peternhale committed Aug 22, 2024
1 parent 7ed7260 commit 9ce5d4d
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 265 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@types/istanbul-lib-report": "^3.0.3",
"@types/jest": "^29.5.12",
"@types/node": "^20.15.0",
"@types/vscode": "^1.86.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"commitlint": "^19.4.0",
Expand All @@ -36,7 +37,6 @@
"ts-jest": "^29.2.4",
"ts-node": "^10.7.0",
"typescript": "^5.5.4",
"vscode": "^1.1.37",
"vscode-test": "^1.6.1"
},
"files": [
Expand Down Expand Up @@ -67,4 +67,4 @@
"engines": {
"node": ">=18.18.2"
}
}
}
37 changes: 37 additions & 0 deletions src/services/serviceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,37 @@ export class ServiceProvider {
return serviceInstance;
}

/**
* Sets a service instance of the specified type and instance name.
* If a service instance with the same type and instance name already exists, an error is thrown.
* This function should be used by provider to establish a service with a given name without having
* to go through the getService path. An example of this would be a logging service that needs to
* configure the logger before consumers request an instance of the service
*
* @param type - The type of the service.
* @param instanceName - The name of the service instance.
* @param serviceInstance - The instance of the service to be set.
* @throws {Error} If a service instance with the same type and instance name already exists.
*/
static setService<T extends ServiceType>(
type: T,
instanceName: string,
serviceInstance: ServiceReturnType<T>
): void {
if (ServiceProvider.has(type, instanceName)) {
const serviceTypeName = ServiceType[type];
throw new Error(
`Service instance ${instanceName} of type ${serviceTypeName} already exists`
);
}

if (!ServiceProvider.serviceMap.has(type)) {
ServiceProvider.serviceMap.set(type, new Map());
}

ServiceProvider.serviceMap.get(type)?.set(instanceName, serviceInstance);
}

/**
* Checks if a service of the specified type exists.
* @param type - The type of the service.
Expand Down Expand Up @@ -143,6 +174,12 @@ export class ServiceProvider {
ServiceReturnType<T>
>('sf.vscode.core.logger.get.instance', instanceName, ...rest);
break;
case ServiceType.Telemetry:
// Call VSCode command to materialize service A
serviceInstance = await vscode.commands.executeCommand<
ServiceReturnType<T>
>('sf.vscode.core.get.telemetry', instanceName, ...rest);
break;
default:
throw new Error(`Unsupported service type: ${type}`);
}
Expand Down
76 changes: 14 additions & 62 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,18 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

/**
* Standard `Logger` levels.
*
* **See** {@link https://getpino.io/#/docs/api?id=logger-level |Logger Levels}
*/
export enum LoggerLevel {
TRACE = 10,
DEBUG = 20,
INFO = 30,
WARN = 40,
ERROR = 50,
FATAL = 60
}

/**
* Any numeric `Logger` level.
*/
export type LoggerLevelValue = LoggerLevel | number;

/**
* An object
*/
export type Fields = Record<string, unknown>;

/**
* All possible field value types.
*/
export type FieldValue = string | number | boolean | Fields;

/**
* Log line interface
*/
export interface LogLine {
name: string;
hostname: string;
pid: string;
log: string;
level: number;
msg: string;
time: string;
v: number;
}

export interface ILogger {
getName(): string;
getLevel(): LoggerLevelValue;
setLevel(level?: LoggerLevelValue): ILogger;
shouldLog(level: LoggerLevelValue): boolean;
getBufferedRecords(): LogLine[];
readLogContentsAsText(): string;
child(name: string, fields?: Fields): ILogger;
addField(name: string, value: FieldValue): ILogger;
trace(...args: unknown[]): ILogger;
debug(...args: unknown[]): ILogger;
info(...args: unknown[]): ILogger;
warn(...args: unknown[]): ILogger;
error(...args: unknown[]): ILogger;
fatal(...args: unknown[]): ILogger;
}
import { LoggerInterface } from './logger/loggerTypes';
import { TelemetryServiceInterface } from './telemetry/telemetryTypes';

export enum ServiceType {
Logger
Logger = 'Logger',
Telemetry = 'Telemetry'
}

// Define a mapping from service types to their corresponding parameter types
interface ServiceParamsMap {
[ServiceType.Logger]: [string]; // TelemetryService requires a string parameter
[ServiceType.Logger]: [string]; // Logger requires a string parameter
[ServiceType.Telemetry]: [string];
}

// Define a type that represents the parameter types for a given service type
Expand All @@ -80,4 +25,11 @@ export type ServiceParams<T extends ServiceType> =

// Define a type that represents the return type for a given service type
export type ServiceReturnType<T extends ServiceType> =
T extends ServiceType.Logger ? ILogger : never;
T extends ServiceType.Telemetry
? TelemetryServiceInterface
: T extends ServiceType.Logger
? LoggerInterface
: never;

export * from './logger/loggerTypes';
export * from './telemetry/telemetryTypes';
66 changes: 66 additions & 0 deletions src/types/logger/loggerTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

/**
* Standard `Logger` levels.
*
* **See** {@link https://getpino.io/#/docs/api?id=logger-level |Logger Levels}
*/
export enum LoggerLevel {
TRACE = 10,
DEBUG = 20,
INFO = 30,
WARN = 40,
ERROR = 50,
FATAL = 60
}

/**
* Any numeric `Logger` level.
*/
export type LoggerLevelValue = LoggerLevel | number;

/**
* An object
*/
export type Fields = Record<string, unknown>;

/**
* All possible field value types.
*/
export type FieldValue = string | number | boolean | Fields;

/**
* Log line interface
*/
export interface LogLine {
name: string;
hostname: string;
pid: string;
log: string;
level: number;
msg: string;
time: string;
v: number;
}

export interface LoggerInterface {
getName(): string;
getLevel(): LoggerLevelValue;
setLevel(level?: LoggerLevelValue): LoggerInterface;
shouldLog(level: LoggerLevelValue): boolean;
getBufferedRecords(): LogLine[];
readLogContentsAsText(): string;
child(name: string, fields?: Fields): LoggerInterface;
addField(name: string, value: FieldValue): LoggerInterface;
trace(...args: unknown[]): LoggerInterface;
debug(...args: unknown[]): LoggerInterface;
info(...args: unknown[]): LoggerInterface;
warn(...args: unknown[]): LoggerInterface;
error(...args: unknown[]): LoggerInterface;
fatal(...args: unknown[]): LoggerInterface;
}
133 changes: 133 additions & 0 deletions src/types/telemetry/telemetryTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { ExtensionContext, ExtensionKind, ExtensionMode, Uri } from 'vscode';

/* eslint-disable header/header */
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

export interface TelemetryReporter {
sendTelemetryEvent(
eventName: string,
properties?: { [key: string]: string },
measurements?: { [key: string]: number }
): void;

sendExceptionEvent(
exceptionName: string,
exceptionMessage: string,
measurements?: { [key: string]: number }
): void;

dispose(): Promise<void>;
}
// end of Copyright (C) Microsoft Corporation. All rights reserved.

export type Measurements = {
[key: string]: number;
};

export type Properties = {
[key: string]: string;
};

export type TelemetryData = {
properties?: Properties;
measurements?: Measurements;
};

export type ExtensionInfo = {
isActive: boolean;
path: string;
kind: ExtensionKind;
uri: Uri;
loadStartDate: Date;
};

export type ExtensionsInfo = {
[extensionId: string]: ExtensionInfo;
};

export type ActivationInfo = Partial<ExtensionInfo> & {
startActivateHrTime: [number, number];
activateStartDate: Date;
activateEndDate?: Date;
extensionActivationTime: number;
markEndTime?: number;
};

export interface TelemetryServiceInterface {
/**
* Initialize Telemetry Service during extension activation.
* @param extensionContext extension context
*/
initializeService(extensionContext: ExtensionContext): Promise<void>;
/**
* Initialize Telemetry Service with name, foo, version, and extensionMode.
* @param name extension name
* @param apiKey
* @param version extension version
* @param extensionMode extension mode
*/
initializeServiceWithAttributes(
name: string,
apiKey?: string,
version?: string,
extensionMode?: ExtensionMode
): Promise<void>;

/**
* Helper to get the name for telemetryReporter
* if the extension from extension pack, use salesforcedx-vscode
* otherwise use the extension name
* exported only for unit test
*/
getTelemetryReporterName(): string;

getReporters(): TelemetryReporter[];

isTelemetryEnabled(): Promise<boolean>;

checkCliTelemetry(): Promise<boolean>;

isTelemetryExtensionConfigurationEnabled(): boolean;

setCliTelemetryEnabled(isEnabled: boolean): void;

sendActivationEventInfo(activationInfo: ActivationInfo): void;

sendExtensionActivationEvent(
hrstart: [number, number],
markEndTime?: number,
telemetryData?: TelemetryData
): void;

sendExtensionDeactivationEvent(): void;

sendCommandEvent(
commandName?: string,
hrstart?: [number, number],
properties?: Properties,
measurements?: Measurements
): void;

sendException(name: string, message: string): void;

sendEventData(
eventName: string,
properties?: { [key: string]: string },
measures?: { [key: string]: number }
): void;

dispose(): void;

getEndHRTime(hrstart: [number, number]): number;

hrTimeToMilliseconds(hrtime: [number, number]): number;
}
Loading

0 comments on commit 9ce5d4d

Please sign in to comment.