diff --git a/.gitignore b/.gitignore index 643416e..109efbe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build/ /nbcode/l10n/locale_zh_CN/release/ /nbcode/l10n/locale_ja/nbproject/private/ /nbcode/l10n/locale_zh_CN/nbproject/private/ +telemetryConfig.json \ No newline at end of file diff --git a/vscode/.vscode/launch.json b/vscode/.vscode/launch.json index 14ef7a1..45bf9d5 100644 --- a/vscode/.vscode/launch.json +++ b/vscode/.vscode/launch.json @@ -34,7 +34,7 @@ "preLaunchTask": "${defaultBuildTask}", "env": { "nbcode_userdir": "global", - "oracle.oracle-java.enable.debug-logs": "true" + "oracle_oracleJava_enable_debugLogs": "true" } }, { diff --git a/vscode/esbuild.js b/vscode/esbuild.js index dbcdf5d..80873aa 100644 --- a/vscode/esbuild.js +++ b/vscode/esbuild.js @@ -15,19 +15,19 @@ const scriptConfig = { }; const watchConfig = { - watch: { - onRebuild(error, result) { - console.log("[watch] build started"); - if (error) { - error.errors.forEach(error => - console.error(`> ${error.location.file}:${error.location.line}:${error.location.column}: error: ${error.text}`) - ); - } else { - console.log("[watch] build finished"); - } - }, + watch: { + onRebuild(error, result) { + console.log("[watch] build started"); + if (error) { + error.errors.forEach(error => + console.error(`> ${error.location.file}:${error.location.line}:${error.location.column}: error: ${error.text}`) + ); + } else { + console.log("[watch] build finished"); + } }, - }; + }, +}; const NON_NPM_ARTIFACTORY = new RegExp( String.raw`"resolved"\s*:\s*"http[s]*://(?!registry.npmjs.org)[^"]+"`, @@ -43,26 +43,74 @@ const checkAritfactoryUrl = () => { } } - (async () => { - const args = process.argv.slice(2); - try { - if (args.includes("--watch")) { - // Build and watch source code - console.log("[watch] build started"); - await build({ - ...scriptConfig, - ...watchConfig, +const createTelemetryConfig = () => { + const defaultConfig = { + telemetryRetryConfig: { + maxRetries: 6, + baseCapacity: 256, + baseTimer: 5000, + maxDelayMs: 100000, + backoffFactor: 2, + jitterFactor: 0.25 + }, + telemetryApi: { + baseUrl: null, + baseEndpoint: "/vscode/java/sendTelemetry", + version: "/v1" + } + } + + const envConfig = Object.freeze({ + telemetryRetryConfig: { + maxRetries: process.env.TELEMETRY_MAX_RETRIES, + baseCapacity: process.env.TELEMETRY_BASE_CAPACITY, + baseTimer: process.env.TELEMETRY_BASE_TIMER, + maxDelayMs: process.env.TELEMETRY_MAX_DELAY, + backoffFactor: process.env.TELEMETRY_BACKOFF_FACTOR, + jitterFactor: process.env.TELEMETRY_JITTER_FACTOR + }, + telemetryApi: { + baseUrl: process.env.TELEMETRY_API_BASE_URL, + baseEndpoint: process.env.TELEMETRY_API_ENDPOINT, + version: process.env.TELEMETRY_API_VERSION + } + }); + + Object.entries(defaultConfig).forEach(([parent, configs]) => { + if (parent in envConfig) { + Object.entries(configs).forEach(([key, _]) => { + if (envConfig[parent]?.[key]) { + defaultConfig[parent][key] = envConfig[parent][key]; + } }); - console.log("[watch] build finished"); - } else if(args.includes("--artifactory-check")){ - checkAritfactoryUrl(); - } else { - // Build source code - await build(scriptConfig); - console.log("build complete"); } - } catch (err) { - process.stderr.write(err.message); - process.exit(1); + }); + + fs.writeFileSync("telemetryConfig.json", JSON.stringify(defaultConfig, null, 4)); + console.log("Telemetry config generated successfully."); +} + +(async () => { + const args = process.argv.slice(2); + try { + if (args.includes("--watch")) { + // Build and watch source code + console.log("[watch] build started"); + await build({ + ...scriptConfig, + ...watchConfig, + }); + console.log("[watch] build finished"); + } else if (args.includes("--artifactory-check")) { + checkAritfactoryUrl(); + } else { + // Build source code + createTelemetryConfig(); + await build(scriptConfig); + console.log("build complete"); } - })(); \ No newline at end of file + } catch (err) { + process.stderr.write(err.message); + process.exit(1); + } +})(); \ No newline at end of file diff --git a/vscode/package.json b/vscode/package.json index 22d1c15..c6647ad 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -804,4 +804,4 @@ "jsonc-parser": "3.3.1", "vscode-languageclient": "^9.0.1" } -} +} \ No newline at end of file diff --git a/vscode/src/logger.ts b/vscode/src/logger.ts index de0ca56..660956a 100644 --- a/vscode/src/logger.ts +++ b/vscode/src/logger.ts @@ -29,7 +29,7 @@ export class ExtensionLogger { constructor(channelName: string) { this.outChannel = window.createOutputChannel(channelName); - this.isDebugLogEnabled = process.env['oracle.oracle-java.enable.debug-logs'] === "true"; + this.isDebugLogEnabled = process.env['oracle_oracleJava_enable_debugLogs'] === "true"; } public log(message: string): void { diff --git a/vscode/src/lsp/nbLanguageClient.ts b/vscode/src/lsp/nbLanguageClient.ts index f472ab7..05efe6e 100644 --- a/vscode/src/lsp/nbLanguageClient.ts +++ b/vscode/src/lsp/nbLanguageClient.ts @@ -62,7 +62,7 @@ export class NbLanguageClient extends LanguageClient { 'showHtmlPageSupport': true, 'wantsJavaSupport': true, 'wantsGroovySupport': false, - 'wantsTelemetryEnabled': Telemetry.isTelemetryFeatureAvailable, + 'wantsTelemetryEnabled': Telemetry.getIsTelemetryFeatureAvailable(), 'commandPrefix': extConstants.COMMAND_PREFIX, 'configurationPrefix': `${extConstants.COMMAND_PREFIX}.`, 'altConfigurationPrefix': `${extConstants.COMMAND_PREFIX}.` diff --git a/vscode/src/telemetry/config.ts b/vscode/src/telemetry/config.ts index 1852099..b087bae 100644 --- a/vscode/src/telemetry/config.ts +++ b/vscode/src/telemetry/config.ts @@ -5,7 +5,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -14,18 +14,58 @@ limitations under the License. */ import { RetryConfig, TelemetryApi } from "./types"; +import * as path from 'path'; +import * as fs from 'fs'; +import { LOGGER } from "../logger"; -export const TELEMETRY_RETRY_CONFIG: RetryConfig = Object.freeze({ - maxRetries: 6, - baseCapacity: 256, - baseTimer: 5 * 1000, - maxDelayMs: 100 * 1000, - backoffFactor: 2, - jitterFactor: 0.25 -}); - -export const TELEMETRY_API: TelemetryApi = Object.freeze({ - baseUrl: null, - baseEndpoint: "/vscode/java/sendTelemetry", - version: "/v1" -}); \ No newline at end of file +export class TelemetryConfiguration { + private static CONFIG_FILE_PATH = path.resolve(__dirname, "..", "..", "telemetryConfig.json"); + + private static instance: TelemetryConfiguration; + private retryConfig!: RetryConfig; + private apiConfig!: TelemetryApi; + + public constructor() { + this.initialize(); + } + + public static getInstance(): TelemetryConfiguration { + if (!TelemetryConfiguration.instance) { + TelemetryConfiguration.instance = new TelemetryConfiguration(); + } + return TelemetryConfiguration.instance; + } + + private initialize(): void { + try { + const config = JSON.parse(fs.readFileSync(TelemetryConfiguration.CONFIG_FILE_PATH).toString()); + + this.retryConfig = Object.freeze({ + maxRetries: config.telemetryRetryConfig.maxRetries, + baseCapacity: config.telemetryRetryConfig.baseCapacity, + baseTimer: config.telemetryRetryConfig.baseTimer, + maxDelayMs: config.telemetryRetryConfig.maxDelayMs, + backoffFactor: config.telemetryRetryConfig.backoffFactor, + jitterFactor: config.telemetryRetryConfig.jitterFactor + }); + + this.apiConfig = Object.freeze({ + baseUrl: config.telemetryApi.baseUrl, + baseEndpoint: config.telemetryApi.baseEndpoint, + version: config.telemetryApi.version + }); + } catch (error: any) { + LOGGER.error("Error occurred while setting up telemetry config"); + LOGGER.error(error.message); + } + } + + public getRetryConfig(): Readonly { + return this.retryConfig; + } + + public getApiConfig(): Readonly { + return this.apiConfig; + } + +} \ No newline at end of file diff --git a/vscode/src/telemetry/impl/postTelemetry.ts b/vscode/src/telemetry/impl/postTelemetry.ts index 32ce3f8..1385a91 100644 --- a/vscode/src/telemetry/impl/postTelemetry.ts +++ b/vscode/src/telemetry/impl/postTelemetry.ts @@ -13,8 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { integer } from "vscode-languageclient"; import { LOGGER } from "../../logger"; -import { TELEMETRY_API } from "../config"; +import { TelemetryConfiguration } from "../config"; import { BaseEvent } from "../events/baseEvent"; interface TelemetryEventResponse { @@ -28,9 +29,11 @@ export interface TelemetryPostResponse { }; export class PostTelemetry { + private TELEMETRY_API = TelemetryConfiguration.getInstance().getApiConfig(); + public post = async (events: BaseEvent[]): Promise => { try { - if (TELEMETRY_API.baseUrl == null) { + if (this.TELEMETRY_API.baseUrl == null) { return { success: [], failures: [] @@ -47,7 +50,7 @@ export class PostTelemetry { }; private addBaseEndpoint = (endpoint: string) => { - return `${TELEMETRY_API.baseUrl}${TELEMETRY_API.baseEndpoint}${TELEMETRY_API.version}${endpoint}`; + return `${this.TELEMETRY_API.baseUrl}${this.TELEMETRY_API.baseEndpoint}${this.TELEMETRY_API.version}${endpoint}`; } private postEvent = (event: BaseEvent): Promise => { @@ -57,7 +60,12 @@ export class PostTelemetry { return fetch(serverEndpoint, { method: "POST", - body: JSON.stringify(payload) + body: JSON.stringify(payload), + redirect: "follow", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } }); } @@ -65,17 +73,21 @@ export class PostTelemetry { let success: TelemetryEventResponse[] = [], failures: TelemetryEventResponse[] = []; eventResponses.forEach((eventResponse, index) => { const event = events[index]; + let list: TelemetryEventResponse[] = success; + let statusCode: integer = 0; if (eventResponse.status === "rejected") { - failures.push({ - event, - statusCode: -1 - }); + list = failures; + statusCode = -1; } else { - success.push({ - statusCode: eventResponse.value.status, - event - }); + statusCode = eventResponse.value.status; + if (statusCode <= 0 || statusCode >= 400) { + list = failures; + } } + list.push({ + event, + statusCode + }); }); return { diff --git a/vscode/src/telemetry/impl/telemetryRetry.ts b/vscode/src/telemetry/impl/telemetryRetry.ts index 12a70e4..c8a29db 100644 --- a/vscode/src/telemetry/impl/telemetryRetry.ts +++ b/vscode/src/telemetry/impl/telemetryRetry.ts @@ -15,15 +15,16 @@ */ import { LOGGER } from "../../logger"; -import { TELEMETRY_RETRY_CONFIG } from "../config"; +import { TelemetryConfiguration } from "../config"; import { BaseEvent } from "../events/baseEvent"; import { TelemetryPostResponse } from "./postTelemetry"; export class TelemetryRetry { - private timePeriod: number = TELEMETRY_RETRY_CONFIG.baseTimer; + private TELEMETRY_RETRY_CONFIG = TelemetryConfiguration.getInstance().getRetryConfig(); + private timePeriod: number = this.TELEMETRY_RETRY_CONFIG?.baseTimer; private timeout?: NodeJS.Timeout | null; private numOfAttemptsWhenTimerHits: number = 1; - private queueCapacity: number = TELEMETRY_RETRY_CONFIG.baseCapacity; + private queueCapacity: number = this.TELEMETRY_RETRY_CONFIG?.baseCapacity; private numOfAttemptsWhenQueueIsFull: number = 1; private triggeredDueToQueueOverflow: boolean = false; private callbackHandler?: () => {}; @@ -45,12 +46,12 @@ export class TelemetryRetry { private resetTimerParameters = () => { this.numOfAttemptsWhenTimerHits = 1; - this.timePeriod = TELEMETRY_RETRY_CONFIG.baseTimer; + this.timePeriod = this.TELEMETRY_RETRY_CONFIG.baseTimer; this.clearTimer(); } private increaseTimePeriod = (): void => { - if (this.numOfAttemptsWhenTimerHits <= TELEMETRY_RETRY_CONFIG.maxRetries) { + if (this.numOfAttemptsWhenTimerHits <= this.TELEMETRY_RETRY_CONFIG.maxRetries) { this.timePeriod = this.calculateDelay(); this.numOfAttemptsWhenTimerHits++; return; @@ -66,26 +67,26 @@ export class TelemetryRetry { } private calculateDelay = (): number => { - const baseDelay = TELEMETRY_RETRY_CONFIG.baseTimer * - Math.pow(TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenTimerHits); + const baseDelay = this.TELEMETRY_RETRY_CONFIG.baseTimer * + Math.pow(this.TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenTimerHits); - const cappedDelay = Math.min(baseDelay, TELEMETRY_RETRY_CONFIG.maxDelayMs); + const cappedDelay = Math.min(baseDelay, this.TELEMETRY_RETRY_CONFIG.maxDelayMs); - const jitterMultiplier = 1 + (Math.random() * 2 - 1) * TELEMETRY_RETRY_CONFIG.jitterFactor; + const jitterMultiplier = 1 + (Math.random() * 2 - 1) * this.TELEMETRY_RETRY_CONFIG.jitterFactor; return Math.floor(cappedDelay * jitterMultiplier); }; private increaseQueueCapacity = (): void => { - if (this.numOfAttemptsWhenQueueIsFull < TELEMETRY_RETRY_CONFIG.maxRetries) { - this.queueCapacity = TELEMETRY_RETRY_CONFIG.baseCapacity * - Math.pow(TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenQueueIsFull); + if (this.numOfAttemptsWhenQueueIsFull < this.TELEMETRY_RETRY_CONFIG.maxRetries) { + this.queueCapacity = this.TELEMETRY_RETRY_CONFIG.baseCapacity * + Math.pow(this.TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenQueueIsFull); } throw new Error("Number of retries exceeded"); } private resetQueueCapacity = (): void => { - this.queueCapacity = TELEMETRY_RETRY_CONFIG.baseCapacity; + this.queueCapacity = this.TELEMETRY_RETRY_CONFIG.baseCapacity; this.numOfAttemptsWhenQueueIsFull = 1; this.triggeredDueToQueueOverflow = false; } diff --git a/vscode/src/telemetry/telemetry.ts b/vscode/src/telemetry/telemetry.ts index 3f60cf4..a43623b 100644 --- a/vscode/src/telemetry/telemetry.ts +++ b/vscode/src/telemetry/telemetry.ts @@ -18,21 +18,24 @@ import { ExtensionContextInfo } from "../extensionContextInfo"; import { LOGGER } from "../logger"; import { BaseEvent } from "./events/baseEvent"; import { TelemetryReporter } from "./types"; -import { TELEMETRY_API } from "./config"; +import { TelemetryConfiguration } from "./config"; export namespace Telemetry { let telemetryManager: TelemetryManager; - - export const isTelemetryFeatureAvailable = TELEMETRY_API.baseUrl != null && TELEMETRY_API.baseUrl.trim().length; - + + export const getIsTelemetryFeatureAvailable = (): boolean => { + const TELEMETRY_API = TelemetryConfiguration.getInstance()?.getApiConfig(); + return TELEMETRY_API?.baseUrl != null && TELEMETRY_API?.baseUrl.trim().length > 0; + } + export const initializeTelemetry = (contextInfo: ExtensionContextInfo): TelemetryManager => { if (!!telemetryManager) { LOGGER.warn("Telemetry is already initialized"); return telemetryManager; } telemetryManager = new TelemetryManager(contextInfo); - if (isTelemetryFeatureAvailable) { + if (getIsTelemetryFeatureAvailable()) { telemetryManager.initializeReporter(); } @@ -40,7 +43,7 @@ export namespace Telemetry { } const enqueueEvent = (cbFunction: (reporter: TelemetryReporter) => void) => { - if (telemetryManager.isExtTelemetryEnabled() && isTelemetryFeatureAvailable) { + if (telemetryManager.isExtTelemetryEnabled() && getIsTelemetryFeatureAvailable()) { const reporter = telemetryManager.getReporter(); if (reporter) { cbFunction(reporter);