配布したアプリで「動かない」と言われたとき、手がかりになるのはログファイルです。Log プラグインを入れると、Rust の log::info! などと JS の info() などをまとめて、ターミナル・ファイル・開発者ツールへ振り分けて出せます。出力先とレベル、ファイルの場所、既定の設定の落とし穴、env_logger との違いを説明します。
前提条件
npm run tauri add log
tauri add はクレートと npm パッケージを追加し、Info 以上を記録する設定でプラグインを登録し、capabilities に log:default を足します。log:default の中身は JS からログを書く log:allow-log だけで、Rust のログに権限は要りません。3 章でフォルダを開くには、テンプレートに入っている Opener プラグインの opener:default を使います。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"log:default"
]
}
Rust の log::info! などのマクロは、プラグインが再公開している tauri_plugin_log::log から使えます(cargo add log で入れても同じです)。
1. 出力先とレベルを決める (Rust)
TargetKind | 出力先 |
|---|---|
Stdout / Stderr | tauri dev を実行したターミナル |
LogDir { file_name } | OS のログフォルダのファイル(3 章) |
Folder { path, file_name } | 指定したフォルダのファイル |
Webview | 開発者ツールのコンソール(JS で attachConsole() が必要) |
出力先を指定しなければ Stdout と LogDir の 2 つです。level() を省くとすべてのレベルが記録され、依存クレートのログも混ざるので、開発中は Debug、配布版は Info のように分けます。
既定のままだと困る点が 2 つあります。ファイルは 40,000 バイトを超えると中身を消して書き直され、時刻は UTC です。上限を広げて古いファイルを日付付きの名前で残し、時刻を現地時間にしておきます(取得できない環境では UTC のまま)。
use tauri_plugin_log::{log, RotationStrategy, Target, TargetKind, TimezoneStrategy};
#[tauri::command]
fn save_note(text: String) -> Result<(), String> {
log::debug!("save_note: {} 文字", text.chars().count());
if text.trim().is_empty() {
log::warn!("空のメモは保存しません");
return Err("empty".into());
}
log::info!("メモを保存しました");
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let level = if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
};
tauri::Builder::default()
.plugin(
tauri_plugin_log::Builder::new()
.targets([
Target::new(TargetKind::Stdout),
Target::new(TargetKind::LogDir { file_name: Some("app".into()) }), // app.log
Target::new(TargetKind::Webview),
])
.level(level)
.level_for("tauri_app_lib::sync", log::LevelFilter::Trace) // このモジュールだけ細かく
.max_file_size(2_000_000) // 既定は 40,000 バイト
.rotation_strategy(RotationStrategy::KeepSome(5)) // 古いものを 5 つまで残す
.timezone_strategy(TimezoneStrategy::UseLocal) // 既定は UTC
.build(),
)
.plugin(tauri_plugin_opener::init())
.setup(|app| {
log::info!("起動 v{}", app.package_info().version);
Ok(())
})
.invoke_handler(tauri::generate_handler![save_note])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
ログの発生元(ターゲット)にはモジュールのパスが入り、level_for() でモジュールごとにレベルを変えられます。プラグインの初期化より前に書いたログは捨てられます。
2. JS からログを書く (TypeScript)
console.log は自動では転送されないので、残したい内容はプラグインの info() / warn() / error() などで書きます。呼び出し元の関数名と場所が発生元に入ります。呼ぶたびに Rust との通信が起きるので、ループの中では避けます。
import { attachConsole, error, info, warn } from '@tauri-apps/plugin-log';
// Rust のログを開発者ツールにも出す(Webview ターゲットが必要)
await attachConsole();
export async function onSave(text: string) {
await info(`保存を開始: ${text.length} 文字`);
}
// 捕まえ損ねた例外もファイルに残す
window.addEventListener('error', (e) => {
void error(`${e.message} (${e.filename}:${e.lineno})`);
});
window.addEventListener('unhandledrejection', (e) => {
void warn(`処理されなかった Promise の失敗: ${String(e.reason)}`);
});
3. ログファイルの場所 (TypeScript)
<識別子> は tauri.conf.json の identifier です。ファイル名は file_name に .log を付けたもので、None ならアプリ名(productName)になります。
| OS | LogDir のフォルダ |
|---|---|
| Windows | %LOCALAPPDATA%\<識別子>\logs |
| macOS / iOS | ~/Library/Logs/<識別子> |
| Linux | $XDG_DATA_HOME/<識別子>/logs(未設定なら ~/.local/share/<識別子>/logs) |
| Android | /data/data/<識別子>/files/logs など |
ログを送ってもらうときは、ファイルを選択した状態でフォルダを開くボタンがあると案内が楽です。Rust では app.path().app_log_dir() で同じフォルダが取れます。
import { appLogDir, join } from '@tauri-apps/api/path';
import { revealItemInDir } from '@tauri-apps/plugin-opener';
export async function showLogFile() {
const file = await join(await appLogDir(), 'app.log'); // file_name: Some("app") の場合
await revealItemInDir(file); // エクスプローラーや Finder で選択した状態で開く
}
4. env_logger との違い (Rust)
| Log プラグイン | env_logger | |
|---|---|---|
| 出力先 | ターミナル・ファイル・開発者ツール | ターミナル(標準エラー)だけ |
| レベルの指定 | コード(level()) | 環境変数 RUST_LOG |
| JS からのログ | 書ける | 書けない |
| 何も指定しないとき | すべてのレベル | error だけ |
Windows のリリースビルドはテンプレートの main.rs の設定でコンソールを持たないので、env_logger の出力は配布先では見えません。開発中に RUST_LOG で手早く切り替えたいだけなら、cargo add env_logger して次のように使えます。
// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
env_logger::init(); // RUST_LOG を読んでロガーを登録する
tauri_app_lib::run();
}
$env:RUST_LOG="tauri_app_lib=debug"; npm run tauri dev
ロガーはプロセスに 1 つしか登録できず、そのまま併用すると起動に失敗します。併用するならプラグインに .skip_logger() を付け、JS からのログを env_logger に渡す役だけにします。
動作確認
npm run tauri dev で起動して onSave() を呼ぶと、ターミナルとログファイルに次のように出ます。timezone_strategy を指定すると、時刻の後はレベル、発生元の順になります(既定の書式では発生元が先)。
[2026-09-12][10:15:30][INFO][tauri_app_lib] 起動 v0.1.0
[2026-09-12][10:15:42][INFO][webview:onSave@http://localhost:1420/src/main.ts:7:9] 保存を開始: 12 文字
[2026-09-12][10:15:42][DEBUG][tauri_app_lib] save_note: 12 文字
[2026-09-12][10:15:42][INFO][tauri_app_lib] メモを保存しました
よくあるエラーと対処法
- 「log.log not allowed. Permissions associated with this command: log:allow-log, log:default」:
log:defaultがありません。リリースビルドでは「Command plugin:log|log not allowed by ACL」になります。 - 起動時に「attempted to set a logger after the logging system was already initialized」を含む panic: env_logger など別のロガーが先に登録されています。どちらかをやめるか
.skip_logger()を付けます。 attachConsole()を呼んでも Rust のログが出ない:Webviewターゲットがありません。ページの読み込み前(setupの中など)のログはコンソールには出ないので、ファイルで見ます。- 同じログが止まらずに増え続ける:
consoleを書き換えてプラグインへ送り、attachConsole()も使うと往復します。Webviewターゲットに.filter(|m| !m.target().starts_with(tauri_plugin_log::WEBVIEW_TARGET))を付け、JS 由来のログを送り返さないようにします。 - 古いログが消えている /
debug!が出ない: 既定の上限(40,000 バイト)と、tauri addが設定する Info のレベルのためです(1 章)。
OS ごとの違いと注意点
- Windows: リリースビルドにはコンソールが無く、
Stdoutの出力は見えないので、ログファイルが唯一の記録です。 - Android / iOS:
revealItemInDir()は使えません。ログを渡してもらう方法を別に用意します。 - 共通: 既定では前回のファイルに追記します。
file_open_strategy(FileOpenStrategy::Rotate)で起動ごとに新しいファイルにできますが、既定のKeepOneのままだと前回のファイルが消えるので、KeepSomeと組み合わせます。 - 共通: ログファイルは利用者から送ってもらうものなので、パスワードやトークン、個人情報は書きません。panic の記録は パニック(クラッシュ)時の処理を書く を参照してください。
