LoginSignup
5
7

Tauri でロギングする公式プラグイン Tauri Log Plugin

Last updated at Posted at 2024-03-15

はじめに

Tauri は、そのフレームワークの構造故にバックエンドを Rust で書く。それが故、Rust でのロギング事情を調べるというのもあるが、Tauri そのものの構造を利用した上でのロギングとはどいう言うものかを調べたくなった。
また、Tauri はフロントエンドが Webview で構成されているため、そちらのコンソール出力をどう受け取るかも気になった。
そこで調べていると、Tauri のプラグインワークスペースTauri Log Plugin というものがあることに気付いた。
今回はこれをきっかけに Tauri でのログの取り方を学んでみたいと思う。

環境

  • macOS Sonoma 14.2.1
  • Windows 11 Pro 23H2
  • Tauri 1.5.4
  • Rust 1.17.0

Tauri Log Plugin を使用する

まずは、Tauri 公式のプラグインから。

クレートを依存関係に追加する。

Cargo.toml
[dependencies]
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }

Tauri v2 への対応
Tauri は、まもなく Tauri 2 リリースを控えている様子。そして、Tauri v2 ではプラグインへのサポートも手厚くなっている。
そうした状況から、Tauri Log Plugin も v2 が控えている
v2 では、設定の仕方が変更になっているため、また正式リリース時に試して追記していこうと思う。

基本セットアップ

main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri_plugin_log::LogTarget;

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

まず、Tauri に対して .plugin() を使用して、プラグインを登録する。

次に、.targets({}) を使用し、どのターゲットのログを取るのかを選択する。
ターゲットのログは LogTarget::Stdout, LogTarget::Stderr, LogTarget::Webview, LogTarget::LogDirLogTarget::Folder(PathBuf) がある。

タイプ 用途
Stdout 標準出力のログを取る。
Stderr 標準エラー出力のログを取る。
Webview Webview側 (フロントエンド) の出力ログを取る。
LogDir 規定のディレクトリパス以下にログファイルを保存する。(以下参照)
Folder(PathBuf) 指定のディレクトリパス以下にログファイルを保存する。

LogTarget::LogDir を使用すると、ローカルファイルにログを書き出してくれるが、デフォルトの保存場所は以下の様になっている。

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

ログを取る

ログを取る方法はシンプルで、 log クレートを使用する。

cargo add log

バックエンド側

main.rs
fn main() {
    tauri::Builder::default()
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([
                    LogTarget::Stdout,
                    LogTarget::Webview,
                    LogTarget::LogDir
                ])
                .build(),
        )
        .setup(|app| {
            // 起動時に以下のログを取る
+           info!("This is info message!");
+           warn!("This is warning message!");
+           error!("this is error message!");
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

スクリーンショット 2024-03-16 2.49.31.png

フロントエンド側

まず依存モジュールをインストールする。

npm i https://github.com/tauri-apps/tauri-plugin-log#v1

依存モジュールである tauri-plugin-log-api の中から、それぞれログレベルごとの関数とコンソール出力と連結できる attachConsole を引用してくることが出来る。

+page.svelte
<script lang="ts">
    import { info, warn, trace, error, debug, attachConsole } from 'tauri-plugin-log-api';

    // こちらを実行しておく事で Webview 側のコンソール出力にも表示される様になる。
    // 又、返り値の dettach を使用する事で、再度 Webview のコンソール出力を切り離すことが出来る。
    const dettach = attachConsole();

    trace('this trace log from frontend')
	info('this info log from frontend');
	warn('this warn log from frontend');
	error('this error log from frontend');
	debug('this debug log from frontend');
</script>

Code_2024-03-19_11-29-46.png

Webview のモジュール名
Webview のモジュール名は webview::* となる。

ログレベルの設定

Tauri Log Plugin では、ログレベルの設定は 2 種類ある。

  • .level(LogFilter)
  • .level_for(module, LogFilter)

違いは、.level_for(module, LogFilter) はモジュール単位のでフィルタもできるという機能があるもの。
なので、コンフィグモジュール でのログレベル設定だけ行うといったこともできる。

main.rs
mod config;

use log::{error, info, warn, LevelFilter};
use tauri::{generate_handler, Manager};
use tauri_plugin_log::LogTarget;

fn main() {
    let app_state = config::AppState::new();

    tauri::Builder::default()
        .manage(app_state)
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([
                    LogTarget::Stdout,
                    LogTarget::Webview,
                    LogTarget::LogDir])
+               .level(LevelFilter::Info)
+               .level_for("app::config", LevelFilter::Trace)
                .build(),
        )
        .invoke_handler(generate_handler![
            config::commands::get_language,
            config::commands::set_language
        ])
        .setup(|app| {
            info!("This is info message!");
            warn!("This is warning message!");
            error!("this is error message!");

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
config.rs
use log::{debug, info};
use std::sync::Mutex;

#[allow(dead_code)]
pub struct AppState {
    language: Mutex<String>,
}

impl Default for AppState {
    fn default() -> Self {
        Self {
            language: Mutex::from("en".to_string()),
        }
    }
}

impl AppState {
    pub fn new() -> Self {
        Self::default()
    }
}

pub mod commands {
    use super::*;

    #[tauri::command]
    pub async fn get_language(
        state: tauri::State<'_, AppState>
    ) -> Result<String, String> {
        let language = state.language.lock().unwrap().clone();
+       debug!("Current language is `{}`.", language);
        Ok(language)
    }

    #[tauri::command]
    pub async fn set_language(
        state: tauri::State<'_, AppState>,
        new_lang: String,
    ) -> Result<(), String> {
        *state.language.lock().unwrap() = new_lang;
+       info!("Set language to `{}`", *state.language.lock().unwrap());
        Ok(())
    }
}

src/routes/+page.svelte
<script lang="ts">
    import { info, warn, trace, error, debug, attachConsole } from 'tauri-plugin-log-api';
    import { invoke } from '@tauri-apps/api'

    const dettach = attachConsole();

    trace('this trace log from frontend')
    info('this info log from frontend');
    warn('this warn log from frontend');
    error('this error log from frontend');
    debug('this debug log from frontend');

    // ページ表示時に言語情報を取得してプリント
	$: invoke('get_language').then((res) => {
		console.log(res);
	});

	async function onClicked() {
        // 言語を設定する別の機能の呼び出しコマンド
		await invoke('set_language', { newLang: 'ja' });
		console.log('Button clicked.');
	}
</script>

<button on:click={onClicked}>Click!!</button>

スクリーンショット 2024-03-16 6.37.10.png
Code_2024-03-19_11-27-03.png

config 名について
ログレベルをフィルタする際に、config の値を文字列で入力することになるが、上記のように app::* となることに気をつけておく。
今回の場合は app::config としてあったが、さらに app::config::commands といったスコープ指定もできる。
又、先の通り Webview のモジュール名は webview::* となる。

タイムゾーンを設定する

デフォルトではログに使用されるタイムゾーンは UTC になる。
ローカルタイムゾーンを使用する場合は別途設定が必要。

タイムゾーンの設定には、.timezone_strategy(TimezoneStrategy) に指定 Enum タイプを渡す。
タイプは UseUtcUseLocal があり、ローカルタイムでログを取る場合は UseLocal を使用する。

main.rs
mod config;

use log::{error, info, warn, LevelFilter};
use tauri::generate_handler;
-use tauri_plugin_log::LogTarget;
+use tauri_plugin_log::{LogTarget, TimezoneStrategy};

fn main() {
    let app_state = config::AppState::new();

    tauri::Builder::default()
        .manage(app_state)
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([LogTarget::Stdout, LogTarget::Webview, LogTarget::LogDir])
                .level(LevelFilter::Info)
                .level_for("app::config", LevelFilter::Trace)
+               .timezone_strategy(TimezoneStrategy::UseLocal)
                .build(),
        )
        .invoke_handler(generate_handler![
            config::commands::get_language,
            config::commands::set_language
        ])
        .setup(|_app| {
            info!("This is info message!");
            warn!("This is warning message!");
            error!("this is error message!");

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

ログファイル保存のオプション

ファイル分割オプション

ログファイルへの保存 (Rotation) には2通りのオプションがある。

  • KeepAll
    • すべてのログを保存
    • 随時ログが溜められるファイルは [log_name].log として保存される
    • 最大容量を超えると [log_name]_[year]-[month]-[day]_[hour]-[minute]-[second].log 形式で保存される
  • KeepOne
    • デフォルト
    • 単一ログファイルのみを保存。最大ログファイルサイズになった場合、ファイル削除され作成し直される

設定する場合は .rotation_strategy(RotationStrategy) を使用する。

main.rs
mod config;

use log::{error, info, warn, LevelFilter};
use tauri::generate_handler;
-use tauri_plugin_log::{LogTarget, TimezoneStrategy};
+use tauri_plugin_log::{LogTarget, TimezoneStrategy, RotationStrategy};

fn main() {
    let app_state = config::AppState::new();

    tauri::Builder::default()
        .manage(app_state)
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([LogTarget::Stdout, LogTarget::Webview, LogTarget::LogDir])
                .level(LevelFilter::Info)
                .level_for("app::config", LevelFilter::Trace)
                .timezone_strategy(TimezoneStrategy::UseLocal)
+               .rotation_strategy(RotationStrategy::KeepAll)
                .build(),
        )
        .invoke_handler(generate_handler![
            config::commands::get_language,
            config::commands::set_language
        ])
        .setup(|_app| {
            info!("This is info message!");
            warn!("This is warning message!");
            error!("this is error message!");

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

ファイルサイズオプション

ファイル保存の最大ファイルサイズを設定する。
設定する場合は .max_file_size(u128) を使用する。

デフォルトは 40000 (Byte)。

この最大ファイルサイズを超えると、上記 RotationStrategy に則り、ファイル分割を行う

main.rs
mod config;

use log::{error, info, warn, LevelFilter};
use tauri::generate_handler;
use tauri_plugin_log::{LogTarget, TimezoneStrategy, RotationStrategy};

fn main() {
    let app_state = config::AppState::new();

    tauri::Builder::default()
        .manage(app_state)
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([LogTarget::Stdout, LogTarget::Webview, LogTarget::LogDir])
                .level(LevelFilter::Info)
                .level_for("app::config", LevelFilter::Trace)
                .timezone_strategy(TimezoneStrategy::UseLocal)
                .rotation_strategy(RotationStrategy::KeepAll)
+               .max_file_size(100000)
                .build(),
        )
        .invoke_handler(generate_handler![
            config::commands::get_language,
            config::commands::set_language
        ])
        .setup(|_app| {
            info!("This is info message!");
            warn!("This is warning message!");
            error!("this is error message!");

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

ログ名を設定する

カスタムのログ名 (log_name) を設定する。
設定するには .log_name() を使用する。

ログ名は、ログファイル ([log_name].log or [log_name]_[year]-[month]-[day]_[hour]-[minute]-[second].log) にも使用される。

デフォルトでは Tauri アプリの名前が当てられる。

main.rs
mod config;

use log::{error, info, warn, LevelFilter};
use tauri::generate_handler;

fn main() {
    let app_state = config::AppState::new();

    tauri::Builder::default()
        .manage(app_state)
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([LogTarget::Stdout, LogTarget::Webview, LogTarget::LogDir])
+                .log_name("MyApp")
                .build(),
        )
        .invoke_handler(generate_handler![
            config::commands::get_language,
            config::commands::set_language
        ])
        .setup(|_app| {
            info!("This is info message!");
            warn!("This is warning message!");
            error!("this is error message!");

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

まとめ

今回は Tauri Log Plugin をどう設定すればいいかに注力してまとめてみた。
ただ、もうちょっと深く入りこんで見る必要もありそうなので、また機能や気づき等を見つけたら追記していきたい。
また、log モジュールは非同期状態でのロギングに対応していないので、 Tracing を使った方法なども検証していきたい。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7