log
Rustコアチームによって保守される軽量ロギングファサード。実際のロギング実装を抽象化する単一APIを提供。error!、warn!、info!、debug!、trace!の5つのマクロでログレベルを管理。ライブラリ開発での標準的な選択肢。
GitHub概要
スター2,367
ウォッチ39
フォーク267
作成日:2014年12月13日
言語:Rust
ライセンス:Apache License 2.0
トピックス
loggingrust-library
スター履歴
データ取得日時: 2025/7/17 08:20
ライブラリ
log
概要
logは、Rustコアチームによって保守される軽量ロギングファサードライブラリです。実際のロギング実装を抽象化する単一APIを提供し、error!、warn!、info!、debug!、trace!の5つのマクロでログレベルを管理します。Rust生態系において事実上の標準的なロギングインターフェースとして機能し、ライブラリ開発での推奨選択肢として確固たる地位を確立しています。
詳細
logクレートは2025年でもRust生態系の基盤として不動の地位を維持し続けています。Rust Cookbookで最初に紹介されるクレートとして、新規Rust開発者の学習起点となっており、ライブラリ開発ではlogクレートのみにリンクすることが推奨される標準的な慣例となっています。ファサードパターンにより、実際のログ出力はenv_logger、log4rs、tracing等の実装クレートが担当し、アプリケーション開発者が具体的なロガーを選択できる柔軟性を提供します。
主な特徴
- 軽量ファサード: 実装に依存しない抽象的なロギングインターフェース
- 5段階ログレベル: error, warn, info, debug, traceの標準レベル提供
- マクロベースAPI: コンパイル時最適化とゼロコスト抽象化
- 実装中立性: 任意のロガー実装との組み合わせ可能
- 構造化ログ対応: kvフィーチャーによる構造化データサポート
- no_std対応: 組み込み環境での使用可能
- Rustコアチーム保守: 長期安定性と品質保証
メリット・デメリット
メリット
- Rust生態系における標準的なロギングインターフェースとしての普遍性
- ライブラリ開発時の実装選択柔軟性とエコシステム互換性
- 軽量で最小限の依存関係による高いパフォーマンス
- コンパイル時最適化とゼロコスト抽象化の実現
- Rustコアチームによる長期サポートと安定性保証
- 豊富なドキュメントと学習リソースの充実
デメリット
- ファサードのため単体では実際のログ出力不可
- 別途env_loggerなどの実装クレートの選択と設定が必要
- 構造化ログは限定的でtracingほど高機能ではない
- 非同期ログやコンテキスト管理の機能が限定的
- 複雑なログフォーマットやフィルタリングには追加実装が必要
- シンプルさゆえの高度な機能制限
参考ページ
書き方の例
基本的なセットアップとログマクロ
// Cargo.toml
[dependencies]
log = "0.4"
env_logger = "0.10" // 実装クレートの例
// main.rs
use log::{debug, error, info, trace, warn};
fn main() {
// ロガーの初期化(実装クレート依存)
env_logger::init();
// 5つのログレベルマクロの基本使用
error!("重大なエラーが発生しました");
warn!("警告: 設定ファイルが見つかりません");
info!("アプリケーションが開始されました");
debug!("デバッグ情報: 変数x = {}", 42);
trace!("詳細トレース: 関数呼び出し");
// 条件付きログ(コンパイル時最適化)
if log::log_enabled!(log::Level::Debug) {
let expensive_computation = calculate_heavy_task();
debug!("計算結果: {}", expensive_computation);
}
}
fn calculate_heavy_task() -> u32 {
// 重い計算のシミュレーション
(1..=1000).sum()
}
ライブラリでのlog使用パターン
// ライブラリクレートでの推奨パターン
use log::{debug, error, info, trace, warn};
pub struct YakShaver {
pub name: String,
}
impl YakShaver {
pub fn new(name: String) -> Self {
info!("YakShaver '{}' を作成しました", name);
Self { name }
}
pub fn shave_yak(&mut self, yak: &mut Yak) -> Result<(), ShavingError> {
trace!("yak shaving開始: {}", yak.name);
loop {
match self.find_razor() {
Ok(razor) => {
info!("カミソリを発見: {:?}", razor);
yak.shave(razor)?;
info!("yak shaving完了: {}", yak.name);
break;
}
Err(err) => {
warn!("カミソリが見つかりません: {}、再試行中...", err);
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
debug!("shaving統計: {} 回の試行", self.attempt_count);
Ok(())
}
fn find_razor(&self) -> Result<Razor, String> {
// カミソリ検索ロジック
if rand::random::<f32>() > 0.7 {
Ok(Razor::Sharp)
} else {
Err("カミソリが見つかりません".to_string())
}
}
}
#[derive(Debug)]
pub enum Razor {
Sharp,
Dull,
}
pub struct Yak {
pub name: String,
shaved: bool,
}
impl Yak {
pub fn new(name: String) -> Self {
Self { name, shaved: false }
}
pub fn shave(&mut self, razor: Razor) -> Result<(), ShavingError> {
match razor {
Razor::Sharp => {
self.shaved = true;
debug!("yak '{}' のshaving成功", self.name);
Ok(())
}
Razor::Dull => {
error!("鈍いカミソリでshavingに失敗: {}", self.name);
Err(ShavingError::DullRazor)
}
}
}
}
#[derive(Debug)]
pub enum ShavingError {
DullRazor,
YakEscaped,
}
impl std::fmt::Display for ShavingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ShavingError::DullRazor => write!(f, "カミソリが鈍すぎます"),
ShavingError::YakEscaped => write!(f, "yakが逃げました"),
}
}
}
impl std::error::Error for ShavingError {}
ターゲットとログフィルタリング
use log::{debug, error, info, warn};
// モジュール別ログターゲットの使用
pub mod database {
use log::{debug, error, info};
pub fn connect() -> Result<Connection, DbError> {
info!(target: "database", "データベース接続開始");
match establish_connection() {
Ok(conn) => {
info!(target: "database", "データベース接続成功");
debug!(target: "database", "接続詳細: {:?}", conn);
Ok(conn)
}
Err(e) => {
error!(target: "database", "データベース接続失敗: {}", e);
Err(e)
}
}
}
pub fn query(sql: &str) -> Result<Vec<Row>, DbError> {
debug!(target: "database::query", "SQLクエリ実行: {}", sql);
let start = std::time::Instant::now();
let result = execute_query(sql);
let duration = start.elapsed();
match result {
Ok(rows) => {
info!(target: "database::query",
"クエリ成功: {}行を{}ms で取得", rows.len(), duration.as_millis());
debug!(target: "database::query", "結果: {:?}", rows);
Ok(rows)
}
Err(e) => {
error!(target: "database::query",
"クエリエラー: {} (実行時間: {}ms)", e, duration.as_millis());
Err(e)
}
}
}
// ダミー実装
struct Connection;
struct Row;
#[derive(Debug)]
struct DbError(String);
impl std::fmt::Display for DbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for DbError {}
fn establish_connection() -> Result<Connection, DbError> {
if rand::random::<f32>() > 0.2 {
Ok(Connection)
} else {
Err(DbError("接続エラー".to_string()))
}
}
fn execute_query(_sql: &str) -> Result<Vec<Row>, DbError> {
if rand::random::<f32>() > 0.1 {
Ok(vec![Row; rand::random::<usize>() % 100])
} else {
Err(DbError("SQLエラー".to_string()))
}
}
}
pub mod network {
use log::{debug, error, info, warn};
pub async fn fetch_data(url: &str) -> Result<String, NetworkError> {
info!(target: "network", "HTTP リクエスト開始: {}", url);
if !is_valid_url(url) {
warn!(target: "network", "無効なURL: {}", url);
return Err(NetworkError::InvalidUrl);
}
let start = std::time::Instant::now();
// HTTPリクエストのシミュレーション
let result = perform_request(url).await;
let duration = start.elapsed();
match result {
Ok(data) => {
info!(target: "network",
"HTTP リクエスト成功: {} bytes を {}ms で取得",
data.len(), duration.as_millis());
debug!(target: "network", "レスポンスデータ: {}",
if data.len() > 100 {
format!("{}...", &data[..100])
} else {
data.clone()
});
Ok(data)
}
Err(e) => {
error!(target: "network",
"HTTP リクエスト失敗: {} (実行時間: {}ms)",
e, duration.as_millis());
Err(e)
}
}
}
#[derive(Debug)]
pub enum NetworkError {
InvalidUrl,
Timeout,
ServerError,
}
impl std::fmt::Display for NetworkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NetworkError::InvalidUrl => write!(f, "無効なURL"),
NetworkError::Timeout => write!(f, "タイムアウト"),
NetworkError::ServerError => write!(f, "サーバーエラー"),
}
}
}
impl std::error::Error for NetworkError {}
fn is_valid_url(url: &str) -> bool {
url.starts_with("http")
}
async fn perform_request(url: &str) -> Result<String, NetworkError> {
// リクエストのシミュレーション
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
if url.contains("timeout") {
Err(NetworkError::Timeout)
} else if url.contains("error") {
Err(NetworkError::ServerError)
} else {
Ok(format!("レスポンスデータ from {}", url))
}
}
}
// メイン関数での統合使用例
#[tokio::main]
async fn main() {
env_logger::init();
info!("アプリケーション開始");
// データベース操作
if let Err(e) = database::connect() {
error!("データベース接続エラー: {}", e);
} else if let Err(e) = database::query("SELECT * FROM users") {
error!("クエリエラー: {}", e);
}
// ネットワーク操作
let urls = vec![
"https://api.example.com/users",
"https://api.example.com/timeout",
"invalid-url",
"https://api.example.com/error",
];
for url in urls {
if let Err(e) = network::fetch_data(url).await {
warn!("ネットワークエラー for {}: {}", url, e);
}
}
info!("アプリケーション終了");
}
構造化ログとカスタムデータ(kvフィーチャー)
// Cargo.toml
[dependencies]
log = { version = "0.4", features = ["kv"] }
env_logger = "0.10"
serde = { version = "1.0", features = ["derive"] }
use log::{as_debug, as_error, as_serde, debug, error, info, warn};
use serde::Serialize;
#[derive(Debug, Serialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[derive(Debug, Serialize)]
struct RequestMetrics {
endpoint: String,
method: String,
status_code: u16,
duration_ms: u64,
user_id: Option<u64>,
}
impl User {
fn new(id: u64, name: String, email: String) -> Self {
let user = Self { id, name, email };
// 構造化ログとしてユーザー作成をログ
info!(
user_id = user.id,
user_name = user.name.as_str(),
action = "user_created";
"新しいユーザーが作成されました"
);
user
}
fn update_email(&mut self, new_email: String) -> Result<(), &'static str> {
let old_email = self.email.clone();
if !is_valid_email(&new_email) {
warn!(
user_id = self.id,
old_email = old_email.as_str(),
new_email = new_email.as_str(),
action = "email_update_failed";
"無効なメールアドレスによる更新失敗"
);
return Err("無効なメールアドレス");
}
self.email = new_email.clone();
info!(
user_id = self.id,
old_email = old_email.as_str(),
new_email = new_email.as_str(),
action = "email_updated";
"ユーザーのメールアドレスが更新されました"
);
Ok(())
}
}
// HTTPリクエスト処理の例
async fn handle_request(
method: &str,
endpoint: &str,
user_id: Option<u64>,
) -> Result<String, Box<dyn std::error::Error>> {
let start_time = std::time::Instant::now();
// リクエスト開始ログ
info!(
endpoint = endpoint,
method = method,
user_id = user_id,
action = "request_started";
"HTTPリクエスト処理開始"
);
// 実際の処理(シミュレーション)
let result = process_request(method, endpoint, user_id).await;
let duration = start_time.elapsed();
let status_code = match &result {
Ok(_) => 200,
Err(_) => 500,
};
// メトリクス構造体の作成
let metrics = RequestMetrics {
endpoint: endpoint.to_string(),
method: method.to_string(),
status_code,
duration_ms: duration.as_millis() as u64,
user_id,
};
match &result {
Ok(response) => {
info!(
metrics:serde = metrics,
response_size = response.len(),
action = "request_completed";
"HTTPリクエスト処理完了"
);
}
Err(error) => {
error!(
metrics:serde = metrics,
error:err = error.as_ref(),
action = "request_failed";
"HTTPリクエスト処理失敗"
);
}
}
result
}
async fn process_request(
method: &str,
endpoint: &str,
user_id: Option<u64>,
) -> Result<String, Box<dyn std::error::Error>> {
// リクエスト処理のシミュレーション
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
match endpoint {
"/api/users" if method == "GET" => {
debug!(
endpoint = endpoint,
method = method,
action = "database_query";
"ユーザー一覧取得のためのデータベースクエリ"
);
Ok("ユーザー一覧データ".to_string())
}
"/api/profile" if method == "GET" => {
if let Some(uid) = user_id {
debug!(
endpoint = endpoint,
method = method,
user_id = uid,
action = "profile_fetch";
"ユーザープロフィール取得"
);
Ok(format!("ユーザー{}のプロフィール", uid))
} else {
Err("認証が必要です".into())
}
}
"/api/error" => {
Err("シミュレーションエラー".into())
}
_ => {
warn!(
endpoint = endpoint,
method = method,
action = "unknown_endpoint";
"未知のエンドポイント"
);
Err("未知のエンドポイント".into())
}
}
}
fn is_valid_email(email: &str) -> bool {
email.contains('@') && email.contains('.')
}
// アプリケーション状態の監視とログ
struct AppState {
active_connections: u32,
memory_usage_mb: u64,
cpu_usage_percent: f32,
}
impl AppState {
fn new() -> Self {
Self {
active_connections: 0,
memory_usage_mb: 0,
cpu_usage_percent: 0.0,
}
}
fn update_metrics(&mut self) {
// メトリクス更新のシミュレーション
self.active_connections = rand::random::<u32>() % 1000;
self.memory_usage_mb = 100 + (rand::random::<u64>() % 400);
self.cpu_usage_percent = rand::random::<f32>() * 100.0;
debug!(
active_connections = self.active_connections,
memory_usage_mb = self.memory_usage_mb,
cpu_usage_percent = self.cpu_usage_percent,
action = "metrics_updated";
"アプリケーションメトリクス更新"
);
// 閾値チェック
if self.memory_usage_mb > 400 {
warn!(
memory_usage_mb = self.memory_usage_mb,
threshold = 400,
action = "high_memory_usage";
"メモリ使用量が閾値を超えました"
);
}
if self.cpu_usage_percent > 80.0 {
warn!(
cpu_usage_percent = self.cpu_usage_percent,
threshold = 80.0,
action = "high_cpu_usage";
"CPU使用率が高くなっています"
);
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
info!(action = "app_started"; "アプリケーション開始");
// ユーザー管理のテスト
let mut user = User::new(1, "田中太郎".to_string(), "[email protected]".to_string());
user.update_email("[email protected]".to_string())?;
let _ = user.update_email("invalid-email"); // エラーケース
// HTTPリクエスト処理のテスト
let requests = vec![
("GET", "/api/users", None),
("GET", "/api/profile", Some(1)),
("GET", "/api/profile", None), // 認証エラー
("GET", "/api/error", Some(1)), // サーバーエラー
("POST", "/api/unknown", Some(1)), // 未知のエンドポイント
];
for (method, endpoint, user_id) in requests {
let _ = handle_request(method, endpoint, user_id).await;
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
// アプリケーション状態監視のテスト
let mut app_state = AppState::new();
for _ in 0..5 {
app_state.update_metrics();
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
}
info!(action = "app_finished"; "アプリケーション終了");
Ok(())
}
ログレベル制御とパフォーマンス最適化
use log::{debug, error, info, trace, warn, Level};
// パフォーマンス重視のログ制御
struct PerformanceCriticalService {
operation_count: u64,
last_log_time: std::time::Instant,
}
impl PerformanceCriticalService {
fn new() -> Self {
Self {
operation_count: 0,
last_log_time: std::time::Instant::now(),
}
}
fn process_item(&mut self, item: &str) -> Result<String, ProcessError> {
self.operation_count += 1;
// コンパイル時最適化:デバッグログが無効な場合、
// expensive_debug_info()は呼び出されない
debug!("処理開始: item={}, debug_info={}",
item,
if log::log_enabled!(Level::Debug) {
expensive_debug_info()
} else {
String::new()
});
// バッチログ:1秒間隔での統計ログ
if self.last_log_time.elapsed() >= std::time::Duration::from_secs(1) {
info!("処理統計: {} 操作/秒", self.operation_count);
self.operation_count = 0;
self.last_log_time = std::time::Instant::now();
}
// 実際の処理
if item.is_empty() {
warn!("空のアイテムが渡されました");
return Err(ProcessError::EmptyItem);
}
if item.len() > 1000 {
error!("アイテムサイズが制限を超えています: {}", item.len());
return Err(ProcessError::OversizedItem);
}
// 重い処理のシミュレーション
let result = format!("処理済み: {}", item);
// トレースレベル:最詳細ログ
trace!("処理詳細: input_len={}, output_len={}, operation_id={}",
item.len(), result.len(), self.operation_count);
Ok(result)
}
fn bulk_process(&mut self, items: Vec<&str>) -> Vec<Result<String, ProcessError>> {
info!("バルク処理開始: {} アイテム", items.len());
let start = std::time::Instant::now();
let results: Vec<_> = items.into_iter()
.map(|item| self.process_item(item))
.collect();
let duration = start.elapsed();
let success_count = results.iter().filter(|r| r.is_ok()).count();
let error_count = results.len() - success_count;
info!("バルク処理完了: success={}, errors={}, duration={}ms",
success_count, error_count, duration.as_millis());
if error_count > 0 {
warn!("エラーが発生しました: {}/{} アイテム", error_count, results.len());
}
results
}
}
#[derive(Debug)]
enum ProcessError {
EmptyItem,
OversizedItem,
}
impl std::fmt::Display for ProcessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProcessError::EmptyItem => write!(f, "空のアイテム"),
ProcessError::OversizedItem => write!(f, "サイズ超過アイテム"),
}
}
}
impl std::error::Error for ProcessError {}
fn expensive_debug_info() -> String {
// 重い計算のシミュレーション(デバッグ時のみ実行)
let start = std::time::Instant::now();
let sum: u64 = (1..=1000).map(|x| x * x).sum();
let duration = start.elapsed();
format!("debug_computation: sum={}, time={}μs", sum, duration.as_micros())
}
// カスタムロガー実装の例
struct CustomLogger {
level: Level,
module_filters: std::collections::HashMap<String, Level>,
}
impl CustomLogger {
fn new(default_level: Level) -> Self {
Self {
level: default_level,
module_filters: std::collections::HashMap::new(),
}
}
fn set_module_level(&mut self, module: String, level: Level) {
self.module_filters.insert(module, level);
}
fn should_log(&self, metadata: &log::Metadata) -> bool {
let target_level = self.module_filters
.get(metadata.target())
.copied()
.unwrap_or(self.level);
metadata.level() <= target_level
}
}
impl log::Log for CustomLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.should_log(metadata)
}
fn log(&self, record: &log::Record) {
if !self.should_log(record.metadata()) {
return;
}
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
println!("[{}] {} [{}:{}] {}",
timestamp,
record.level(),
record.module_path().unwrap_or("unknown"),
record.line().unwrap_or(0),
record.args());
}
fn flush(&self) {}
}
fn main() {
// カスタムロガーの設定
let mut logger = CustomLogger::new(Level::Info);
logger.set_module_level("performance_test".to_string(), Level::Debug);
logger.set_module_level("verbose_module".to_string(), Level::Trace);
log::set_boxed_logger(Box::new(logger))
.map(|()| log::set_max_level(log::LevelFilter::Trace))
.expect("ロガー初期化失敗");
info!("カスタムロガーでアプリケーション開始");
// パフォーマンステスト
let mut service = PerformanceCriticalService::new();
// 個別処理テスト
let test_items = vec!["item1", "", "item3", &"x".repeat(2000)];
for item in test_items {
match service.process_item(item) {
Ok(result) => debug!("処理成功: {}", result),
Err(e) => warn!("処理エラー: {}", e),
}
}
// バルク処理テスト
let bulk_items = (1..=100)
.map(|i| format!("bulk_item_{}", i))
.collect::<Vec<_>>();
let bulk_refs: Vec<&str> = bulk_items.iter().map(|s| s.as_str()).collect();
let results = service.bulk_process(bulk_refs);
info!("バルク処理結果: {}/{} 成功",
results.iter().filter(|r| r.is_ok()).count(),
results.len());
// 負荷テスト(大量ログ)
info!("負荷テスト開始");
let start = std::time::Instant::now();
for i in 0..10000 {
if i % 1000 == 0 {
info!("負荷テスト進捗: {}/10000", i);
}
// デバッグログは通常無効化されるためパフォーマンス影響なし
debug!("負荷テストイテレーション: {}", i);
// トレースログも同様
trace!("詳細トレース: iteration={}, timestamp={}", i, start.elapsed().as_millis());
}
let duration = start.elapsed();
info!("負荷テスト完了: 10000ログを{}ms で処理", duration.as_millis());
info!("アプリケーション終了");
}