Skip to content

Building Logging in a Tauri App

Introduction

Logging is a critical part of any application. It helps with debugging, monitoring behavior, and understanding runtime issues. In this article, the focus is on configuring logging in a Tauri application and wiring it into both the Rust side and the JavaScript side.

Configure logging

First, add the log crate to the Rust project:

toml
[dependencies]
tauri = { version = "1", features = ["shell-open"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4.21"

The log crate provides logging macros such as:

  • log::info!()
  • log::error!()
  • and others

By itself, log only defines the logging interface. It still needs a logger implementation behind it.

Tauri has a community-maintained logging plugin that makes it much easier to collect logs from both Rust and JavaScript and send them to common log targets.

Install the Tauri logging plugin

Add this to Cargo.toml:

toml
[dependencies]
tauri = { version = "1", features = ["shell-open"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4.21"
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }

Then install the JavaScript package:

bash
pnpm add https://github.com/tauri-apps/tauri-plugin-log#v1
# or
npm add https://github.com/tauri-apps/tauri-plugin-log#v1
# or
yarn add https://github.com/tauri-apps/tauri-plugin-log#v1

Configure the logger in main.rs

rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri_plugin_log::LogTarget;

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

fn main() {
    tauri::Builder::default()
        .plugin(
            tauri_plugin_log::Builder::default()
                .targets([LogTarget::Stdout, LogTarget::Webview])
                .build()
        )
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

LogTarget controls where logs are written:

  • LogTarget::Stdout: terminal / console
  • LogTarget::Webview: developer console in the Tauri window
  • LogTarget::LogDir: OS-specific log directory

The OS-specific log directory depends on the application identifier:

OSFormatExample
macOS{homeDir}/Library/Logs/{bundleIdentifier}/Users/Alice/Library/Logs/com.tauri.dev
Linux{configDir}/{bundleIdentifier}/home/alice/.config/com.tauri.dev
Windows{configDir}/{bundleIdentifier}C:\Users\Alice\AppData\Roaming\com.tauri.dev

Writing logs

On the Rust side, replace plain console output with log::info!():

rust
fn change_menu_language(config: tauri::State<'_, Config>, lang: &str) {
    log::info!("change_menu_language: {}", lang);
    if let Err(e) = write_data_to_file(&config, lang) {
        eprintln!("Error writing file: {}", e);
    }
}

To see logs in the WebView console, call attachConsole() during frontend initialization:

typescript
import ReactDOM from "react-dom/client";
import { I18nextProvider } from "react-i18next";
import { attachConsole } from "tauri-plugin-log-api";
import i18n from "../src-tauri/locales/index";
import App from "./App";

attachConsole();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <I18nextProvider i18n={i18n}>
    <App />
  </I18nextProvider>
);

Then use logging on the frontend like this:

typescript
import { info } from "tauri-plugin-log-api";

const changeLangAction = async (lang: string) => {
  info("changeLangAction in render");
  setStore("lang", lang);
  setCurrentLang(lang);
  i18n.changeLanguage(lang);
  if (IS_TAURI) {
    invoke("change_menu_language", { lang }).then(() => {
      relaunch();
    });
  }
};

Illustration of Tauri log output inside the console panel

Set the log level

If you log everything, especially trace-level output, the console becomes noisy. In most cases, it is better to keep only Info and above.

Add LevelFilter::Info:

rust
use log::LevelFilter;

.plugin(
    tauri_plugin_log::Builder::default()
        .targets([LogTarget::LogDir, LogTarget::Stdout, LogTarget::Webview])
        .level(LevelFilter::Info)
        .build()
)

After that, trace-level messages disappear.

One caveat: you may still see some trace logs very early in startup, before the logger plugin has finished initialization.

Closing

Logging in Tauri is straightforward and very worthwhile. By combining log with tauri-plugin-log, you can build a clean logging path that works across both Rust and JavaScript.

For client-side desktop development, consistent logging improves stability, debugging speed, and long-term maintainability.

Source code

https://github.com/Xutaotaotao/XTools/tree/feature-log

Last updated: