packages/docs/development/logging.md
Nuclear uses Tauri's log plugin for unified Rust + TypeScript logging. All logs go to stdout, log files on disk, and the in-app log viewer (via webview forwarding).
Each platform has its own log directory.
| Level | When to use | Examples |
|---|---|---|
| error | Something failed that the user cares about | Stream resolution failed, plugin crash, file write failed |
| warn | Something unexpected but recoverable. Or something you should fix, because it will start failing in the future | Retrying requests, fallback used, deprecated API called |
| info | User-initiated actions | Plugin enabled, theme changed, track started playing |
| debug | Anything that's useful for debugging | HTTP request/response details, state transitions, timing |
| trace | Extremely verbose, rarely needed | Function entry/exit |
Production default: info and above
Dev builds: debug
You can enable trace for yourself locally, but it's not used anywhere by default.
The Logger service provides scoped loggers. Import from @/services/logger.
Available scopes: app, playback, streaming, plugins, http, fs, settings, themes, updates, queue, metadata
import { Logger } from '@/services/logger';
Logger.playback.info('Track started playing');
Logger.streaming.error('Failed to resolve stream');
Logger.plugins.debug('Loading plugin manifest');
All methods are async (return promises) but can be fire-and-forget (no need to await).
Each scope automatically prefixes messages with [scope]. For example, Logger.plugins.info('Loaded') produces [plugins] Loaded in the log output.
Use formatLogValue() to format JS objects, arrays, and Errors into log-safe strings. It's a DWIM (Do What I Mean) utility. Whatever you throw its way, it'll handle it.
The reportError utility logs the full error and shows a clean toast. Import from @/utils/logging.
import { reportError } from '@/utils/logging';
reportError('plugins', {
userMessage: 'Failed to load plugin',
error,
});
userMessage appears in the toast titleerror is a caught error (from catch block); the error message is truncated to 100 chars for the toast descriptionPlugins use api.Logger (provided by the plugin SDK). All methods are synchronous and fire-and-forget.
const plugin: NuclearPlugin = {
onLoad(api: NuclearPluginAPI) {
api.Logger.info('Plugin loaded');
api.Logger.debug('Initializing providers...');
},
onEnable(api: NuclearPluginAPI) {
api.Logger.info('Plugin enabled');
},
};
Available methods: trace(), debug(), info(), warn(), error()
Logs from plugins are automatically prefixed with [plugin:plugin-id]. Plugin authors don't need to add any prefix.
Plugin debug logs are always captured, even in production. This is intentional - plugin issues are the hardest to debug remotely.
Don't try being cute with the plugin ID. It's tamper-resistant, and I know where you live.
For Rust code, use the standard log crate macros (trace!, debug!, info!, warn!, error!).
Use the target parameter to identify the source:
use log::{debug, error};
debug!(target: "http", "GET {} -> {}", url, status);
error!(target: "http", "Request failed: {}", err);
Rust HTTP logging already includes:
The bundle identifier is com.nuclearplayer.
| Platform | Location |
|---|---|
| macOS | ~/Library/Logs/com.nuclearplayer/ |
| Linux | ~/.local/share/com.nuclearplayer/logs/ |
| Windows | %APPDATA%\com.nuclearplayer\logs\ |
Log files:
Users can access log files via the "Open Log Folder" button in the Logs view, or navigate to the directory manually.
Nuclear has a built-in log viewer accessible from the sidebar.