Log プラグインでログファイルを出力する

Log プラグインで Rust と JS のログをターミナル・ファイル・開発者ツールに出す。出力先とレベルの指定、ログファイルの場所、40KB で消える既定の設定、env_logger との違いも示す。

プラグイン拡張 対象: Tauri 2.x 更新日: 読了目安: 約9分 plugin-008
目次
  1. 前提条件
  2. 1. 出力先とレベルを決める (Rust)
  3. 2. JS からログを書く (TypeScript)
  4. 3. ログファイルの場所 (TypeScript)
  5. 4. env_logger との違い (Rust)
  6. 動作確認
  7. よくあるエラーと対処法
  8. OS ごとの違いと注意点
  9. 関連レシピ

配布したアプリで「動かない」と言われたとき、手がかりになるのはログファイルです。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 / Stderrtauri 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)になります。

OSLogDir のフォルダ
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 の記録は パニック(クラッシュ)時の処理を書く を参照してください。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する