@rescript-tauri/plugin-log¶
ReScript bindings for the Tauri 2.x logging
plugin — five log levels
(error / warn / info / debug / trace) plus log-stream
subscription via attachLogger and attachConsole. The 100%
stable public surface of @tauri-apps/plugin-log v2.8.x is
covered.
Note
This package is feature-complete in main. Its first npm publish is scheduled alongside the other packages. Until then, consume it via the source repository or a workspace link.
Install¶
pnpm add @rescript-tauri/plugin-log @tauri-apps/plugin-log
@rescript-tauri/plugin-log declares both
@rescript-tauri/core and @tauri-apps/plugin-log as
peerDependencies, so you control each upstream version.
Add the package to dependencies in your rescript.json:
{
"dependencies": [
"@rescript-tauri/core",
"@rescript-tauri/plugin-log"
]
}
On the Rust side, add the plugin crate and register it on the
builder. tauri_plugin_log::Builder lets you select which sinks
(stdout, the webview console, a rotating log file) receive
records and the global level filter:
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-log = "2"
log = "0.4"
// src-tauri/src/main.rs
use tauri_plugin_log::{Target, TargetKind};
fn main() {
tauri::Builder::default()
.plugin(
tauri_plugin_log::Builder::new()
.targets([
Target::new(TargetKind::Stdout),
Target::new(TargetKind::Webview),
Target::new(TargetKind::LogDir { file_name: None }),
])
.level(log::LevelFilter::Info)
.build(),
)
.run(tauri::generate_context!())
.expect("error while running app");
}
The three TargetKind variants above are the most common combo:
Stdout prints to the host terminal, Webview forwards each
record so attachConsole can mirror it in the JS console, and
LogDir writes to a platform-appropriate $APPLOG/<bundle>.log
file.
Capabilities¶
Tauri 2.x requires every plugin permission to be granted explicitly. The minimal set for logging is:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"log:default"
]
}
log:default covers every log API surfaced by this binding
(level functions, attach helpers, and the underlying
plugin:log|log IPC command).
Minimal example¶
open RescriptTauriPluginLog
let bootstrap = async () => {
let _unlisten = await PluginLog.attachConsole()
await PluginLog.info(
"App started",
~options={file: "Main.res", line: 1},
)
}
attachConsole subscribes a JS-console writer to the log stream
(useful while developing in a webview that doesn’t show stdout).
The returned unlisten is a unit => unit callback — invoke it
to detach the subscription.
Public API¶
All seven functions are exposed under PluginLog, together with
the numeric-enum module LogLevel:
Symbol |
Purpose |
|---|---|
|
Emit a record at the given level |
|
Subscribe a callback to every record |
|
Forward every record to the JS console |
|
|
|
Optional metadata record passed to a log call |
|
|
|
|
Level functions¶
Each of the five level functions has the same shape:
let error: (string, ~options: logOptions=?) => promise<unit>
let warn: (string, ~options: logOptions=?) => promise<unit>
let info: (string, ~options: logOptions=?) => promise<unit>
let debug: (string, ~options: logOptions=?) => promise<unit>
let trace: (string, ~options: logOptions=?) => promise<unit>
The labeled ~options argument is optional. When provided it
attaches per-call metadata that the Rust side records alongside
the message:
await PluginLog.warn(
"queue draining slowly",
~options={
file: "Worker.res",
line: 42,
keyValues: Dict.fromArray([
("queue", "ingest"),
("backlog", "1872"),
]),
},
)
logOptions fields:
Field |
Type |
Notes |
|---|---|---|
|
|
Source file the call originated from |
|
|
Source line number |
|
|
Free-form structured fields appended to the record |
@rescript/core’s Dict.t<string> maps to a plain JS object on
output, which the upstream plugin reads as the structured-fields
payload.
LogLevel.t variant¶
LogLevel exposes the upstream numeric enum as an @unboxed
variant with @as(N) annotations, so the runtime representation is
the bare integer while ReScript code uses constructor names:
PluginLog.LogLevel.Trace // @as(1)
PluginLog.LogLevel.Debug // @as(2)
PluginLog.LogLevel.Info // @as(3)
PluginLog.LogLevel.Warn // @as(4)
PluginLog.LogLevel.Error // @as(5)
Constructor |
Wire value |
|---|---|
|
|
|
|
|
|
|
|
|
|
Use switch over recordPayload.level for exhaustive matching —
the compiler will warn if a case is missed when a new level is
added upstream.
attachLogger / attachConsole¶
let attachLogger: (recordPayload => unit) => promise<unlisten>
let attachConsole: unit => promise<unlisten>
attachLogger runs your callback for every record the Rust
side emits. Pattern-match on record.level:
let unlisten = await PluginLog.attachLogger(record => {
let label = switch record.level {
| Error => "ERROR"
| Warn => "WARN"
| Info => "INFO"
| Debug => "DEBUG"
| Trace => "TRACE"
}
Console.log(label ++ ": " ++ record.message)
})
// ...later
unlisten()
attachConsole is a convenience helper that wires the records to
console.log / console.warn / console.error based on level —
useful for mirroring Rust-side logs in the webview devtools
without writing the dispatcher yourself.
Both functions return a promise<unlisten>. Always await the
promise before treating the subscription as live, and call the
returned unlisten() once when you’re done — multiple listeners
can be attached in parallel but they are not de-duplicated.
Pitfalls¶
Log calls are async — await them¶
The five level functions return promise<unit>, not unit.
Forgetting to await swallows errors silently and can race
against process shutdown:
// ❌ may be dropped if the program exits immediately
let _ = PluginLog.info("starting")
// ✅
await PluginLog.info("starting")
If you do not need to wait for delivery, bind the promise to
_ignore explicitly so the intent is visible at the call site.
attachLogger / attachConsole are not covered by Mocks.mockIPC¶
The two attach helpers subscribe via
__TAURI_INTERNALS__.transformCallback, not the regular IPC
command bridge, so Mocks.mockIPC cannot intercept them. Runtime
tests that exercise log streaming stub
globalThis.__TAURI_INTERNALS__ directly — see
packages/plugin-log/tests/runtime/plugin_log.test.mjs for the
working pattern.
The level functions themselves (error / warn / info /
debug / trace) go through the normal Tauri IPC
(plugin:log|log) and are mockable with Mocks.mockIPC.
Compatibility¶
Component |
Supported range |
|---|---|
Upstream |
|
Rust |
|
|
|
ReScript |
|
|
|
OS |
Linux / macOS / Windows |
See also¶
Live demo:
examples/plugin-log-demoSource:
packages/plugin-logPackage README:
packages/plugin-log/README.mdUpstream docs: Tauri 2.x logging plugin
Upstream JS reference: log module