diff --git a/src/BrightScriptCommands.ts b/src/BrightScriptCommands.ts index 31d65929..6690c9bf 100644 --- a/src/BrightScriptCommands.ts +++ b/src/BrightScriptCommands.ts @@ -22,6 +22,9 @@ import type { CredentialStore } from './managers/CredentialStore'; import type { DevicesViewProvider } from './viewProviders/DevicesViewProvider'; import { DEVICE_FILTER_KEYS } from './deviceFilters'; import { rokuDeploy } from 'roku-deploy'; +import { createLogger } from './logging'; + +const logger = createLogger('BrightScriptCommands'); export class BrightScriptCommands { @@ -82,7 +85,7 @@ export class BrightScriptCommands { placeholder: 'Press enter to send all typed characters to the Roku', items: items }); - console.log('userInput', stuffUserTyped); + logger.log('userInput', stuffUserTyped); if (stuffUserTyped) { new GlobalStateManager(this.context).addTextHistory(stuffUserTyped); @@ -96,7 +99,7 @@ export class BrightScriptCommands { // fallbackToHttp = false; // } // } catch (error) { - // console.error(error); + // logger.error(error); // // Let this fallback to the old HTTP based logic // } @@ -908,7 +911,7 @@ export class BrightScriptCommands { if (host) { let clickUrl = `http://${host}:8060/keypress/${key}`; - console.log(`send ${clickUrl}`); + logger.log(`send ${clickUrl}`); return new Promise((resolve, reject) => { request.post(clickUrl, (err, response) => { if (err) { @@ -940,7 +943,7 @@ export class BrightScriptCommands { try { this.host = await rokuDebugUtil.dnsLookup(this.host); } catch (e) { - console.error('Error doing dns lookup for host ', this.host, e); + logger.error('Error doing dns lookup for host ', this.host, e); } } return this.host; diff --git a/src/BrightScriptDeclaration.ts b/src/BrightScriptDeclaration.ts index f15c293b..bbb159c3 100644 --- a/src/BrightScriptDeclaration.ts +++ b/src/BrightScriptDeclaration.ts @@ -8,6 +8,9 @@ import { SymbolKind } from 'vscode'; import * as vscode from 'vscode'; +import { createLogger } from './logging'; + +const logger = createLogger('BrightScriptDeclaration'); export class BrightScriptDeclaration { constructor( @@ -42,7 +45,7 @@ export class BrightScriptDeclaration { } else if (this.container) { return this.container.getDocumentUri(); } else { - console.log('getDocumentUri: ERROR could not find container for symbol' + this); + logger.log('getDocumentUri: ERROR could not find container for symbol' + this); } } diff --git a/src/DebugConfigurationProvider.ts b/src/DebugConfigurationProvider.ts index 415adf2c..02f7516c 100644 --- a/src/DebugConfigurationProvider.ts +++ b/src/DebugConfigurationProvider.ts @@ -23,6 +23,9 @@ import type { BrightScriptCommands } from './BrightScriptCommands'; import type { RokuProjectManager } from './managers/RokuProject/RokuProjectManager'; import type { DeviceManager, RokuDevice } from './deviceDiscovery/DeviceManager'; import type { CredentialStore } from './managers/CredentialStore'; +import { createLogger } from './logging'; + +const logger = createLogger('DebugConfigurationProvider'); export class BrightScriptDebugConfigurationProvider implements DebugConfigurationProvider { @@ -370,7 +373,7 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio } if (!config.rootDir) { - console.log('No rootDir specified: defaulting to ${workspaceFolder}'); + logger.log('No rootDir specified: defaulting to ${workspaceFolder}'); //use the current workspace folder config.rootDir = folderUri.fsPath; } @@ -434,7 +437,7 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio } if (await this.util.fileExists(envFilePath) === false) { //the .env file is optional, so just warn instead of failing the debug session - console.warn(`Cannot find .env file at "${envFilePath}". Falling back to the process environment for '\${env:*}' values.`); + logger.warn(`Cannot find .env file at "${envFilePath}". Falling back to the process environment for '\${env:*}' values.`); } else { //parse the .env file, letting its values override the process environment environmentValues = { @@ -476,7 +479,7 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio while ((match = regexp.exec(configValue))) { let environmentVariableName = match[1]; configValue = configDefaults[key]; - console.log(`The configuration value for ${key} was not found in the environment variables${loadedEnvFile ? ' or env file' : ''} under the name ${environmentVariableName}. Defaulting the value to: ${configValue}`); + logger.log(`The configuration value for ${key} was not found in the environment variables${loadedEnvFile ? ' or env file' : ''} under the name ${environmentVariableName}. Defaulting the value to: ${configValue}`); } config[key] = configValue; } @@ -696,7 +699,7 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio } catch (e) { //only log the error if the user explicitly defined a config path if (!isDefaultPath) { - console.error(`Could not load bsconfig file at "${configFilePath}`); + logger.error(`Could not load bsconfig file at "${configFilePath}`); } return undefined; } diff --git a/src/DeclarationProvider.ts b/src/DeclarationProvider.ts index 85989fe9..bc7f072d 100644 --- a/src/DeclarationProvider.ts +++ b/src/DeclarationProvider.ts @@ -17,6 +17,9 @@ import { import { BrightScriptDeclaration } from './BrightScriptDeclaration'; import { util } from './util'; +import { createLogger } from './logging'; + +const logger = createLogger('DeclarationProvider'); /////////////////////////////////////////////////////////////////////////////////////////////////////////// // CREDIT WHERE CREDIT IS DUE @@ -273,13 +276,13 @@ export class DeclarationProvider implements Disposable { }; for (const [line, text] of iterlines(input)) { - // console.log("" + line + ": " + text); + // logger.log("" + line + ": " + text); funcEndLine = line; funcEndChar = text.length; //FUNCTION START let match = /^\s*(?:public|protected|private)*\s*(?:override)*\s*(?:function|sub)\s+(.*[^\(])\s*\((.*)\)/i.exec(text); - // console.log("match " + match); + // logger.log("match " + match); if (match !== null) { // function has started if (currentFunction !== undefined) { @@ -300,7 +303,7 @@ export class DeclarationProvider implements Disposable { //FUNCTION END match = /^\s*(end)\s*(function|sub)/i.exec(text); if (match !== null) { - // console.log("function END"); + // logger.log("function END"); if (currentFunction !== undefined) { currentFunction.bodyRange = currentFunction.bodyRange.with({ end: new Position(funcEndLine, funcEndChar) }); //reset so the function's range isn't stretched over whatever follows it (e.g. a nested namespace) @@ -312,7 +315,7 @@ export class DeclarationProvider implements Disposable { // //FIELD match = /^(?!.*\()(?: |\t)*(public|private|protected)(?: |\t)*([a-z|\.|_]*).*((?: |\t)*=(?: |\t)*.*)*$/i.exec(text); if (match !== null) { - // console.log("FOUND VAR " + match); + // logger.log("FOUND VAR " + match); const name = match[2].trim(); if (mDefs[name] !== true) { mDefs[name] = true; @@ -324,7 +327,7 @@ export class DeclarationProvider implements Disposable { new Range(line, match[0].length - match[1].length, line, match[0].length), new Range(line, 0, line, text.length) ); - // console.log('FOUND VAR ' + varSymbol.name); + // logger.log('FOUND VAR ' + varSymbol.name); symbols.push(varSymbol); } continue; @@ -346,7 +349,7 @@ export class DeclarationProvider implements Disposable { new Range(line, match[0].length - match[1].length, line, match[0].length), new Range(line, 0, line, text.length) ); - // console.log('FOUND NAMESPACES ' + namespaceSymbol.name); + // logger.log('FOUND NAMESPACES ' + namespaceSymbol.name); symbols.push(namespaceSymbol); namespaces.add(name.toLowerCase()); containerStack.push(namespaceSymbol); @@ -373,7 +376,7 @@ export class DeclarationProvider implements Disposable { new Range(line, match[0].length - match[1].length, line, match[0].length), new Range(line, 0, line, text.length) ); - // console.log('FOUND enumS ' + enumSymbol.name); + // logger.log('FOUND enumS ' + enumSymbol.name); symbols.push(enumSymbol); enums.add(name.toLowerCase()); containerStack.push(enumSymbol); @@ -400,7 +403,7 @@ export class DeclarationProvider implements Disposable { new Range(line, match[0].length - match[2].length, line, match[0].length), new Range(line, 0, line, text.length) ); - // console.log('FOUND CLASS ' + classSymbol.name); + // logger.log('FOUND CLASS ' + classSymbol.name); symbols.push(classSymbol); classes.add(name.toLowerCase()); containerStack.push(classSymbol); @@ -427,7 +430,7 @@ export class DeclarationProvider implements Disposable { new Range(line, match[0].length - match[2].length, line, match[0].length), new Range(line, 0, line, text.length) ); - // console.log('FOUND interface ' + interfaceSymbol.name); + // logger.log('FOUND interface ' + interfaceSymbol.name); symbols.push(interfaceSymbol); interfaces.add(name.toLowerCase()); containerStack.push(interfaceSymbol); @@ -468,7 +471,7 @@ export class DeclarationProvider implements Disposable { // if there was no match, then get the declarations now symbols = this.cache.get(filePath); } catch (e) { - console.error(`error loading symbols for file ${filePath}: ${e.message}`); + logger.error(`error loading symbols for file ${filePath}: ${e.message}`); } } //try to load it now diff --git a/src/DefinitionRepository.ts b/src/DefinitionRepository.ts index 60b98ff7..8ca83930 100644 --- a/src/DefinitionRepository.ts +++ b/src/DefinitionRepository.ts @@ -7,6 +7,9 @@ import type { import { BrightScriptDeclaration } from './BrightScriptDeclaration'; import type { DeclarationProvider } from './DeclarationProvider'; import { getExcludeGlob } from './DeclarationProvider'; +import { createLogger } from './logging'; + +const logger = createLogger('DefinitionRepository'); export class DefinitionRepository { @@ -126,7 +129,7 @@ export class DefinitionRepository { } const fresh = new Set([document.uri.fsPath]); for (const doc of vscode.workspace.textDocuments) { - console.log('>>>>>doc ' + doc.uri.path); + logger.log('>>>>>doc ' + doc.uri.path); if (!doc.isDirty) { continue; diff --git a/src/LanguageServerManager.ts b/src/LanguageServerManager.ts index b426d5ea..0a2ad240 100644 --- a/src/LanguageServerManager.ts +++ b/src/LanguageServerManager.ts @@ -5,8 +5,8 @@ import * as path from 'path'; import type { Disposable } from 'vscode'; import { window, workspace } from 'vscode'; import { BusyStatus, NotificationName, standardizePath as s } from 'brighterscript'; -import { Logger } from '@rokucommunity/logger'; import { CustomCommands, Deferred } from 'brighterscript'; +import { createLogger } from './logging'; import type { CodeWithSourceMap } from 'source-map'; import BrightScriptDefinitionProvider from './BrightScriptDefinitionProvider'; import { BrightScriptWorkspaceSymbolProvider, SymbolInformationRepository } from './SymbolInformationRepository'; @@ -22,6 +22,8 @@ import * as dayjs from 'dayjs'; import type { LocalPackageManager, ParsedVersionInfo } from './managers/LocalPackageManager'; import { firstBy } from 'thenby'; +const logger = createLogger('LanguageServerManager'); + /** * Tracks the running/stopped state of the language server. When the lsp crashes, vscode will restart it. After the 5th crash, they'll leave it permanently crashed. * There seems to be no time limit on adding up to the 5, so even after a few days, vscode may still terminate the language server. @@ -279,7 +281,7 @@ export class LanguageServerManager { this.client = this.constructLanguageClient(); this.client.onDidChangeState((event: StateChangeEvent) => { - console.log(new Date().toLocaleTimeString(), 'onDidChangeState', State[event.newState]); + logger.log('onDidChangeState', State[event.newState]); this.lspRunTracker.setState(event.newState); }); @@ -316,7 +318,6 @@ export class LanguageServerManager { private registerBusyStatusHandler() { let timeoutHandle: NodeJS.Timeout; - const logger = new Logger(); this.client.onNotification(NotificationName.busyStatus, (event: any) => { this.updateStatusbar(event.status === BusyStatus.busy, event.activeRuns); @@ -487,7 +488,7 @@ export class LanguageServerManager { try { this.selectedBscInfo = await this.ensureBscVersionInstalled(versionInfo); } catch (e) { - console.error(e); + logger.error(e); //fall back to the embedded version, and show a popup (don't await the popup because that blocks this flow) void vscode.window.showErrorMessage(`Language server failure. Did you forget \`npm install\`? Using embedded version ${this.embeddedBscInfo.version}. Can't find language server for "${versionInfo}"`); this.selectedBscInfo = this.embeddedBscInfo; @@ -608,7 +609,7 @@ export class LanguageServerManager { } catch (e) { if (retryCount > 0) { - console.error('Failed to install brighterscript', versionInfo, e); + logger.error('Failed to install brighterscript', versionInfo, e); //if the install failed for some reason, uninstall the package and try again await this.localPackageManager.uninstall('brighterscript', versionInfo); @@ -673,7 +674,7 @@ function OneAtATime(options: { timeout?: number }) { //race for the timeout to expire (we give up waiting for the previous task to complete) timer.then(() => { //our timer fired before we had a chance to cancel it. Report the error and move on - console.error(`timer expired waiting for the previous ${propertyKey} to complete. Running the next instance`, target); + logger.error(`timer expired waiting for the previous ${propertyKey} to complete. Running the next instance`, target); }) //now we can move on to the actual task ]).then(() => { diff --git a/src/LogDocumentLinkProvider.ts b/src/LogDocumentLinkProvider.ts index 71a8530e..7c3ff694 100644 --- a/src/LogDocumentLinkProvider.ts +++ b/src/LogDocumentLinkProvider.ts @@ -4,6 +4,9 @@ import { DocumentLink, Position, Range } from 'vscode'; import * as vscode from 'vscode'; import BrightscriptFileUtils from './BrightScriptFileUtils'; import type { BrightScriptLaunchConfiguration } from './DebugConfigurationProvider'; +import { createLogger } from './logging'; + +const logger = createLogger('LogDocumentLinkProvider'); export class CustomDocumentLink { constructor(outputLine: number, startChar: number, length: number, pkgPath: string, lineNumber: number, filename: string) { @@ -89,7 +92,7 @@ export class LogDocumentLinkProvider implements vscode.DocumentLinkProvider { let uri = vscode.Uri.parse(`vscode://rokucommunity.brightscript/openFile/${customLink.pkgPath}#${customLink.lineNumber}`); this.customLinks.push(new DocumentLink(range, uri)); } else { - console.log('could not find matching file for link with path ' + customLink.pkgPath); + logger.log('could not find matching file for link with path ' + customLink.pkgPath); } } diff --git a/src/LogOutputManager.ts b/src/LogOutputManager.ts index d4f0201a..d77723e2 100644 --- a/src/LogOutputManager.ts +++ b/src/LogOutputManager.ts @@ -7,6 +7,9 @@ import { util } from './util'; import * as fsExtra from 'fs-extra'; import type { BrightScriptLaunchConfiguration } from './DebugConfigurationProvider'; import stripAnsi from 'strip-ansi'; +import { createLogger } from './logging'; + +const logger = createLogger('LogOutputManager'); export class LogLine { constructor( @@ -259,12 +262,12 @@ export class LogOutputManager { if (!this.includeStackTraces) { // filter out debugger noise if (this.debugStartRegex.exec(line)) { - console.log('start MicroDebugger block'); + logger.log('start MicroDebugger block'); this.isInMicroDebugger = true; this.isNextBreakpointSkipped = false; line = 'Pausing for a breakpoint...'; } else if (this.isInMicroDebugger && (this.debugEndRegex.exec(line))) { - console.log('ended MicroDebugger block'); + logger.log('ended MicroDebugger block'); this.isInMicroDebugger = false; if (this.isNextBreakpointSkipped) { line = '\n**Was a bogus breakpoint** Skipping!\n'; @@ -273,7 +276,7 @@ export class LogOutputManager { } } else if (this.isInMicroDebugger) { if (this.launchConfig.enableDebuggerAutoRecovery && line.startsWith('Break in ')) { - console.log('this block is a break: skipping it'); + logger.log('this block is a break: skipping it'); this.isNextBreakpointSkipped = true; } line = null; diff --git a/src/commands/BrighterScriptPreviewCommand.ts b/src/commands/BrighterScriptPreviewCommand.ts index d6d5ee85..36f12bd0 100644 --- a/src/commands/BrighterScriptPreviewCommand.ts +++ b/src/commands/BrighterScriptPreviewCommand.ts @@ -5,6 +5,9 @@ import * as path from 'path'; import * as querystring from 'querystring'; import { SourceMapConsumer } from 'source-map'; import { languageServerManager } from '../LanguageServerManager'; +import { createLogger } from '../logging'; + +const logger = createLogger('BrighterScriptPreviewCommand'); export const FILE_SCHEME = 'bs-preview'; @@ -120,7 +123,7 @@ export class BrighterScriptPreviewCommand { activePreview.previewEditor.revealRange(mappedRange, vscode.TextEditorRevealType.InCenter); activePreview.previewEditor.selection = new vscode.Selection(mappedRange.start, mappedRange.end); } catch (e) { - console.error(e); + logger.error(e); } } } @@ -172,7 +175,7 @@ export class BrighterScriptPreviewCommand { activePreview.sourceEditor.revealRange(mappedRange, vscode.TextEditorRevealType.InCenter); activePreview.sourceEditor.selection = new vscode.Selection(mappedRange.start, mappedRange.end); } catch (e) { - console.error(e); + logger.error(e); } } } diff --git a/src/commands/ProfilingCommands.ts b/src/commands/ProfilingCommands.ts index 28c28e19..f4078e68 100644 --- a/src/commands/ProfilingCommands.ts +++ b/src/commands/ProfilingCommands.ts @@ -1,6 +1,9 @@ import * as vscode from 'vscode'; import { vscodeContextManager } from '../managers/VscodeContextManager'; import { isProfilingEnabledEvent, isProfilingStartEvent, isProfilingStopEvent, isProfilingErrorEvent } from 'roku-debug'; +import { createLogger } from '../logging'; + +const logger = createLogger('ProfilingCommands'); export class ProfilingCommands { @@ -68,7 +71,7 @@ export class ProfilingCommands { await session.customRequest('startPerfettoTracing'); await vscodeContextManager.set('brightscript.tracingActive', true); } catch (e) { - console.error(`Failed to start tracing`, e); + logger.error(`Failed to start tracing`, e); } } ) @@ -86,7 +89,7 @@ export class ProfilingCommands { try { await session.customRequest('stopPerfettoTracing'); } catch (e) { - console.error(`Failed to stop tracing:`, e); + logger.error(`Failed to stop tracing:`, e); } } ) @@ -103,7 +106,7 @@ export class ProfilingCommands { try { await session.customRequest('captureHeapSnapshot'); } catch (e) { - console.error(`Failed to capture snapshot:`, e); + logger.error(`Failed to capture snapshot:`, e); } } diff --git a/src/deviceDiscovery/DeviceManager.ts b/src/deviceDiscovery/DeviceManager.ts index 1e18cae6..857b5f85 100644 --- a/src/deviceDiscovery/DeviceManager.ts +++ b/src/deviceDiscovery/DeviceManager.ts @@ -12,6 +12,9 @@ import { util } from '../util'; import { vscodeContextManager } from '../managers/VscodeContextManager'; import { debounce } from 'lodash'; import { icons } from '../icons'; +import { createLogger } from '../logging'; + +const logger = createLogger('DeviceManager'); export class DeviceManager { // #region constructor @@ -138,7 +141,7 @@ export class DeviceManager { this.setScanNeeded(); } }).catch((e) => { - console.error(e); + logger.error(e); }); } } @@ -903,7 +906,7 @@ export class DeviceManager { // Check if the serial was last seen at this IP (don't trust cache if device moved) const cachedIp = serialForCache ? this.globalStateManager.getIpForSerial(serialForCache, this.networkId) : undefined; const cacheIsFresh = cached && (Date.now() - cached.createdAt < this.DEVICE_INFO_CACHE_MS) && cachedIp === device.ip; - console.log('[TRACE] resolveDevice', device.ip, 'serialForCache=', serialForCache, 'cachedIp=', cachedIp, 'cacheIsFresh=', cacheIsFresh); + logger.log('[TRACE] resolveDevice', device.ip, 'serialForCache=', serialForCache, 'cachedIp=', cachedIp, 'cacheIsFresh=', cacheIsFresh); // Use cache only if: // - Not forced @@ -1125,7 +1128,7 @@ export class DeviceManager { return info; } catch (e) { - console.error(e); + logger.error(e); return undefined; } } @@ -1430,7 +1433,7 @@ export class DeviceManager { // Restart if device discovery is enabled if (this.deviceDiscoveryEnabled) { this.startRokuFinder().catch((e) => { - console.error('Failed to restart RokuFinder:', e); + logger.error('Failed to restart RokuFinder:', e); }); } } diff --git a/src/deviceDiscovery/RokuFinder.ts b/src/deviceDiscovery/RokuFinder.ts index e5381fee..c9ab40f7 100644 --- a/src/deviceDiscovery/RokuFinder.ts +++ b/src/deviceDiscovery/RokuFinder.ts @@ -2,6 +2,9 @@ import { EventEmitter } from 'eventemitter3'; import type { SsdpHeaders } from 'node-ssdp'; import { Client, Server } from 'node-ssdp'; import type { GlobalStateManager } from '../GlobalStateManager'; +import { createLogger } from '../logging'; + +const logger = createLogger('RokuFinder'); export class RokuFinder extends EventEmitter { constructor( @@ -91,7 +94,7 @@ export class RokuFinder extends EventEmitter { Promise.resolve( this.client.search('roku:ecp') ).catch((error) => { - console.error(error); + logger.error(error); }); }; diff --git a/src/extension.ts b/src/extension.ts index 93d0475d..81b180ca 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -38,6 +38,9 @@ import { standardizePath as s } from 'brighterscript'; import { PerfettoEditorProvider } from './editors/PerfettoEditor'; import { RokuProjectManager } from './managers/RokuProject/RokuProjectManager'; import { RokuProjectsViewProvider } from './viewProviders/RokuProjectsViewProvider'; +import { attachExtensionOutputChannel, createLogger } from './logging'; + +const logger = createLogger('Extension'); export class Extension { public outputChannel: vscode.OutputChannel; @@ -81,6 +84,7 @@ export class Extension { this.telemetryManager.sendStartupEvent(); this.extensionOutputChannel = util.createOutputChannel('BrightScript Extension', this.writeExtensionLog.bind(this)); + attachExtensionOutputChannel(this.extensionOutputChannel); this.extensionOutputChannel.appendLine('Extension startup'); this.deviceManager = new DeviceManager(context, this.globalStateManager, this.extensionOutputChannel); const credentialStore = new CredentialStore(context); @@ -395,7 +399,7 @@ export class Extension { try { await logOutputManager.onDidReceiveDebugSessionCustomEvent(e); } catch (err) { - console.error('Error handling custom event', e, err); + logger.error('Error handling custom event', e, err); } } @@ -455,7 +459,7 @@ export class Extension { }); execution = await vscode.tasks.executeTask(targetTask); - console.log(execution); + logger.log(execution); await taskFinished; } @@ -465,10 +469,10 @@ export class Extension { */ private async processStagingDir(event: CustomRequestEvent<{ projects: Array<{ type: string; stagingDir: string }> }>) { const projects = event.body.projects ?? []; - console.log(`[processStagingDir] received ${projects.length} project(s) to process`); + logger.log(`[processStagingDir] received ${projects.length} project(s) to process`); for (const project of projects) { const exists = await fsExtra.pathExists(project.stagingDir); - console.log(`[processStagingDir] ${project.type} staging dir ${exists ? 'exists' : 'is MISSING'}: ${project.stagingDir}`); + logger.log(`[processStagingDir] ${project.type} staging dir ${exists ? 'exists' : 'is MISSING'}: ${project.stagingDir}`); } } diff --git a/src/logging.ts b/src/logging.ts new file mode 100644 index 00000000..f1960729 --- /dev/null +++ b/src/logging.ts @@ -0,0 +1,59 @@ +import type * as vscode from 'vscode'; +import type { LogMessage, Transport } from '@rokucommunity/logger'; +import { Logger, ConsoleTransport } from '@rokucommunity/logger'; + +/** + * Routes log messages to a vscode OutputChannel once one has been attached. + * Until then, messages are buffered so logs emitted during module load + * (before extension activation) are not lost. + */ +class OutputChannelTransport implements Transport { + private channel: vscode.OutputChannel | undefined; + private buffer: LogMessage[] = []; + + public attach(channel: vscode.OutputChannel) { + this.channel = channel; + for (const message of this.buffer) { + this.write(message); + } + this.buffer = []; + } + + public pipe(message: LogMessage) { + if (this.channel) { + this.write(message); + } else { + this.buffer.push(message); + } + } + + private write(message: LogMessage) { + this.channel.appendLine(message.logger.formatMessage(message, false)); + } +} + +const outputChannelTransport = new OutputChannelTransport(); + +/** + * Singleton logger for the extension. Writes to the developer console (for + * extension-host debugging) and to the `BrightScript Extension` output channel + * (for end-users). Use `logger.createLogger('Prefix')` to make a sub-logger + * that tags every message with a component name. + */ +export const logger = new Logger({ + logLevel: 'log', + transports: [ + new ConsoleTransport(), + outputChannelTransport + ] +}); + +export const createLogger = logger.createLogger.bind(logger) as typeof Logger.prototype.createLogger; + +/** + * Attach the extension output channel to the shared logger. Should be called + * once during extension activation, right after the channel is created. + */ +export function attachExtensionOutputChannel(channel: vscode.OutputChannel) { + outputChannelTransport.attach(channel); +} diff --git a/src/managers/RokuProject/RokuProjectManager.ts b/src/managers/RokuProject/RokuProjectManager.ts index c7f80cee..feb07d03 100644 --- a/src/managers/RokuProject/RokuProjectManager.ts +++ b/src/managers/RokuProject/RokuProjectManager.ts @@ -7,6 +7,9 @@ import { BsConfigProjectProvider } from './BsConfigProjectProvider'; import { ManifestProjectProvider } from './ManifestProjectProvider'; import { VscodeCommand } from '../../commands/VscodeCommand'; import { util } from '../../util'; +import { createLogger } from '../../logging'; + +const logger = createLogger('RokuProjectManager'); export class RokuProjectManager { @@ -50,7 +53,7 @@ export class RokuProjectManager { watcher.onDidCreate(uri => { if (!isExcluded(uri)) { this.registerProject(uri).catch(err => { - console.error('Error registering Roku project:', err); + logger.error('Error registering Roku project:', err); }); } }), @@ -68,7 +71,7 @@ export class RokuProjectManager { // rebuilds the project from the updated file this.unregisterProject(uri); this.registerProject(uri).catch(err => { - console.error('Error registering Roku project after change:', err); + logger.error('Error registering Roku project after change:', err); }); } }) @@ -83,7 +86,7 @@ export class RokuProjectManager { this.unregisterProject(project.configUri); } this.syncProjects().catch(err => { - console.error('Error resyncing Roku projects after exclude change:', err); + logger.error('Error resyncing Roku projects after exclude change:', err); }); } }), @@ -108,7 +111,7 @@ export class RokuProjectManager { } } }).catch((err: unknown) => { - console.error('Error syncing Roku projects for added workspace folder:', err); + logger.error('Error syncing Roku projects for added workspace folder:', err); }); } } @@ -117,7 +120,7 @@ export class RokuProjectManager { // Populate the task registry with whatever is currently in the workspace this.syncProjects().catch(err => { - console.error('Error syncing Roku projects:', err); + logger.error('Error syncing Roku projects:', err); }); } @@ -211,7 +214,7 @@ export class RokuProjectManager { this.resyncTimer = setTimeout(() => { this.resyncTimer = undefined; this.syncProjects().catch(err => { - console.error('Error during scheduled resync of Roku projects:', err); + logger.error('Error during scheduled resync of Roku projects:', err); }); }, RokuProjectManager.resyncDebounceMs); } diff --git a/src/managers/WebviewViewProviderManager.ts b/src/managers/WebviewViewProviderManager.ts index b0ef97cb..6bfdbba5 100644 --- a/src/managers/WebviewViewProviderManager.ts +++ b/src/managers/WebviewViewProviderManager.ts @@ -89,7 +89,7 @@ export class WebviewViewProviderManager { // Mainly for communicating between webviews public sendMessageToWebviews(viewIds: string | string[], message) { - // console.log(`WebviewViewProviderManager: sendMessageToWebviews: ${viewIds} ${JSON.stringify(message)}`); + // logger.log(`WebviewViewProviderManager: sendMessageToWebviews: ${viewIds} ${JSON.stringify(message)}`); if (typeof viewIds === 'string') { viewIds = [viewIds]; } diff --git a/src/managers/WhatsNewManager.ts b/src/managers/WhatsNewManager.ts index 5f19611b..2766e5aa 100644 --- a/src/managers/WhatsNewManager.ts +++ b/src/managers/WhatsNewManager.ts @@ -3,6 +3,9 @@ import { gte as semverGte } from 'semver'; import * as vscode from 'vscode'; import type { GlobalStateManager } from '../GlobalStateManager'; import { util } from '../util'; +import { createLogger } from '../logging'; + +const logger = createLogger('WhatsNewManager'); const FILE_SCHEME = 'bs-whatsNew'; @@ -97,7 +100,7 @@ export class WhatsNewManager { uri = uri.with({ scheme: FILE_SCHEME }); void vscode.commands.executeCommand('markdown.showPreview', uri, { sideBySide: false }); } else { - console.error(`WhatsNewManager.showReleaseNotes: Unknown version: ${version}`); + logger.error(`WhatsNewManager.showReleaseNotes: Unknown version: ${version}`); } } } diff --git a/src/viewProviders/BaseWebviewViewProvider.ts b/src/viewProviders/BaseWebviewViewProvider.ts index aee92ef2..3a58a486 100644 --- a/src/viewProviders/BaseWebviewViewProvider.ts +++ b/src/viewProviders/BaseWebviewViewProvider.ts @@ -11,6 +11,9 @@ import { ViewProviderCommand } from './ViewProviderCommand'; import type { VscodeCommand } from '../commands/VscodeCommand'; import type { RtaManager } from '../managers/RtaManager'; import type { BrightScriptCommands } from '../BrightScriptCommands'; +import { createLogger } from '../logging'; + +const logger = createLogger('BaseWebviewViewProvider'); export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvider, vscode.Disposable { constructor( @@ -96,11 +99,11 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi protected postMessage(message) { this.view?.webview.postMessage(message).then(null, (reason) => { - console.log('postMessage failed: ', reason); + logger.log('postMessage failed: ', reason); }); this.panel?.webview.postMessage(message).then(null, (reason) => { - console.log('postMessage failed: ', reason); + logger.log('postMessage failed: ', reason); }); } @@ -146,7 +149,7 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi } else { const callback = this.messageCommandCallbacks[command]; if (!callback || !await callback(message)) { - console.warn('Did not handle message', message); + logger.warn('Did not handle message', message); } } } catch (e) { @@ -208,7 +211,7 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi }); } } catch (e) { - console.error(e); + logger.error(e); } return this.getIndexHtml(); } @@ -229,7 +232,7 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi try { html = fsExtra.readFileSync(this.webviewBasePath + '/index.html').toString(); } catch (e) { - console.error(e); + logger.error(e); html = '

Error loading webview

'; } //the data that will be replaced in the index.html diff --git a/src/viewProviders/RendezvousViewProvider.ts b/src/viewProviders/RendezvousViewProvider.ts index 44d70825..2968dc8b 100644 --- a/src/viewProviders/RendezvousViewProvider.ts +++ b/src/viewProviders/RendezvousViewProvider.ts @@ -3,6 +3,9 @@ import * as vscode from 'vscode'; import type { RendezvousHistory } from 'roku-debug'; import { isRendezvousEvent } from 'roku-debug'; import { ViewProviderId } from './ViewProviderId'; +import { createLogger } from '../logging'; + +const logger = createLogger('RendezvousViewProvider'); export class RendezvousViewProvider implements vscode.TreeDataProvider { @@ -68,7 +71,7 @@ export class RendezvousViewProvider implements vscode.TreeDataProvider { - console.error(`Error loading overlay thumbnails: ${e.message}`); + logger.error(`Error loading overlay thumbnails: ${e.message}`); reject(e); }); }); @@ -75,7 +78,7 @@ export class RokuAppOverlaysViewViewProvider extends BaseRdbViewProvider { const base64String = `data:image/png;base64, ${contents}`; return base64String; } catch (error) { - console.error(`Error reading or encoding file: ${error.message}`); + logger.error(`Error reading or encoding file: ${error.message}`); return null; } } diff --git a/src/viewProviders/RokuReplViewProvider.ts b/src/viewProviders/RokuReplViewProvider.ts index c3da59e3..263df859 100644 --- a/src/viewProviders/RokuReplViewProvider.ts +++ b/src/viewProviders/RokuReplViewProvider.ts @@ -11,6 +11,9 @@ import * as getPort from 'get-port'; import * as path from 'path'; import * as os from 'os'; import { Parser } from 'brighterscript'; +import { createLogger } from '../logging'; + +const logger = createLogger('RokuReplViewProvider'); export class RokuReplViewProvider extends BaseRdbViewProvider { public readonly id = ViewProviderId.rokuReplView; @@ -42,7 +45,7 @@ export class RokuReplViewProvider extends BaseRdbViewProvider { this.componentLibraryServer = new ComponentLibraryServer(); this.componentLibraryPort = await getPort(); await fsExtra.ensureDir(this.componentLibraryFolder); - await this.componentLibraryServer.startStaticFileHosting(this.componentLibraryFolder, this.componentLibraryPort, console.log); + await this.componentLibraryServer.startStaticFileHosting(this.componentLibraryFolder, this.componentLibraryPort, (line: string) => logger.log(line)); } const zip = new JSZip();