@timheuer/vscode-ext-logger
v0.1.35
Published
A library to make logging simpler for VS Code extension authors.
Downloads
100
Maintainers
Readme
VS Code Extension Logger
A TypeScript library to make logging simpler for VS Code extension authors. Uses VS Code's native LogOutputChannel API for optimal integration.
Features
- 🎯 Simple API - Easy to use logging interface
- 📊 Complete Log Level Support - Off, Error, Warn, Info, Debug, Trace
- 🔗 Native VS Code Integration - Uses LogOutputChannel for proper VS Code integration
- 📦 TypeScript First - Built with TypeScript for better developer experience
- 🎨 Flexible - Works both inside and outside VS Code environment
- 📍 Structured Logging - Leverages VS Code's built-in log formatting and filtering
Installation
npm install @timheuer/vscode-ext-loggerQuick Start
Basic Usage
import { Logger, LogLevel } from '@timheuer/vscode-ext-logger';
// Create a logger with VS Code LogOutputChannel integration
const logger = new Logger({
name: context.extension.packageJSON.displayName, // If you have the ExtensionContext else put a string
level: LogLevel.Info, // Or use string: level: 'info'
outputChannel: true // Enable VS Code LogOutputChannel integration
});
// Use the logger - messages appear in VS Code's Output panel
logger.info('Extension activated');
logger.warn('This is a warning');
logger.error('Something went wrong');
logger.debug('Debug information'); // Won't show with Info level
logger.trace('Detailed trace info'); // Won't show with Info level
// Change log level
logger.setLevel(LogLevel.Debug);
logger.debug('Now this will show');
// Disable all logging
logger.setLevel(LogLevel.Off);Async Extension Activation
If your VS Code extension uses an async activate() method and you need to ensure the output channel is ready immediately, use these strategies:
Option 1: Sync Initialization (Recommended)
// Default behavior - output channel is created synchronously
export async function activate(context: vscode.ExtensionContext) {
const logger = new Logger({ name: 'MyExtension', context });
logger.info('Ready immediately!'); // Output channel is already available
}Option 2: Async Factory Functions
import { createLoggerWithLevelAsync, createLoggerFromConfigAsync } from '@timheuer/vscode-ext-logger';
// Guaranteed async output channel initialization
export async function activate(context: vscode.ExtensionContext) {
const logger = await createLoggerWithLevelAsync('MyExtension', 'info', true, context);
logger.info('Output channel guaranteed ready!');
// Or with config reading
const configLogger = await createLoggerFromConfigAsync('MyExt', 'myExt', 'logLevel', 'info', true, context);
configLogger.info('Config-based logger ready!');
}Option 3: Explicit Async Initialization
export async function activate(context: vscode.ExtensionContext) {
const logger = new Logger({ name: 'MyExtension', context });
await logger.ensureOutputChannel(); // Explicitly ensure ready
logger.info('Output channel confirmed ready!');
}Available Async Factory Functions:
createLoggerWithLevelAsync()- Basic async logger creationcreateLoggerFromConfigAsync()- Config-based with async initializationcreateLoggerWithConfigMonitoringAsync()- Config monitoring with async initialization
String-Based Log Levels (Simplified Configuration)
For easier configuration management (especially with VS Code settings), you can use string-based log levels:
import { Logger, createLoggerWithLevel, createLoggerFromConfig } from '@timheuer/vscode-ext-logger';
// Create logger with string log level
const logger = new Logger({
name: 'MyExtension',
level: 'debug', // String instead of LogLevel.Debug
outputChannel: true
});
// Or use convenience function
const logger2 = createLoggerWithLevel('MyExtension', 'info');
// Automatically configure from VS Code settings with monitoring
const logger3 = createLoggerFromConfig('MyExtension', 'myExtension');
// This reads from VS Code config: myExtension.logLevel
// AND automatically monitors for config changes!
// Update log level from string
logger.setLevelFromString('trace');
// Update from VS Code configuration (one-time)
logger.setLevelFromConfig('myExtension', 'logLevel', 'info');Automatic Configuration Monitoring 🆕
NEW: The library can now automatically monitor VS Code configuration changes and update log levels in real-time!
// Create logger with automatic config monitoring (opt-in)
const logger = createLoggerFromConfig('MyExtension', 'myExtension', 'logLevel', 'info', true, context, true);
// When user changes 'myExtension.logLevel' in VS Code settings,
// the logger automatically updates its level!
// Or use the explicit function (always enables monitoring)
import { createLoggerWithConfigMonitoring } from '@timheuer/vscode-ext-logger';
const monitoringLogger = createLoggerWithConfigMonitoring('MyExtension', 'myExtension');
// Manual control over monitoring
const logger = new Logger({ name: 'MyExtension', context });
logger.enableConfigMonitoring('myExtension', 'logLevel', 'info');
// Logger now automatically responds to config changes
// Later, disable monitoring if needed
logger.disableConfigMonitoring();Benefits of Config Monitoring
- Real-time updates: Log level changes immediately when users modify settings
- No restart required: Works without reloading the extension
- Automatic cleanup: Listeners are properly disposed when logger is disposed
- Context integration: Uses VS Code's extension context for automatic cleanup
- Performance optimized: Only monitors the specific config section you specify
- Conflict-free: Opt-in design prevents conflicts with existing extension monitoring
Avoiding Double-Monitoring
Important: If your extension already monitors configuration changes and calls setLevelFromString(), you should either:
Remove your existing monitoring and enable library monitoring:
// Remove your existing config monitoring code and use: const logger = createLoggerFromConfig('MyExt', 'myExt', 'logLevel', 'info', true, context, true);Keep your existing monitoring and disable library monitoring:
// Keep your existing config monitoring and use: const logger = createLoggerFromConfig('MyExt', 'myExt', 'logLevel', 'info', true, context, false); // Your existing code continues to work: vscode.workspace.onDidChangeConfiguration((event) => { if (event.affectsConfiguration('myExt')) { logger.setLevelFromString(vscode.workspace.getConfiguration('myExt').get('logLevel')); } });
The default is false to ensure existing extensions don't accidentally get double-monitoring.
Best Practices for Config Monitoring
// ✅ RECOMMENDED: Pass extension context for automatic cleanup
export function activate(context: vscode.ExtensionContext) {
// Option 1: Enable automatic monitoring (opt-in for safety)
const logger = createLoggerFromConfig(
'MyExtension',
'myExtension',
'logLevel',
'info',
true,
context,
true // Enable monitoring - opt-in to avoid conflicts with existing code
);
// Option 2: Use the always-monitoring convenience function
const alwaysMonitoringLogger = createLoggerWithConfigMonitoring(
'MyExtension',
'myExtension'
);
// Config changes are automatically handled
logger.info('Extension activated');
}
// ✅ Manual cleanup if not using context
const logger = createLoggerFromConfig('MyExtension', 'myExtension', 'logLevel', 'info', true, undefined, true);
// In your extension's deactivate function
export function deactivate() {
logger.dispose(); // This cleans up the config watcher
}
// ✅ Disable monitoring for performance-critical scenarios or to avoid conflicts
const logger = createLoggerFromConfig(
'MyExtension',
'myExtension',
'logLevel',
'info',
true,
context,
false // Disable monitoring - safe for existing extensions with their own config monitoring
);
// ✅ Enable monitoring later when needed
logger.enableConfigMonitoring('myExtension', 'logLevel', 'info');Simplified Extension Configuration
Instead of the manual mapping you showed, you can now do this:
// Before (cumbersome):
const config = vscode.workspace.getConfiguration('loggerTester');
const logLevelSetting = config.get<string>('logLevel', 'info');
let logLevel: LogLevel;
switch (logLevelSetting.toLowerCase()) {
case 'off': logLevel = LogLevel.Off; break; // etc...
}
const logger = new Logger({ name: 'Test', level: logLevel });
// After (simple + optional automatic monitoring):
const logger = createLoggerFromConfig('MyExtension', 'loggerTester', 'logLevel', 'info', true, context, true);
// Now when user changes 'loggerTester.logLevel' in settings,
// the logger automatically updates! (opt-in to avoid conflicts)
// Or without monitoring (safe for existing extensions):
const logger = createLoggerFromConfig('MyExtension', 'loggerTester');
// Or:
const logger = new Logger({
name: 'MyExtension',
level: vscode.workspace.getConfiguration('loggerTester').get('logLevel', 'info')
});API Reference
Logger Class
Constructor Options
interface LoggerOptions {
name?: string; // Logger name (default: 'Extension')
level?: LogLevel | string; // Log level enum or string (default: LogLevel.Info)
outputChannel?: boolean; // Create VS Code LogOutputChannel (default: true)
context?: ExtensionContext; // VS Code ExtensionContext for accurate log file access
}String Log Levels
All methods that accept LogLevel enum also accept string equivalents:
'off'→LogLevel.Off'error'→LogLevel.Error'warn'or'warning'→LogLevel.Warn'info'→LogLevel.Info'debug'→LogLevel.Debug'trace'→LogLevel.Trace
String matching is case-insensitive and handles whitespace.
Log Levels
enum LogLevel {
Off = 0, // No logging
Error = 1, // Only errors
Warn = 2, // Errors and warnings
Info = 3, // Errors, warnings, and info (default)
Debug = 4, // All except trace
Trace = 5, // All log levels
}Methods
error(message: string, ...args: unknown[]): void- Log error messagewarn(message: string, ...args: unknown[]): void- Log warning messageinfo(message: string, ...args: unknown[]): void- Log info messagedebug(message: string, ...args: unknown[]): void- Log debug messagetrace(message: string, ...args: unknown[]): void- Log trace messagesetLevel(level: LogLevel): void- Set the log levelsetLevelFromString(level: string): void- Set log level from stringsetLevelFromConfig(configSection: string, configKey?: string, defaultLevel?: string): void- Update log level from VS Code configurationgetLevel(): LogLevel- Get current log levelgetLogContents(): Promise<LogContentsResult>- Get the persisted log file contents for this loggerenableConfigMonitoring(configSection: string, configKey?: string, defaultLevel?: string): void- Enable automatic monitoring of VS Code configuration changesdisableConfigMonitoring(): void- Disable automatic configuration monitoringshow(): void- Show the VS Code output channel (if available)dispose(): void- Dispose of resources
Static Methods
Logger.getLogContentsForChannel(channelName: string): Promise<LogContentsResult>- Get log contents for any channel name
LogLevelUtils Class
Utility functions for working with log levels:
import { LogLevelUtils } from '@timheuer/vscode-ext-logger';
// Convert string to LogLevel enum
const level = LogLevelUtils.fromString('debug'); // Returns LogLevel.Debug
// Convert LogLevel enum to string
const levelString = LogLevelUtils.toString(LogLevel.Error); // Returns 'error'
// Get all valid level strings
const validLevels = LogLevelUtils.getValidLevels();
// Returns: ['off', 'error', 'warn', 'warning', 'info', 'debug', 'trace']Convenience Functions
import {
createLogger,
createLoggerWithLevel,
createLoggerFromConfig,
createLoggerWithConfigMonitoring,
getLogContentsForChannel,
getLogContents,
logger
} from '@timheuer/vscode-ext-logger';
// Use the default logger (no VS Code integration)
logger.info('Using default logger');
// Create a custom logger with VS Code integration
const myLogger = createLogger({
name: 'MyComponent',
level: LogLevel.Debug,
outputChannel: true // This creates the LogOutputChannel
});
// Create a logger with string-based level
const stringLogger = createLoggerWithLevel('MyComponent', 'debug');
// Create a logger that automatically reads from VS Code configuration
// with automatic monitoring available as opt-in
const configLogger = createLoggerFromConfig(
'MyExtension', // Logger name
'myExtension', // Config section
'logLevel', // Config key (optional, defaults to 'logLevel')
'info', // Default level (optional, defaults to 'info')
true, // Output channel (optional, defaults to true)
context, // Extension context (optional, for auto cleanup)
false // Monitor config (optional, defaults to false to avoid conflicts)
);
// Create a logger with config monitoring explicitly enabled
const monitoringLogger = createLoggerWithConfigMonitoring(
'MyExtension', // Logger name
'myExtension', // Config section
'logLevel', // Config key (optional, defaults to 'logLevel')
'info', // Default level (optional, defaults to 'info')
true, // Output channel (optional, defaults to true)
context // Extension context (optional, for auto cleanup)
);
// Create a simple logger without VS Code integration
const simpleLogger = createLogger({
name: 'SimpleComponent'
// outputChannel defaults to false, so uses console.log fallback
});
// Get log contents for a specific channel
const logResult = await getLogContentsForChannel('MyExtension', context); // context required
if (logResult.success) {
console.log('Log contents:', logResult.contents);
}
// Get log contents for the default 'Extension' channel
const defaultLogResult = await getLogContents(context); // context requiredVS Code LogOutputChannel Integration
When outputChannel: true is set and the library is running in a VS Code environment, logs are sent to VS Code's native LogOutputChannel. This provides:
- Structured logging with proper log levels
- Built-in filtering by log level in VS Code's Output panel
- Timestamps and formatting handled by VS Code
- Performance optimized logging
const logger = new Logger({
name: 'MyExtension',
outputChannel: true
});
// Show the output channel in VS Code
logger.show();
// Logs appear with proper formatting and colors in VS Code's Output panel
logger.error('This appears in red');
logger.warn('This appears in yellow');
logger.info('This appears in blue');
logger.debug('This appears in gray');
logger.trace('This appears in light gray');Log Contents Retrieval
This library provides functionality to retrieve the persisted log file contents from VS Code's output channels. This is useful for debugging, exporting logs, or implementing custom log viewers within your extension.
Basic Log Contents Access
import { Logger, getLogContents, getLogContentsForChannel } from '@timheuer/vscode-ext-logger';
// In your extension's activate function
export function activate(context: vscode.ExtensionContext) {
const logger = new Logger({ name: 'MyExtension', outputChannel: true, context });
// Log some messages
logger.info('Application started');
logger.warn('Configuration issue detected');
logger.error('Failed to load data');
// Get log contents for this logger instance (uses stored context)
const result = await logger.getLogContents();
if (result.success) {
console.log('Log file path:', result.filePath);
console.log('Log contents:', result.contents);
} else {
console.error('Failed to read logs:', result.error);
}
}Static Method for Any Channel
// Get log contents for any channel by name (context required)
const result = await Logger.getLogContentsForChannel('SomeOtherExtension', context);
if (result.success) {
const lines = result.contents.split('\n');
const errorLines = lines.filter(line => line.includes('[ERROR]'));
console.log(`Found ${errorLines.length} errors in the log`);
}Factory Functions and Default Logger
// Get log contents for a specific channel name (context required)
const specificResult = await getLogContentsForChannel('MyExtension', context);
// Get log contents for the default 'Extension' channel (context required)
const defaultResult = await getLogContents(context);LogContentsResult Interface
All log contents methods return a LogContentsResult object:
interface LogContentsResult {
success: boolean; // Whether the operation succeeded
contents?: string; // The log file contents (if successful)
error?: string; // Error message (if failed)
filePath?: string; // Path to the log file (if available)
}Usage Examples
Error Handling and Analysis
async function analyzeLogErrors() {
const result = await logger.getLogContents();
if (result.success && result.contents) {
const lines = result.contents.split('\n');
const errors = lines.filter(line => line.includes('[ERROR]'));
const warnings = lines.filter(line => line.includes('[WARN]'));
console.log(`Log analysis for ${result.filePath}:`);
console.log(`- Total lines: ${lines.length}`);
console.log(`- Errors: ${errors.length}`);
console.log(`- Warnings: ${warnings.length}`);
return { errors, warnings, totalLines: lines.length };
} else {
console.warn('Could not analyze logs:', result.error);
return null;
}
}Export Logs for Support
async function exportLogsForSupport() {
const result = await getLogContentsForChannel('MyExtension');
if (result.success) {
// Create a support bundle with relevant information
const supportData = {
timestamp: new Date().toISOString(),
logFilePath: result.filePath,
logContents: result.contents,
extensionVersion: '1.0.0', // Your extension version
vscodeVersion: vscode.version
};
// Save or send to support
const supportBundle = JSON.stringify(supportData, null, 2);
return supportBundle;
}
return null;
}VS Code Command Integration
// In your extension's activate function - RECOMMENDED APPROACH
export function activate(context: vscode.ExtensionContext) {
// Pass the context to enable accurate log file access via context.logUri
const logger = new Logger({
name: 'MyExtension',
outputChannel: true,
context: context // This enables proper log file location detection
});
// Register a command to show log contents
const showLogsCommand = vscode.commands.registerCommand('myExtension.showLogs', async () => {
const result = await logger.getLogContents(); // Uses context.logUri automatically
if (result.success) {
// Show in a new editor window
const doc = await vscode.workspace.openTextDocument({
content: result.contents,
language: 'log'
});
await vscode.window.showTextDocument(doc);
} else {
vscode.window.showErrorMessage(`Unable to load logs: ${result.error}`);
}
});
context.subscriptions.push(showLogsCommand);
// Alternative: Use standalone functions with context
const exportLogsCommand = vscode.commands.registerCommand('myExtension.exportLogs', async () => {
const result = await getLogContents(context); // Pass context explicitly
if (result.success) {
// Save logs to a file or export for support
console.log('Log file location:', result.filePath);
}
});
context.subscriptions.push(exportLogsCommand);
}Log File Locations
The library uses VS Code's context.logUri to access log files. Extension context is required for log contents functionality.
Extension Context Required
Pass the context parameter (VS Code's ExtensionContext) to enable log file access:
// ✅ REQUIRED: Pass context for log file access
const logger = new Logger({
name: 'MyExtension',
outputChannel: true,
context: context // VS Code ExtensionContext
});
// Or with factory functions
const logger = createLoggerFromConfig('MyExtension', 'myExt', 'logLevel', 'info', true, context);
// Or with standalone functions
const result = await getLogContents(context);No Fallback Path Detection
The library does not attempt to guess log file locations. If context.logUri is not available, log contents methods will return an error. This keeps the implementation simple and reliable.
// ❌ Will fail without context
const logger = new Logger({ name: 'MyExt' });
const result = await logger.getLogContents();
// Returns: { success: false, error: 'Extension context with logUri is required...' }
// ✅ Works with context
const logger = new Logger({ name: 'MyExt', context });
const result = await logger.getLogContents(); // Uses context.logUriRequirements
- VS Code Environment Required: Log contents retrieval only works within VS Code extensions
- Extension Context Required: Must pass
contextparameter withlogUriproperty - Output Channel Required: The logger must use an output channel (
outputChannel: true) - File Permissions: Requires read access to VS Code's log directory (handled by
context.logUri)
Environment Compatibility
This library works both inside and outside VS Code environments:
- Inside VS Code: Uses LogOutputChannel for structured logging with proper formatting and filtering
- Outside VS Code: Falls back to console.log for basic logging (graceful degradation)
When outputChannel: true is set:
- In VS Code: Creates and uses a LogOutputChannel
- Outside VS Code: Falls back to console.log with
[LoggerName]prefix
Advanced Usage
Disable Logging Completely
logger.setLevel(LogLevel.Off);
// No logs will be output regardless of method calledStructured Logging with Objects
logger.info('User action completed', {
userId: '123',
action: 'file_save',
duration: 150
});
// Output: User action completed {"userId":"123","action":"file_save","duration":150}Multiple Loggers for Different Components
const apiLogger = createLogger({ name: 'API', level: LogLevel.Debug });
const uiLogger = createLogger({ name: 'UI', level: LogLevel.Info });
apiLogger.debug('API request started'); // Shows in VS Code with [API] prefix
uiLogger.info('UI component mounted'); // Shows in VS Code with [UI] prefixDevelopment
# Install dependencies
npm install
# Set version from Git versioning
npm run setversion
# Build the library (automatically updates version from Git)
npm run build
# Full build with linting and tests
npm run build:full
# Run tests (located in /test directory)
npm test
# Run tests in watch mode
npm run test:watch
# Lint code
npm run lint
# Fix linting issues
npm run lint:fix
# Watch for changes and run tests
npm run watchVersioning
This project uses Nerdbank.GitVersioning for automatic semantic versioning based on Git commits and tags, integrated via the npm package and Gulp tasks.
Version Management
- Development builds: Automatically get versions based on Git history (e.g.,
0.1.0) - Release builds: Created from
mainbranch get stable versions - Version synchronization: Gulp task automatically updates
package.jsonfrom Git version info
Build Process
The build process uses Gulp tasks that integrate with the nerdbank-gitversioning npm package:
gulp setversion- Updates package.json version from Gitgulp build- Sets version and builds the librarygulp build:full- Runs linting, tests, then builds
Release Process
# Prepare a release (creates release branch and updates version)
npm run release
# Or manually:
nbgv prepare-release
git push origin main --follow-tagsThe version in package.json is automatically managed by Gulp tasks using the nbgv npm package.
CI/CD Pipeline
This project includes comprehensive GitHub Actions workflows for automated building, testing, and publishing:
Workflow Overview
- CI/CD Pipeline (
.github/workflows/ci-cd.yml)- ✅ Build & Test: Runs on all PRs and pushes to main/develop
- 📦 Publish to NPM: Triggered on GitHub releases
- 🚀 Preview Releases: Beta versions published from develop branch
Required GitHub Secrets
To enable NPM publishing, add these secrets to your GitHub repository:
NPM_TOKEN- Your NPM authentication token# Generate an NPM token with 'publish' permissions npm login npm token create
Project Structure
├── src/
│ └── index.ts # Main library code
├── test/ # Test directory
│ └── index.test.ts # Test suite
├── dist/ # Built files (CJS + ESM)
├── package.json # Package configuration
├── version.json # Nerdbank.GitVersioning config
├── gulpfile.js # Gulp build tasks
├── tsconfig.json # TypeScript config
├── jest.config.js # Jest test config
├── .eslintrc.js # ESLint config
├── .prettierrc # Prettier config
└── README.md # DocumentationContributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see the LICENSE file for details.
