Skip to content

Instantly share code, notes, and snippets.

@tobiashochguertel
Last active June 16, 2025 15:31
Show Gist options
  • Select an option

  • Save tobiashochguertel/ad34a03523fba5d42c6a1c94c1095cab to your computer and use it in GitHub Desktop.

Select an option

Save tobiashochguertel/ad34a03523fba5d42c6a1c94c1095cab to your computer and use it in GitHub Desktop.
userscript-lib
// ==UserScript==
// @name TH-LoggingLib
// @namespace https://github.com/tobiashochguertel/userscript-lib
// @version 1.0.0
// @description Advanced logging system for UserScripts with level control
// @author tobiashochguertel
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Create global namespace
window.THLib = window.THLib || {};
// Check for storage dependency
const hasStorage = !!window.THLib.storage;
// =====================================
// Core Logging Framework
// =====================================
/**
* Log level enum - ordered by severity
* Higher number = more severe, more logs will be shown
*/
const LogLevel = {
TRACE: 0,
DEBUG: 1,
INFO: 2,
WARN: 3,
ERROR: 4,
NONE: 5,
// Helper to convert string log level to enum value
fromString: function(level) {
const upperLevel = (level || '').toUpperCase();
return this[upperLevel] !== undefined ? this[upperLevel] : this.INFO;
},
// Helper to get level name from value
toString: function(level) {
for (const key in this) {
if (this[key] === level && typeof this[key] !== 'function') {
return key;
}
}
return 'INFO';
}
};
/**
* Initialization logic based on storage dependency
*/
function initializeLogStorage() {
if (hasStorage) {
// If storage is available, create a storage instance
console.log('Using THLib.storage for log configuration persistence');
return window.THLib.storage.create('THLib_Logger');
} else {
// Create a simple in-memory storage fallback
console.warn('THLib.storage not available, using in-memory storage for logging');
const memoryStore = {};
return {
get: (key, defaultValue) => key in memoryStore ? memoryStore[key] : defaultValue,
set: (key, value) => {
memoryStore[key] = value;
return true;
},
remove: (key) => {
delete memoryStore[key];
return true;
},
clear: () => {
Object.keys(memoryStore).forEach(key => delete memoryStore[key]);
return true;
}
};
}
}
// Initialize storage for logger configuration
const logStorage = initializeLogStorage();
/**
* Storage manager for log configuration
*/
const LogConfigStorage = {
// Get full configuration object
getConfig: function() {
return logStorage.get('config', { global: LogLevel.INFO, namespaces: {} });
},
// Save full configuration object
saveConfig: function(config) {
return logStorage.set('config', config);
},
// Get specific namespace level
getNamespaceLevel: function(namespace) {
const config = this.getConfig();
// Return namespace level if defined, or global level as fallback
return config.namespaces[namespace] !== undefined ?
config.namespaces[namespace] :
config.global;
},
// Set specific namespace level
setNamespaceLevel: function(namespace, level) {
const config = this.getConfig();
config.namespaces[namespace] = level;
this.saveConfig(config);
},
// Get global level
getGlobalLevel: function() {
return this.getConfig().global;
},
// Set global level
setGlobalLevel: function(level) {
const config = this.getConfig();
config.global = level;
this.saveConfig(config);
}
};
/**
* Console styling for different log levels
*/
const LogStyles = {
TRACE: 'color: #6c757d', // Grey
DEBUG: 'color: #0d6efd', // Blue
INFO: 'color: #198754', // Green
WARN: 'color: #ffc107; font-weight: bold', // Yellow
ERROR: 'color: #dc3545; font-weight: bold', // Red
// Default style for namespace
NAMESPACE: 'color: #6610f2; font-weight: bold', // Purple
// Style for timestamp
TIMESTAMP: 'color: #6c757d; font-style: italic' // Grey italic
};
/**
* Logger class - provides logging methods for a specific namespace
*/
class Logger {
constructor(namespace) {
this.namespace = namespace || 'default';
}
/**
* Format a log message with timestamp, namespace, and styling
*/
_formatMessage(level, args) {
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
return [
`%c[${timestamp}]%c [${this.namespace}]%c [${level}]:`,
LogStyles.TIMESTAMP,
LogStyles.NAMESPACE,
LogStyles[level],
...args
];
}
/**
* Check if the provided log level is enabled for this namespace
*/
_isLevelEnabled(level) {
const configuredLevel = LogConfigStorage.getNamespaceLevel(this.namespace);
return level >= configuredLevel;
}
/**
* Base log method that all specific level methods call
*/
_log(level, levelStr, args) {
if (this._isLevelEnabled(level)) {
const method = levelStr === 'ERROR' ? 'error' :
levelStr === 'WARN' ? 'warn' : 'log';
console[method](...this._formatMessage(levelStr, args));
}
}
// Public logging methods
trace(...args) {
this._log(LogLevel.TRACE, 'TRACE', args);
}
debug(...args) {
this._log(LogLevel.DEBUG, 'DEBUG', args);
}
info(...args) {
this._log(LogLevel.INFO, 'INFO', args);
}
warn(...args) {
this._log(LogLevel.WARN, 'WARN', args);
}
error(...args) {
this._log(LogLevel.ERROR, 'ERROR', args);
}
/**
* Set log level for this specific namespace
*/
setLevel(level) {
const numericLevel = typeof level === 'string' ?
LogLevel.fromString(level) : level;
LogConfigStorage.setNamespaceLevel(this.namespace, numericLevel);
this.info(`Log level for '${this.namespace}' set to ${LogLevel.toString(numericLevel)}`);
return this;
}
/**
* Get current log level for this namespace
*/
getLevel() {
return LogConfigStorage.getNamespaceLevel(this.namespace);
}
}
/**
* Logger factory that creates and manages logger instances
*/
const LoggerFactory = {
// Cache of logger instances by namespace
_loggers: {},
/**
* Get or create a logger for a specific namespace
*/
getLogger: function(namespace) {
const ns = namespace || 'default';
if (!this._loggers[ns]) {
this._loggers[ns] = new Logger(ns);
}
return this._loggers[ns];
},
/**
* Get all registered loggers
*/
getAllLoggers: function() {
return Object.values(this._loggers);
},
/**
* Set global log level (affects any namespace without specific level)
*/
setGlobalLogLevel: function(level) {
const numericLevel = typeof level === 'string' ?
LogLevel.fromString(level) : level;
LogConfigStorage.setGlobalLevel(numericLevel);
const defaultLogger = this.getLogger('default');
defaultLogger.info(`Global log level set to ${LogLevel.toString(numericLevel)}`);
},
/**
* Get current global log level
*/
getGlobalLogLevel: function() {
return LogConfigStorage.getGlobalLevel();
},
/**
* Set log level for a specific namespace
*/
setNamespaceLogLevel: function(namespace, level) {
const logger = this.getLogger(namespace);
logger.setLevel(level);
},
/**
* Reset all log levels to default
*/
resetAllLogLevels: function() {
LogConfigStorage.saveConfig({ global: LogLevel.INFO, namespaces: {} });
const defaultLogger = this.getLogger('default');
defaultLogger.info('All log levels reset to default');
},
/**
* Display current configuration in console
*/
showConfiguration: function() {
const config = LogConfigStorage.getConfig();
const defaultLogger = this.getLogger('default');
defaultLogger.info('=== THLib Logger Configuration ===');
defaultLogger.info(`Global level: ${LogLevel.toString(config.global)}`);
defaultLogger.info('Namespace levels:');
for (const [namespace, level] of Object.entries(config.namespaces)) {
defaultLogger.info(` - ${namespace}: ${LogLevel.toString(level)}`);
}
defaultLogger.info('=== Active Loggers ===');
for (const namespace of Object.keys(this._loggers)) {
defaultLogger.info(` - ${namespace}`);
}
}
};
// =====================================
// Expose Public API
// =====================================
// Expose logging framework
THLib.logging = {
// Core components
LogLevel,
// Factory methods
getLogger: (namespace) => LoggerFactory.getLogger(namespace),
setGlobalLogLevel: (level) => LoggerFactory.setGlobalLogLevel(level),
setNamespaceLogLevel: (namespace, level) => LoggerFactory.setNamespaceLogLevel(namespace, level),
resetAllLogLevels: () => LoggerFactory.resetAllLogLevels(),
showConfiguration: () => LoggerFactory.showConfiguration(),
// Convenience logger
get defaultLogger() {
return LoggerFactory.getLogger('default');
}
};
// =====================================
// Global Helper Functions
// =====================================
// Set log level globally or for specific namespace
window.THLibSetLogLevel = function(namespace, level) {
if (level === undefined) {
THLib.logging.setGlobalLogLevel(namespace);
return `Global log level set to ${namespace}`;
} else {
THLib.logging.setNamespaceLogLevel(namespace, level);
return `Log level for '${namespace}' set to ${level}`;
}
};
// Show logger configuration
window.THLibShowLogConfig = function() {
THLib.logging.showConfiguration();
return "Logger configuration displayed in console";
};
// Reset all log levels to default
window.THLibResetLogLevels = function() {
THLib.logging.resetAllLogLevels();
return "All log levels reset to default (INFO)";
};
// =====================================
// Initialize the library
// =====================================
function initializeLogging() {
// Create default logger
const defaultLogger = THLib.logging.getLogger('default');
defaultLogger.info(`TH-LoggingLib v1.0.0 initialized ${hasStorage ? 'with' : 'without'} storage integration`);
// Dispatch event to notify that the library is ready
document.dispatchEvent(new CustomEvent('THLib:logging:ready'));
// Output help information to console
console.log(
'%cTH-LoggingLib v1.0.0 %cis ready!\n\n' +
'%cAvailable commands:%c\n' +
' THLibSetLogLevel("DEBUG") Set global log level\n' +
' THLibSetLogLevel("my-script", "TRACE") Set namespace log level\n' +
' THLibShowLogConfig() Show logging configuration\n' +
' THLibResetLogLevels() Reset all logging levels\n\n' +
'%cAPI usage:%c\n' +
' const logger = THLib.logging.getLogger("my-script");\n' +
' logger.debug("Debug message");',
'font-weight:bold;color:#6610f2;font-size:16px',
'color:#333;font-size:16px',
'color:#0d6efd;font-weight:bold',
'color:#333',
'color:#0d6efd;font-weight:bold',
'color:#198754;font-family:monospace'
);
}
// Check if storage is already loaded
if (hasStorage) {
// Storage already available, initialize immediately
initializeLogging();
} else {
// Wait for storage to become available
document.addEventListener('THLib:storage:ready', function() {
// Re-check for storage
if (window.THLib.storage) {
console.log('TH-LoggingLib: Storage dependency now available');
// Re-initialize storage
const logStorage = window.THLib.storage.create('THLib_Logger');
initializeLogging();
} else {
console.warn('TH-LoggingLib: Storage dependency event received but still not available');
initializeLogging();
}
});
// Fallback initialization after delay
setTimeout(function() {
if (!window.THLib.logging) {
console.warn('TH-LoggingLib: Initializing without storage after timeout');
initializeLogging();
}
}, 2000);
}
})();
// ==UserScript==
// @name TH-StorageLib
// @namespace https://github.com/tobiashochguertel/userscript-lib
// @version 1.0.0
// @description Advanced localStorage management for UserScripts
// @author tobiashochguertel
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Create global namespace
window.THLib = window.THLib || {};
/**
* Storage system with namespace support and type safety
*/
class StorageManager {
/**
* @param {string} namespace - Storage namespace to prefix keys
* @param {Storage} storageBackend - Storage backend (localStorage/sessionStorage)
*/
constructor(namespace = 'THLib', storageBackend = localStorage) {
this.namespace = namespace;
this.storage = storageBackend;
this._cache = {};
}
/**
* Generate a namespaced key for storage
* @param {string} key - Original key
* @returns {string} - Namespaced key
*/
_getKey(key) {
return `${this.namespace}:${key}`;
}
/**
* Get a value from storage with type safety
* @param {string} key - Storage key
* @param {any} defaultValue - Default value if not found
* @returns {any} - Retrieved value or default
*/
get(key, defaultValue = null) {
// Check cache first
if (this._cache[key] !== undefined) {
return this._cache[key];
}
try {
const storedValue = this.storage.getItem(this._getKey(key));
if (storedValue === null) return defaultValue;
// Parse the stored value
const value = JSON.parse(storedValue);
// Cache the value
this._cache[key] = value;
return value;
} catch (error) {
console.error(`StorageManager[${this.namespace}]: Error retrieving '${key}'`, error);
return defaultValue;
}
}
/**
* Store a value with type preservation
* @param {string} key - Storage key
* @param {any} value - Value to store
* @returns {boolean} - Success state
*/
set(key, value) {
try {
// Update cache
this._cache[key] = value;
// Store serialized value
this.storage.setItem(this._getKey(key), JSON.stringify(value));
return true;
} catch (error) {
console.error(`StorageManager[${this.namespace}]: Error storing '${key}'`, error);
return false;
}
}
/**
* Remove a value from storage
* @param {string} key - Storage key
* @returns {boolean} - Success state
*/
remove(key) {
try {
// Remove from cache
delete this._cache[key];
// Remove from storage
this.storage.removeItem(this._getKey(key));
return true;
} catch (error) {
console.error(`StorageManager[${this.namespace}]: Error removing '${key}'`, error);
return false;
}
}
/**
* Check if a key exists in storage
* @param {string} key - Storage key
* @returns {boolean} - Whether key exists
*/
has(key) {
if (this._cache[key] !== undefined) return true;
return this.storage.getItem(this._getKey(key)) !== null;
}
/**
* Clear all values in the current namespace
* @returns {boolean} - Success state
*/
clear() {
try {
const keysToRemove = [];
// Clear cache
this._cache = {};
// Find all keys in this namespace
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key && key.startsWith(`${this.namespace}:`)) {
keysToRemove.push(key);
}
}
// Remove all keys
keysToRemove.forEach(key => this.storage.removeItem(key));
return true;
} catch (error) {
console.error(`StorageManager[${this.namespace}]: Error clearing storage`, error);
return false;
}
}
/**
* Get all keys in the current namespace
* @returns {string[]} - Array of keys
*/
keys() {
try {
const keys = [];
const prefix = `${this.namespace}:`;
// Find all keys in this namespace
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key && key.startsWith(prefix)) {
keys.push(key.substring(prefix.length));
}
}
return keys;
} catch (error) {
console.error(`StorageManager[${this.namespace}]: Error listing keys`, error);
return [];
}
}
/**
* Get all data in the current namespace as an object
* @returns {Object} - Object with all data
*/
getAll() {
const data = {};
this.keys().forEach(key => {
data[key] = this.get(key);
});
return data;
}
/**
* Set multiple values at once
* @param {Object} data - Object with key-value pairs to store
* @returns {boolean} - Success state
*/
setMultiple(data) {
try {
Object.entries(data).forEach(([key, value]) => {
this.set(key, value);
});
return true;
} catch (error) {
console.error(`StorageManager[${this.namespace}]: Error setting multiple values`, error);
return false;
}
}
}
/**
* Static utility methods for storage management
*/
class StorageUtils {
/**
* Get all namespaces currently used in storage
* @returns {string[]} - Array of namespaces
*/
static getNamespaces() {
try {
const namespaces = new Set();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const parts = key?.split(':');
if (parts && parts.length > 1) {
namespaces.add(parts[0]);
}
}
return Array.from(namespaces);
} catch (error) {
console.error('StorageUtils: Error getting namespaces', error);
return [];
}
}
/**
* Display storage usage statistics
* @returns {Object} - Storage statistics
*/
static getStorageStats() {
try {
const stats = {
totalItems: localStorage.length,
totalBytes: 0,
namespaces: {},
percentUsed: 0
};
// Calculate total size
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key) || '';
const size = key.length + value.length;
stats.totalBytes += size;
// Group by namespace
const namespaceParts = key.split(':');
const namespace = namespaceParts.length > 1 ? namespaceParts[0] : 'unknown';
if (!stats.namespaces[namespace]) {
stats.namespaces[namespace] = {
items: 0,
bytes: 0
};
}
stats.namespaces[namespace].items++;
stats.namespaces[namespace].bytes += size;
}
// Calculate percentage used (typical browser limit is 5MB)
const limit = 5 * 1024 * 1024;
stats.percentUsed = (stats.totalBytes / limit) * 100;
stats.limit = limit;
return stats;
} catch (error) {
console.error('StorageUtils: Error getting storage stats', error);
return { error: 'Failed to calculate storage statistics' };
}
}
/**
* Clear all data in a namespace
* @param {string} namespace - Namespace to clear
* @returns {boolean} - Success state
*/
static clearNamespace(namespace) {
try {
const storage = new StorageManager(namespace);
return storage.clear();
} catch (error) {
console.error(`StorageUtils: Error clearing namespace ${namespace}`, error);
return false;
}
}
}
// Expose public API
THLib.storage = {
// Core functionality
create: (namespace, backend = localStorage) => new StorageManager(namespace, backend),
// Utility functions
getNamespaces: StorageUtils.getNamespaces,
getStats: StorageUtils.getStorageStats,
clearNamespace: StorageUtils.clearNamespace,
// Default instance
defaultStorage: new StorageManager('THLib_Default')
};
// =====================================
// Global Helper Functions
// =====================================
// Display storage statistics
window.THLibShowStorage = function() {
const stats = THLib.storage.getStats();
// Format size in KB
const formatSize = bytes => (bytes / 1024).toFixed(2) + ' KB';
console.group('THLib Storage Statistics');
console.log(`Total items: ${stats.totalItems}`);
console.log(`Total used: ${formatSize(stats.totalBytes)} (${stats.percentUsed.toFixed(2)}% of 5MB limit)`);
console.group('Namespaces');
Object.entries(stats.namespaces).forEach(([namespace, data]) => {
console.log(`${namespace}: ${data.items} items, ${formatSize(data.bytes)}`);
});
console.groupEnd();
console.groupEnd();
return "Storage statistics displayed in console";
};
// Clear storage for a namespace
window.THLibClearStorage = function(namespace) {
if (!namespace) {
console.error('Please specify a namespace to clear');
return "Error: Namespace required";
}
const cleared = THLib.storage.clearNamespace(namespace);
if (cleared) {
return `Storage for namespace '${namespace}' cleared successfully`;
} else {
return `Failed to clear storage for namespace '${namespace}'`;
}
};
// Initialize
console.log('%cTH-StorageLib v1.0.0 %cinitialized - Storage management ready',
'font-weight:bold;color:#0d6efd', 'color:#333');
// Dispatch ready event for dependent scripts
document.dispatchEvent(new CustomEvent('THLib:storage:ready'));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment