This document describes RangeLink's structured logging approach, message format, and code organization strategy.
RangeLink uses structured logging with stable message codes to enable:
- Future i18n support - Decouple message identification from formatting
- Easier debugging - Unique codes make issues searchable and trackable
- Consistent UX - Users can rely on stable error identifiers
- Better tooling - Codes enable automated error tracking and analytics
All logged messages follow this format:
[LEVEL] [CODE] message
Components:
[LEVEL]- Log level (INFO, WARN, ERROR, CRITICAL)[CODE]- Stable message code (MSG_xxxx, ERR_xxxx, WARN_xxxx)message- Human-readable description
[INFO] [MSG_1001] Configuration loaded: line="L", column="C", hash="#", range="-"
[WARN] [WARN_2001] Position delimiter not in BYOD metadata. Used local setting 'C' to parse link.
[ERROR] [ERR_1005] Invalid delimiterLine value "L~" (reserved character '~')
[CRITICAL] [ERR_1099] CRITICAL: Unknown validation error for delimiterLine value "L?" (error type: INVALID_ERROR_VALUE). This indicates a bug in validation logic.
Purpose: Non-error informational messages When to use: Configuration loaded, defaults applied, feature usage Examples:
- Configuration loaded successfully
- Using default delimiter configuration
- Link copied to clipboard
Purpose: Warnings that don't prevent operation When to use: Recoverable errors, deprecated features, non-ideal conditions Examples:
- BYOD missing position delimiter (recoverable)
- Extra delimiter in BYOD metadata (ignored)
- Configuration validation warning
Purpose: Errors that prevent operation or trigger fallback When to use: Validation failures, parsing errors, operation failures Examples:
- Invalid delimiter configuration
- BYOD parsing failure
- File not found
Purpose: Defensive logging for unexpected errors (indicates bugs) When to use: Catch-all for unknown errors, assertion failures Examples:
- Unknown validation error (ERR_1099)
- Unexpected exception in core logic
- State inconsistency detected
Note: CRITICAL logs should never occur in production. If logged, they indicate a bug.
RangeLink separates errors from messages:
-
RangeLinkErrorCodes (Error Handling):
- Location:
packages/rangelink-core-ts/src/errors/RangeLinkErrorCodes.ts - Contains: Error codes WITHOUT prefixes (no
ERR_, noWARN_) - Values: Descriptive strings (same as keys) for log clarity
- Pattern:
SELECTION_EMPTY = 'SELECTION_EMPTY'(followsSharedErrorCodes) - Purpose: Exception handling with structured error objects
- Principle: If defined here, it's an error. Warning is a logging level.
- Usage:
throw new RangeLinkError({ code: RangeLinkErrorCodes.SELECTION_EMPTY, ... })
- Location:
-
RangeLinkMessageCode (Informational Logging):
- Location:
packages/rangelink-core-ts/src/types/RangeLinkMessageCode.ts - Contains: ONLY
MSG_xxxxcodes - Purpose: Informational messages for i18n logging
- Usage:
logger.info(RangeLinkMessageCode.MSG_CONFIG_LOADED, 'Configuration loaded successfully')
- Location:
Key principle: Logging level is independent of error type. You can catch an error and log it at any level (INFO, WARN, ERROR) based on context.
Informational message codes organized by functional area:
| Range | Category | Description |
|---|---|---|
MSG_1xxx |
Configuration | Configuration status messages |
MSG_2xxx |
BYOD Parsing | BYOD parsing status messages |
MSG_3xxx |
Selection | Selection processing messages |
| Range | Category | Description |
|---|---|---|
MSG_4xxx |
Navigation | Link navigation messages |
MSG_5xxx |
Multi-Range | Multi-range link messages |
Message codes for informational logging are defined in RangeLinkMessageCode.ts:
/**
* Informational message codes for logging and i18n.
* Contains ONLY MSG_xxxx codes.
* Organized by category (1xxx = Configuration, 2xxx = BYOD, etc.)
*/
export enum RangeLinkMessageCode {
// Configuration messages (1xxx)
CONFIG_LOADED = 'MSG_1001',
CONFIG_USING_DEFAULTS = 'MSG_1002',
// ... more MSG_ codes
// BYOD parsing messages (2xxx)
BYOD_PARSED_SUCCESSFULLY = 'MSG_2001',
// ... more MSG_ codes
// Selection messages (3xxx)
SELECTION_PROCESSED = 'MSG_3001',
// ... more MSG_ codes
}Error codes for exception handling are defined in RangeLinkErrorCodes.ts:
/**
* Error codes for exception handling.
* No prefixes: if defined here, it's an error.
* Warning is a logging level, not an error type.
* Values are descriptive strings (same as keys) for log clarity.
*/
export enum RangeLinkSpecificCodes {
// Configuration errors
CONFIG_DELIMITER_EMPTY = 'CONFIG_DELIMITER_EMPTY',
CONFIG_DELIMITER_INVALID = 'CONFIG_DELIMITER_INVALID',
// ... more codes
// BYOD parsing errors
BYOD_INVALID_FORMAT = 'BYOD_INVALID_FORMAT',
// ... more codes
// Selection validation errors
SELECTION_EMPTY = 'SELECTION_EMPTY',
// ... more codes
}
export type RangeLinkErrorCodes = RangeLinkSpecificCodes | SharedErrorCodes;Use RangeLinkErrorCodes when:
- Throwing errors in internal functions
- Returning errors in Result types
- Need structured error context (details, functionName, cause)
- Building error objects for programmatic handling
Use RangeLinkMessageCode when:
- Logging informational messages to output channel
- Preparing non-error messages for i18n translation
- Displaying status updates to users
- Tracking successful operations
Error handling (RangeLinkErrorCodes):
import { RangeLinkError, RangeLinkErrorCodes } from 'rangelink-core-ts';
// Throw structured error
throw new RangeLinkError({
code: RangeLinkErrorCodes.SELECTION_EMPTY,
message: 'Selections array must not be empty',
functionName: 'validateInputSelection',
details: { selectionsLength: 0 },
});
// Return error in Result
return Err(
new RangeLinkError({
code: RangeLinkErrorCodes.CONFIG_DELIMITER_EMPTY,
message: 'Delimiter cannot be empty',
}),
);Logger interface (RangeLinkMessageCode):
interface Logger {
info(code: RangeLinkMessageCode, message: string): void;
warn(code: RangeLinkMessageCode, message: string): void;
error(code: RangeLinkMessageCode, message: string): void;
critical(code: RangeLinkMessageCode, message: string): void;
}Example usage:
import { RangeLinkMessageCode } from './types/RangeLinkMessageCode';
// Configuration loaded
logger.info(
RangeLinkMessageCode.CONFIG_LOADED,
`Configuration loaded: line="${config.delimiterLine}", column="${config.delimiterPosition}"`,
);
// Validation error
logger.error(
RangeLinkMessageCode.CONFIG_ERR_DELIMITER_EMPTY,
`Invalid delimiterLine value "" (empty string not allowed)`,
);
// BYOD warning
logger.warn(
RangeLinkMessageCode.BYOD_WARN_POSITION_FROM_LOCAL,
`Position delimiter not in BYOD metadata. Used local setting '${localDelimiter}' to parse link.`,
);
// Critical error (defensive)
logger.critical(
RangeLinkMessageCode.CONFIG_ERR_UNKNOWN,
`CRITICAL: Unknown validation error for delimiterLine value "${value}" (error type: ${errorType}). This indicates a bug in validation logic.`,
);Guidelines:
- Be specific - Include relevant values and context
- Be actionable - Suggest how to fix the issue when possible
- Be concise - Keep messages short and focused
- Use quotes - Quote delimiter values for clarity:
"L", notL - Include context - Mention which delimiter or field failed validation
Good examples:
Invalid delimiterLine value "L~" (reserved character '~')
Position delimiter not in BYOD metadata. Used local setting 'C' to parse link.
Configuration loaded: line="L", column="C", hash="#", range="-"
Bad examples:
Invalid delimiter ← Too vague
delimiterLine failed validation ← Missing value and reason
Error ← No context
Configuration validation errors:
Invalid <fieldName> value "<value>" (<reason>)
BYOD parsing errors:
<operation> failed: <reason>
Recovery warnings:
<issue>. <recovery-action>.
Critical errors:
CRITICAL: <unexpected-condition>. This indicates a bug in <component>.
All logs are written to the RangeLink output channel in VSCode.
Via UI:
- Open Output panel: View > Output (
Ctrl+Shift+U/Cmd+Shift+U) - Select "RangeLink" from dropdown
Via Command Palette:
- Press
Ctrl+Shift+P/Cmd+Shift+P - Search "Output: Show Output Channels"
- Select "RangeLink"
Logger implementation (VSCode extension):
import * as vscode from 'vscode';
class OutputChannelLogger implements Logger {
private outputChannel: vscode.OutputChannel;
constructor() {
this.outputChannel = vscode.window.createOutputChannel('RangeLink');
}
info(code: RangeLinkMessageCode, message: string): void {
this.outputChannel.appendLine(`[INFO] [${code}] ${message}`);
}
warn(code: RangeLinkMessageCode, message: string): void {
this.outputChannel.appendLine(`[WARN] [${code}] ${message}`);
}
error(code: RangeLinkMessageCode, message: string): void {
this.outputChannel.appendLine(`[ERROR] [${code}] ${message}`);
}
critical(code: RangeLinkMessageCode, message: string): void {
this.outputChannel.appendLine(`[CRITICAL] [${code}] ${message}`);
this.outputChannel.show(); // Auto-show on critical errors
}
}The structured logging approach with message codes enables future internationalization (i18n) support:
Current approach:
logger.error(
RangeLinkMessageCode.CONFIG_ERR_DELIMITER_EMPTY,
`Invalid delimiterLine value "" (empty string not allowed)`,
);Future approach (with i18n):
logger.error(
RangeLinkMessageCode.CONFIG_ERR_DELIMITER_EMPTY,
i18n.formatMessage(RangeLinkMessageCode.CONFIG_ERR_DELIMITER_EMPTY, {
fieldName: 'delimiterLine',
value: '',
}),
);Translation files:
{
"ERR_1002": {
"en": "Invalid {fieldName} value \"{value}\" (empty string not allowed)",
"fr": "Valeur {fieldName} invalide \"{value}\" (chaîne vide non autorisée)",
"es": "Valor {fieldName} inválido \"{value}\" (cadena vacía no permitida)"
}
}- Phase 1 (Current): Use message codes with English messages
- Phase 2: Extract message templates to separate file
- Phase 3: Add i18n library and translation files
- Phase 4: Replace inline messages with
i18n.formatMessage()calls
Advantage: Message codes are stable identifiers, so translations can be added without changing code.
Tests should verify:
- Correct code - Message uses expected
RangeLinkMessageCode - Correct level - INFO, WARN, ERROR, or CRITICAL
- Message content - Includes relevant values and context
Example test:
it('logs error with code ERR_1002 when delimiter is empty', () => {
const logger = new MockLogger();
const validator = new DelimiterValidator(logger);
validator.validate({ delimiterLine: '' });
expect(logger.getLastError()).toMatchObject({
code: RangeLinkMessageCode.CONFIG_ERR_DELIMITER_EMPTY,
level: 'ERROR',
message: expect.stringContaining('empty string not allowed'),
});
});Test implementation can use a mock logger:
class MockLogger implements Logger {
private logs: Array<{ level: string; code: string; message: string }> = [];
info(code: RangeLinkMessageCode, message: string): void {
this.logs.push({ level: 'INFO', code, message });
}
warn(code: RangeLinkMessageCode, message: string): void {
this.logs.push({ level: 'WARN', code, message });
}
error(code: RangeLinkMessageCode, message: string): void {
this.logs.push({ level: 'ERROR', code, message });
}
critical(code: RangeLinkMessageCode, message: string): void {
this.logs.push({ level: 'CRITICAL', code, message });
}
getLogs(): typeof this.logs {
return this.logs;
}
getLastLog(): (typeof this.logs)[0] | undefined {
return this.logs[this.logs.length - 1];
}
getLastError(): (typeof this.logs)[0] | undefined {
return [...this.logs].reverse().find((log) => log.level === 'ERROR');
}
clear(): void {
this.logs = [];
}
}- Always use message codes - Never log without a code
- Use appropriate level - INFO for success, WARN for recoverable, ERROR for failures
- Include context - Add relevant values to message
- Be consistent - Use similar phrasing for similar messages
- Document new codes - Add to RangeLinkMessageCode enum with comment
- Create output channel early - Initialize logger at extension activation
- Use logger abstraction - Don't call
console.log()directly - Show channel on critical errors - Call
outputChannel.show()for CRITICAL level - Test log output - Verify codes and messages in tests
- Write for developers - Audience is developers debugging issues
- Include actionable info - Help user understand what went wrong and how to fix it
- Use quotes for values - Make it clear what the actual value was
- Be specific about fields - Mention which delimiter or field failed
- Explain recovery - If fallback occurs, explain what happened
See ERROR-HANDLING.md for complete list of all message codes, their meanings, and recovery strategies.
Configuration Messages (1xxx):
MSG_1001- CONFIG_LOADEDMSG_1002- CONFIG_USING_DEFAULTSERR_1001- CONFIG_ERR_DELIMITER_INVALIDERR_1002- CONFIG_ERR_DELIMITER_EMPTYERR_1003- CONFIG_ERR_DELIMITER_DIGITSERR_1004- CONFIG_ERR_DELIMITER_WHITESPACEERR_1005- CONFIG_ERR_DELIMITER_RESERVEDERR_1006- CONFIG_ERR_DELIMITER_NOT_UNIQUEERR_1007- CONFIG_ERR_DELIMITER_SUBSTRING_CONFLICTERR_1008- CONFIG_ERR_HASH_NOT_SINGLE_CHARERR_1099- CONFIG_ERR_UNKNOWN
BYOD Parsing Messages (2xxx):
ERR_2001- BYOD_ERR_INVALID_FORMATERR_2002- BYOD_ERR_HASH_INVALIDERR_2003- BYOD_ERR_DELIMITER_VALIDATIONERR_2004- BYOD_ERR_FORMAT_MISMATCHERR_2005- BYOD_ERR_POSITION_RECOVERY_FAILEDERR_2006- BYOD_ERR_RECTANGULAR_MODE_DETECTIONWARN_2001- BYOD_WARN_POSITION_FROM_LOCALWARN_2002- BYOD_WARN_POSITION_FROM_DEFAULTWARN_2003- BYOD_WARN_EXTRA_DELIMITER
- ERROR-HANDLING.md - Complete error code reference and validation rules
- BYOD.md - BYOD format specification and parsing logic
- ARCHITECTURE.md - Core library design including logger interface
Success:
[INFO] [MSG_1001] Configuration loaded: line="L", column="C", hash="#", range="-"
Fallback to defaults:
[ERROR] [ERR_1005] Invalid delimiterLine value "L~" (reserved character '~')
[ERROR] [ERR_1003] Invalid delimiterPosition value "C1" (cannot contain digits)
[INFO] [MSG_1002] Using default delimiter configuration: line="L", column="C", hash="#", range="-"
Missing position delimiter (recovery):
[WARN] [WARN_2001] Position delimiter not in BYOD metadata. Used local setting 'C' to parse link.
Invalid delimiter (error):
[ERROR] [ERR_2003] Invalid BYOD line delimiter "L1" (cannot contain digits)
Format mismatch (error):
[ERROR] [ERR_2004] BYOD format mismatch: link has columns but metadata missing position delimiter
Unknown validation error:
[CRITICAL] [ERR_1099] CRITICAL: Unknown validation error for delimiterLine value "L?" (error type: INVALID_ERROR_VALUE). This indicates a bug in validation logic.
Version: 0.1.0 Last Updated: 2025-01-31 Status: Current specification for RangeLink v0.1.0