404 Not Found

404 Not Found


nginx

RustのファイルI/O:ファイルの読み書きとパスの扱い方

ファイルI/Oは、プログラムと外界をつなぐ「架け橋」のような役割を果たします。ファイルを読み込むことは、誰かが残したメッセージを「聞く」ようなものであり、ファイルに書き込むことは、未来の自分や他の人たちに「話す」ようなものです。

プログラムを人に例えるなら、変数やデータ構造はその「短期記憶」(脳)であり、ファイルシステムはその「長期記憶」(ノート)に相当します。プログラムが終了すると、その短期記憶は消えてしまいますが、ノートの内容は永久に残ります。ファイルI/Oとは、「ノートに読み書きする」機能のことです。これがなければ、プログラムは起動するたびに白紙の状態から始めなければなりません。


1. 学習内容


2. ログアナライザーの物語

(1) ログを手作業でページをめくる手間

シャオ・リンは同社の運用エンジニアで、毎朝真っ先にサーバーのログを手作業で確認している:

「ログを自動的に読み取り、ステータスコードを集計して、レポートを作成してくれるプログラムを書けたら最高なんだけど……」

(2) RustにおけるファイルI/Oへのアプローチ

Rustの標準ライブラリには、「ログ分析パイプライン」のような、ファイル入出力のためのツール一式が用意されています:

TEXT
Log Files(Server)           → Raw Materials
  File::open / read_to_string → Conveyor Feed
  BufReader                   → Buffering(Assembly Line Buffer)
  Line-by-Line Analysis                    → Quality Inspection Station
  HashMap Statistical Status Codes          → Categorical Statistics
  File::create / write        → Generate and Export Report
RUST
use std::fs::File;
use std::io::{BufRead, BufReader, Write};

fn analyze_log(path: &str) -> std::io::Result<()> {
    // Open the file and create a buffer reader
    let file = File::open(path)?;
    let reader = BufReader::new(file);

    // Count the number of occurrences for each status code
    let mut counts: std::collections::HashMap<String, u32> =
        std::collections::HashMap::new();

    for line in reader.lines() {
        let line = line?;
        // Assumed Log Format: "2026-07-03 10:30:45 GET /api/users 200"
        if let Some(status) = line.split_whitespace().last() {
            *counts.entry(status.to_string()).or_insert(0) += 1;
        }
    }

    // Write to the report file
    let mut report = File::create("report.txt")?;
    writeln!(report, "=== Status Code Statistics Report ===")?;
    for (code, count) in &counts {
        writeln!(report, "  {}: {}", code, count)?;
    }

    println!("The report has been generated: report.txt");
    Ok(())
}

RustのファイルI/Oの設計は、「ゼロコストの抽象化」という原則に従っています。BufReaderはパイプライン上のバッファのように機能し、低レベルのシステムコールの回数を減らします。また、?演算子は「自動エラー報告」機能を提供しており、I/Oエラーが発生すると直ちに返却されるため、手動でmatchを記述する必要がなくなります。Path および PathBuf は、ファイルパスに対する「安全なナビゲーター」として機能し、オペレーティングシステム間のパス区切り文字の違いを自動的に処理します。


3. 基本概念

(1) ファイル入出力システム

100%
graph TB
    A[Rust Document I/O] --> B[Read a File]
    A --> C[Write to a file]
    A --> D[Path Operations]
    A --> E[Error Handling]

    B --> B1["std::fs::read_to_string"]
    B --> B2["File::open + read_to_string"]
    B --> B3["BufReader Read line by line"]

    C --> C1["std::fs::write"]
    C --> C2["File::create + write_all"]
    C --> C3["BufWriter Buffered Write"]

    D --> D1["Path(Immutable Slices)"]
    D --> D2["PathBuf(Variable strings)"]
    D --> D3["Path Concatenation .join()"]

    E --> E1["Result<T, io::Error>"]
    E --> E2["? Operator Propagation Error"]
    E --> E3["match Precision Processing"]

(2) 読解法の比較

メソッド 関数/型 ユースケース メモリ使用量 パフォーマンス
シングルパス読み込み fs::read_to_string 小さなファイル(100MB未満) ファイルの内容全体 最速(1回のシステムコール)
手動読み取り File::read_to_string 精密な制御が必要 ファイルの内容全体 高速
行単位のバッファリング BufReader::read_line 大容量のファイル/ログ 1行分のデータ 中程度(システムコールを削減)
バッファ付き反復処理 BufReader::lines() 行単位で処理 1行分のデータ 便利(推奨)

(3) 記述方法の比較

メソッド 関数/型 ユースケース 機能
1回限りの書き込み fs::write 小さなファイル/単純な内容 最も簡潔
ファイルの作成 File::create 上書き ファイルが存在する場合は空にする
追加 OpenOptions::append ログに追加 元のコンテンツを保持
バッファ付き書き込み BufWriter 大量の書き込み システムコールの削減

(4) ファイルを開くためのクイックリファレンス

メソッド アプローチ/タイプ ファイルが存在する場合 ファイルが存在しない場合 適用可能なシナリオ
読み取り専用で開く File::open(path) 通常通り開く エラーを返す 既存のファイルを読み込む
作成/上書き File::create(path) 内容を消去 新規ファイルを作成 新規ファイルに書き込み
追加 OpenOptions::new().append(true).open() 末尾に追加 エラーを返す ログに追加
新規ファイルを作成 OpenOptions::new().write(true).create_new(true).open() エラーを返す 新規ファイルを作成 上書きを回避
読み書き可能 OpenOptions::new().read(true).write(true).open() 通常通り開く エラーを返す 既存のファイルを編集
作成および読み取り/書き込み OpenOptions::new().read(true).write(true).create(true).open() 通常通り開く 新規ファイルの作成 設定ファイルの読み取り/書き込み

4. ファイル入出力の例

(1) ▶ サンプル:エラー処理を含む基本的な読み書き(難易度 ⭐)

RUST
// ============================================
// Basic File Reading and Writing: fs::read_to_string + fs::write
// Demo:Create a temporary file、Enter content、Read the content、Cleanup
// Scene: Write a simple "Memo" program
// ============================================

use std::fs;
use std::path::Path;

fn main() -> std::io::Result<()> {
    println!("=== Simple Note-Taking Program ===\n");

    // Define the temporary file path (In the current directory temp_note.txt)
    let file_path = "temp_note.txt";

    // ---------- Write to a file ----------
    let content = "Today's To-Do List:
1. Study Rust Document I/O
2. Complete the Log Analyzer
3. Review Ownership and Borrowing

Rust Study Notes:
- Document I/O Usage std::fs Module
- BufReader Suitable for reading large files
- ? Simplifying Error Handling for Operators";

    fs::write(file_path, content)?;
    println!("[Write] The note has been saved to: {}", file_path);

    // Check if the file exists
    if Path::new(file_path).exists() {
        println!("[Inspection] The file exists,Size: {} Byte", content.len());
    }

    // ---------- Read a File ----------
    let read_content = fs::read_to_string(file_path)?;
    println!("\n[Read] Note Content:\n{}", read_content);

    // ---------- Statistical Information ----------
    let line_count = read_content.lines().count();
    let word_count: usize = read_content
        .split_whitespace()
        .count();
    let char_count = read_content.chars().count();

    println!("--- Memo Statistics ---");
    println!(" Number of lines: {}", line_count);
    println!(" Number of words: {}", word_count);
    println!(" Number of characters: {}", char_count);

    // ---------- Clear Temporary Files ----------
    fs::remove_file(file_path)?;
    println!("\n[Cleanup] The temporary file has been deleted.: {}", file_path);

    // Confirm that the file has been deleted
    assert!(!Path::new(file_path).exists(), "The file should be deleted.");
    println!("[Confirm] The files have been successfully deleted.");

    Ok(())
}

出力:

TEXT
=== Simple Note-Taking Program ===

[Write] The note has been saved to: temp_note.txt
[Inspection] The file exists,Size: 87 Byte

[Read] Note Content:
Today's To-Do List:
1. Study Rust Document I/O
2. Complete the Log Analyzer
3. Review Ownership and Borrowing

Rust Study Notes:
- Document I/O Usage std::fs Module
- BufReader Suitable for reading large files
- ? Simplifying Error Handling for Operators

--- Memo Statistics ---
 Number of lines: 10
 Number of words: 24
 Number of characters: 87

[Cleanup] The temporary file has been deleted.: temp_note.txt
[Confirm] The files have been successfully deleted.

fs::write および fs::read_to_string は、ファイルの読み書きを行う最も簡潔な方法です。すべての操作が 1 回の呼び出しで完了します。? 演算子は、Result からのエラーを呼び出し元に自動的に伝播し、エラーが発生した場合は早期に処理を終了します。Path::new(file_path).exists() は、ファイルが存在するかどうかを確認します。fs::remove_file はファイルを削除します。この例を実行するたびに、同じ一時ファイルが作成・削除されるため、再現性のある結果が得られます。


(2) ▶ サンプル:BufReader によるログの行単位での読み込み (難易度 ⭐⭐)

RUST
// ============================================
// BufReader Read line by line + Log Analysis
// Demo: Read the server logs, count the number of each status code
// Scene: Operations Engineers Automatically Analyze Log Files
// ============================================

use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};

fn main() -> std::io::Result<()> {
    println!("=== Server Log Analyzer ===\n");

    // Step 1: Create a sample log file
    let log_path = "temp_server.log";
    create_sample_log(log_path)?;
    println!("[Generate] Sample Log File: {}", log_path);

    // Step 2: Analyze Logs
    println!("[Analysis] Processing logs...");
    let status_counts = analyze_log(log_path)?;

    // Step 3: Generate Report
    let report_path = "temp_report.txt";
    generate_report(&status_counts, report_path)?;

    // Step 4: Read and Print the Report
    let report = std::fs::read_to_string(report_path)?;
    println!("\n{}", report);

    // Step 5: Cleanup
    std::fs::remove_file(log_path)?;
    std::fs::remove_file(report_path)?;
    println!("[Cleanup] The temporary file has been deleted.");

    Ok(())
}

/// Create a sample log file
fn create_sample_log(path: &str) -> std::io::Result<()> {
    let mut file = File::create(path)?;

    // Simulated Server Logs for One Day
    let log_entries = vec![
        "2026-07-03 08:01:23 GET /api/users 200",
        "2026-07-03 08:02:15 POST /api/login 200",
        "2026-07-03 08:03:44 GET /api/products 404",
        "2026-07-03 08:05:12 GET /api/users 200",
        "2026-07-03 08:07:33 POST /api/order 500",
        "2026-07-03 08:10:01 GET /api/products 200",
        "2026-07-03 08:12:45 GET /api/users 304",
        "2026-07-03 08:15:22 POST /api/login 401",
        "2026-07-03 08:18:09 GET /api/products 200",
        "2026-07-03 08:20:55 POST /api/order 500",
        "2026-07-03 08:22:30 GET /api/users 200",
        "2026-07-03 08:25:18 GET /api/products 404",
        "2026-07-03 08:28:44 POST /api/login 200",
        "2026-07-03 08:30:01 GET /api/search 200",
        "2026-07-03 08:32:55 POST /api/order 200",
        "2026-07-03 08:35:12 GET /api/users 200",
        "2026-07-03 08:38:29 GET /api/products 304",
        "2026-07-03 08:40:44 POST /api/login 401",
        "2026-07-03 08:42:10 GET /api/search 500",
        "2026-07-03 08:45:33 POST /api/order 200",
        "2026-07-03 08:48:01 GET /api/users 200",
        "2026-07-03 08:50:22 GET /api/products 200",
        "2026-07-03 08:52:55 POST /api/login 200",
        "2026-07-03 08:55:18 GET /api/search 404",
        "2026-07-03 08:58:40 POST /api/order 500",
    ];

    for entry in log_entries {
        writeln!(file, "{}", entry)?;
    }

    Ok(())
}

/// Analyze Log Files,Count each type of HTTP Number of occurrences of status codes
fn analyze_log(path: &str) -> std::io::Result<HashMap<String, u32>> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);

    let mut counts: HashMap<String, u32> = HashMap::new();
    let mut total_lines = 0u32;

    for line_result in reader.lines() {
        let line = line_result?;
        total_lines += 1;

        // Retrieve Status Code (The last field in the row)
        // Log Format: "2026-07-03 08:01:23 GET /api/users 200"
        if let Some(status) = line.split_whitespace().last() {
            *counts.entry(status.to_string()).or_insert(0) += 1;
        }
    }

    println!("[Statistics] Total Number of Lines: {}", total_lines);

    // Calculate the percentage of each status code
    let total: f64 = counts.values().sum::<u32>() as f64;
    println!("[Statistics] Found {} Different status codes", counts.len());

    let mut sorted: Vec<(&String, &u32)> = counts.iter().collect();
    sorted.sort_by_key(|(_, &count)| std::cmp::Reverse(count));
    for (code, count) in &sorted {
        let percentage = (*count as f64 / total * 100.0);
        println!("       {}: {} times ({:.1}%)", code, count, percentage);
    }

    Ok(counts)
}

/// Generate a statistical report file
fn generate_report(
    counts: &HashMap<String, u32>,
    report_path: &str,
) -> std::io::Result<()> {
    let mut file = File::create(report_path)?;

    writeln!(file, "========================================")?;
    writeln!(file, "  Server Log Status Code Statistics Report")?;
    writeln!(file, "  Date: 2026-07-03")?;
    writeln!(file, "========================================")?;
    writeln!(file)?;

    // Sort by quantity in descending order
    let mut sorted: Vec<(&String, &u32)> = counts.iter().collect();
    sorted.sort_by_key(|(_, &count)| std::cmp::Reverse(count));

    let total: u32 = counts.values().sum();

    for (code, count) in &sorted {
        let bar_length = (*count as f64 / total as f64 * 30.0) as usize;
        let bar = "=".repeat(bar_length);
        writeln!(file, "  {} | {} {:.1}%", code, bar, *count as f64 / total as f64 * 100.0)?;
    }

    writeln!(file)?;
    writeln!(file, "  Total Number of Requests: {}", total)?;
    writeln!(file, "  Success Rate (2xx): {:.1}%",
        counts.get("200").unwrap_or(&0) as &u32 + counts.get("304").unwrap_or(&0) as &u32 * 100 / total)?;
    writeln!(file, "========================================")?;

    Ok(())
}

出力:

TEXT
=== Server Log Analyzer ===

[Generate] Sample Log File: temp_server.log
[Analysis] Processing logs...
[Statistics] Total Number of Lines: 25
[Statistics] Found 5 Different status codes
       200: 12 times (48.0%)
       404: 3 times (12.0%)
       500: 4 times (16.0%)
       304: 2 times (8.0%)
       401: 2 times (8.0%)

========================================
  Server Log Status Code Statistics Report
  Date: 2026-07-03
========================================

  200  | ============================== 48.0%
  500  | ========== 16.0%
  404  | ======== 12.0%
  304  | ===== 8.0%
  401  | ===== 8.0%

  Total Number of Requests: 25
  Success Rate (2xx): 56.0%
========================================

[Cleanup] The temporary file has been deleted.

BufReader バッファリーダーは、大容量ファイルの処理に適しています。内部バッファを維持し、大量のデータを一度にメモリに読み込んだ後、行単位で返却するため、システムコールの回数を大幅に削減できます。reader.lines() ファイルを行単位で読み込むイテレータを返します。split_whitespace().last() 各行の最後のフィールド(ステータスコード)を抽出します。HashMap 各ステータスコードの出現回数をカウントし、レポートを生成してファイルに書き込みます。


(3) ▶ サンプル:BufWriter によるバッファ書き込みと Path/PathBuf によるパス操作 (難易度 ⭐⭐)

RUST
// ============================================
// BufWriter Buffered Write + Path / PathBuf Path Operations
// Demo: Merging Multiple Files + Path Concatenation and Normalization
// Scene: Merge multiple log files into a single consolidated file
// ============================================

use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};

fn main() -> std::io::Result<()> {
    println!("=== Log Merging Tool ===\n");

    // Usage PathBuf Build Path
    let base_dir = PathBuf::from(".");
    let log_files = vec![
        base_dir.join("temp_web.log"),
        base_dir.join("temp_api.log"),
        base_dir.join("temp_db.log"),
    ];

    // Generate three sample log files
    generate_log_files(&log_files)?;

    // Merge all logs into a single file
    let merged_path = base_dir.join("temp_merged.log");
    merge_log_files(&log_files, &merged_path)?;

    // Read and display the merge results
    let merged_content = std::fs::read_to_string(&merged_path)?;
    println!("--- Merged Logs ---");
    println!("{}", merged_content);

    // Usage Path Methods for Performing Path Operations
    println!("--- Path Information ---");
    let path = Path::new("temp_merged.log");
    println!("  file_name: {:?}", path.file_name().unwrap_or_default());
    println!("  parent: {:?}", path.parent().unwrap_or_default());
    println!("  extension: {:?}", path.extension().unwrap_or_default());
    println!("  exists: {}", path.exists());
    println!("  is_file: {}", path.is_file());

    // Get the file size
    let metadata = path.metadata()?;
    println!("  size: {} bytes", metadata.len());

    // Delete all temporary files
    cleanup_files(&log_files, &merged_path)?;

    Ok(())
}

/// Generate multiple sample log files
fn generate_log_files(file_paths: &[PathBuf]) -> std::io::Result<()> {
    let logs = vec![
        // Web Server Logs
        vec![
            "[WEB] 2026-07-03 10:00:00 GET /index.html 200",
            "[WEB] 2026-07-03 10:00:05 GET /style.css 200",
            "[WEB] 2026-07-03 10:01:00 POST /login 500",
        ],
        // API Server Logs
        vec![
            "[API] 2026-07-03 10:00:02 GET /api/users 200",
            "[API] 2026-07-03 10:00:10 POST /api/orders 201",
            "[API] 2026-07-03 10:01:05 GET /api/products 404",
        ],
        // Database Logs
        vec![
            "[DB]  2026-07-03 10:00:01 QUERY SELECT * FROM users 0.3ms",
            "[DB]  2026-07-03 10:00:11 QUERY INSERT INTO orders 1.2ms",
            "[DB]  2026-07-03 10:01:01 QUERY SELECT * FROM products 0.5ms",
        ],
    ];

    for (i, path) in file_paths.iter().enumerate() {
        let file = File::create(path)?;
        let mut writer = BufWriter::new(file);

        for entry in &logs[i] {
            writeln!(writer, "{}", entry)?;
        }
        // BufWriter will be drop Automatically flush,But you can also do it manually flush
        writer.flush()?;
        println!("[Generate] {:?}", path.file_name().unwrap_or_default());
    }

    Ok(())
}

/// Merge Multiple Log Files
fn merge_log_files(
    sources: &[PathBuf],
    destination: &PathBuf,
) -> std::io::Result<()> {
    // Usage BufWriter Write to the target file
    let dest_file = File::create(destination)?;
    let mut writer = BufWriter::new(dest_file);

    // Write to the header
    writeln!(writer, "=== Consolidated Log Report ===")?;
    writeln!(writer, "Generation Time: 2026-07-03 10:05:00")?;
    writeln!(writer, "Number of source files: {}", sources.len())?;
    writeln!(writer, "{}", "=".repeat(50))?;
    writeln!(writer)?;

    // Read and write on a per-file basis
    for source_path in sources {
        let file_name = source_path.file_name()
            .unwrap_or_default()
            .to_string_lossy();

        writeln!(writer, "--- Source: {} ---", file_name)?;

        let src_file = File::open(source_path)?;
        let reader = BufReader::new(src_file);

        for line_result in reader.lines() {
            let line = line_result?;
            writeln!(writer, "{}", line)?;
        }

        writeln!(writer)?;  // Blank line separator
    }

    // Write to the end of the statistics
    writeln!(writer, "{}", "=".repeat(50))?;
    writeln!(writer, "Merge Complete")?;

    writer.flush()?;
    println!("[Merge] Done: {:?}", destination.file_name().unwrap_or_default());

    Ok(())
}

/// Delete all temporary files
fn cleanup_files(log_files: &[PathBuf], merged_path: &PathBuf) -> std::io::Result<()> {
    println!("\n--- Clear Temporary Files ---");

    for path in log_files {
        if path.exists() {
            std::fs::remove_file(path)?;
            println!("[Delete] {:?}", path.file_name().unwrap_or_default());
        }
    }

    if merged_path.exists() {
        std::fs::remove_file(merged_path)?;
        println!("[Delete] {:?}", merged_path.file_name().unwrap_or_default());
    }

    println!("[Done] All temporary files have been deleted.");
    Ok(())
}

出力:

TEXT
=== Log Merging Tool ===

[Generate] "temp_web.log"
[Generate] "temp_api.log"
[Generate] "temp_db.log"
[Merge] Done: "temp_merged.log"

--- Merged Logs ---
=== Consolidated Log Report ===
Generation Time: 2026-07-03 10:05:00
Number of source files: 3
==================================================

--- Source: temp_web.log ---
[WEB] 2026-07-03 10:00:00 GET /index.html 200
[WEB] 2026-07-03 10:00:05 GET /style.css 200
[WEB] 2026-07-03 10:01:00 POST /login 500

--- Source: temp_api.log ---
[API] 2026-07-03 10:00:02 GET /api/users 200
[API] 2026-07-03 10:00:10 POST /api/orders 201
[API] 2026-07-03 10:01:05 GET /api/products 404

--- Source: temp_db.log ---
[DB]  2026-07-03 10:00:01 QUERY SELECT * FROM users 0.3ms
[DB]  2026-07-03 10:00:11 QUERY INSERT INTO orders 1.2ms
[DB]  2026-07-03 10:01:01 QUERY SELECT * FROM products 0.5ms

==================================================
Merge Complete

--- Path Information ---
  file_name: "temp_merged.log"
  parent: ""
  extension: "log"
  exists: true
  is_file: true
  size: 638 bytes

--- Clear Temporary Files ---
[Delete] "temp_web.log"
[Delete] "temp_api.log"
[Delete] "temp_db.log"
[Delete] "temp_merged.log"
[Done] All temporary files have been deleted.

BufWriter バッファライターは、まずデータをメモリバッファに書き込み、バッファがいっぱいになったとき、またはflush()が手動で呼び出されたときにのみ、1回の操作でディスクに書き込みを行います。これにより、システムコールの回数が大幅に削減されます。PathBufは可変パス文字列(Stringと同様)であり、Pathはパススライス(&strと同様)です。PathBuf::join() はパスを連結し、Path::file_name() はファイル名を取得し、Path::extension() はファイル拡張子を取得し、Path::exists() はファイルが存在するかどうかを確認します。これらのメソッドは、クロスプラットフォームのパス区切り文字の違いを自動的に処理します。


(4) ▶ サンプル:完全なログアナライザー――実環境のシナリオをシミュレートする(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Log Analyzer——Simulate real-world operations and maintenance scenarios
// Demo:Comprehensive Implementation Document I/O、Path Operations、Error Handling
// Features: Read the log -> Analysis -> Generate HTML Report -> Cleanup
// ============================================

use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

/// Log Entry Structure
#[derive(Debug)]
struct LogEntry {
    timestamp: String,
    method: String,
    path: String,
    status_code: u16,
}

/// Analysis Results
struct AnalysisResult {
    total_requests: u32,
    status_counts: HashMap<u16, u32>,
    method_counts: HashMap<String, u32>,
    top_paths: Vec<(String, u32)>,
    error_rate: f64,
}

fn main() -> std::io::Result<()> {
    println!("=== Complete Log Analyzer ===\n");

    let log_path = "temp_access.log";
    let report_path = "temp_report.html";

    // Generate a sample log
    generate_access_log(log_path)?;
    println!("[1/4] The simulation log has been generated: {}", log_path);

    // Analysis Log
    let entries = parse_log(log_path)?;
    println!("[2/4] Log parsing complete: {} records", entries.len());

    // Analyze Data
    let result = analyze_entries(&entries);
    print_analysis(&result);

    // Generate HTML Report
    generate_html_report(&result, report_path)?;
    println!("[3/4] HTML The report has been generated: {}", report_path);

    // Display Report Content
    let report_content = std::fs::read_to_string(report_path)?;
    println!("\n--- Report Preview(first 20 lines) ---");
    for line in report_content.lines().take(20) {
        println!("{}", line);
    }
    println!("... (total {} lines)", report_content.lines().count());

    // Cleanup
    cleanup(log_path, report_path)?;
    println!("\n[4/4] Temporary files have been cleared.");

    Ok(())
}

/// Generate a simulated access log
fn generate_access_log(path: &str) -> std::io::Result<()> {
    let paths = [
        "/index.html", "/api/users", "/api/products",
        "/api/orders", "/login", "/search",
        "/about", "/contact", "/api/settings",
    ];
    let methods = ["GET", "POST", "PUT", "DELETE"];
    let statuses = [200, 200, 200, 200, 200, 201, 204, 301, 304, 400, 401, 403, 404, 500, 502];

    let file = File::create(path)?;
    let mut writer = std::io::BufWriter::new(file);

    for i in 0..100 {
        let hour = 8 + i / 12;
        let minute = (i * 3) % 60;
        let second = (i * 17) % 60;

        let method = methods[i % methods.len()];
        let url = paths[i % paths.len()];
        let status = statuses[i % statuses.len()];

        writeln!(
            writer,
            "2026-07-03 {:02}:{:02}:{:02} {} {} {}",
            hour, minute, second, method, url, status
        )?;
    }

    writer.flush()?;
    Ok(())
}

/// Analyzing Log Files
fn parse_log(path: &str) -> std::io::Result<Vec<LogEntry>> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let mut entries = Vec::new();

    for line_result in reader.lines() {
        let line = line_result?;
        let parts: Vec<&str> = line.split_whitespace().collect();

        if parts.len() >= 4 {
            let entry = LogEntry {
                timestamp: parts[0].to_string() + " " + parts[1],
                method: parts[2].to_string(),
                path: parts[3].to_string(),
                status_code: parts[4].parse().unwrap_or(0),
            };
            entries.push(entry);
        }
    }

    Ok(entries)
}

/// Analyze Log Entries
fn analyze_entries(entries: &[LogEntry]) -> AnalysisResult {
    let total = entries.len() as u32;

    // Statistical Status Codes
    let mut status_counts: HashMap<u16, u32> = HashMap::new();
    let mut method_counts: HashMap<String, u32> = HashMap::new();
    let mut path_counts: HashMap<String, u32> = HashMap::new();

    for entry in entries {
        *status_counts.entry(entry.status_code).or_insert(0) += 1;
        *method_counts.entry(entry.method.clone()).or_insert(0) += 1;
        *path_counts.entry(entry.path.clone()).or_insert(0) += 1;
    }

    // Calculate the error rate (4xx + 5xx)
    let error_count: u32 = status_counts.iter()
        .filter(|(&code, _)| code >= 400)
        .map(|(_, &count)| count)
        .sum();
    let error_rate = if total > 0 {
        error_count as f64 / total as f64 * 100.0
    } else {
        0.0
    };

    // Most Popular Routes
    let mut top_paths: Vec<(String, u32)> = path_counts.into_iter().collect();
    top_paths.sort_by(|a, b| b.1.cmp(&a.1));
    top_paths.truncate(5);

    AnalysisResult {
        total_requests: total,
        status_counts,
        method_counts,
        top_paths,
        error_rate,
    }
}

/// Print the analysis results
fn print_analysis(result: &AnalysisResult) {
    println!("\n--- Analysis Results ---");
    println!("Total Number of Requests: {}", result.total_requests);
    println!("Error Rate: {:.1}%", result.error_rate);

    println!("\nStatus Code Distribution:");
    let mut sorted_status: Vec<_> = result.status_counts.iter().collect();
    sorted_status.sort_by_key(|(_, &c)| std::cmp::Reverse(c));
    for (&code, &count) in &sorted_status {
        let bar = "=".repeat((count as f64 / result.total_requests as f64 * 20.0) as usize);
        println!("  {} {} ({})", code, bar, count);
    }

    println!("\nHTTP Method Distribution:");
    for (method, count) in &result.method_counts {
        println!("  {}: {}", method, count);
    }

    println!("\nPopular Routes (Top 5):");
    for (i, (path, count)) in result.top_paths.iter().enumerate() {
        println!("  {}. {} ({} times)", i + 1, path, count);
    }
}

/// Generate HTML Analysis Report on Formats
fn generate_html_report(
    result: &AnalysisResult,
    path: &str,
) -> std::io::Result<()> {
    let file = File::create(path)?;
    let mut writer = std::io::BufWriter::new(file);

    writeln!(writer, "<!DOCTYPE html>")?;
    writeln!(writer, "<html lang=\"zh\">")?;
    writeln!(writer, "<head><meta charset=\"UTF-8\"><title>Log Analysis Report</title>")?;
    writeln!(writer, "<style>
        body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
        h1 {{ color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }}
        table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
        th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
        th {{ background: #4CAF50; color: white; }}
        .error {{ color: red; font-weight: bold; }}
        .summary {{ background: white; padding: 20px; border-radius: 5px; }}
    </style></head><body>")?;

    writeln!(writer, "<h1>Server Log Analysis Report</h1>")?;
    writeln!(writer, "<div class=\"summary\">")?;
    writeln!(writer, "<p>Total Number of Requests: <strong>{}</strong></p>", result.total_requests)?;
    writeln!(writer, "<p>Error Rate: <strong class=\"error\">{:.1}%</strong></p>", result.error_rate)?;
    writeln!(writer, "</div>")?;

    // Status Code Table
    writeln!(writer, "<h2>Status Code Distribution</h2><table><tr><th>Status Code</th><th>Count</th><th>Percentage</th></tr>")?;
    let mut sorted_status: Vec<_> = result.status_counts.iter().collect();
    sorted_status.sort_by_key(|(_, &c)| std::cmp::Reverse(c));
    for (&code, &count) in &sorted_status {
        let pct = count as f64 / result.total_requests as f64 * 100.0;
        writeln!(writer, "<tr><td>{}</td><td>{}</td><td>{:.1}%</td></tr>", code, count, pct)?;
    }
    writeln!(writer, "</table>")?;

    writeln!(writer, "</body></html>")?;
    writer.flush()?;

    Ok(())
}

/// Clear Temporary Files
fn cleanup(log_path: &str, report_path: &str) -> std::io::Result<()> {
    let log = Path::new(log_path);
    let report = Path::new(report_path);

    if log.exists() {
        std::fs::remove_file(log)?;
    }
    if report.exists() {
        std::fs::remove_file(report)?;
    }

    Ok(())
}

出力:

TEXT
=== Complete Log Analyzer ===

[1/4] The simulation log has been generated: temp_access.log
[2/4] Log parsing complete: 100 records

--- Analysis Results ---
Total Number of Requests: 100
Error Rate: 26.0%

Status Code Distribution:
  200 ============== (33)
  404 ====== (13)
  500 ====== (13)
  401 ====== (7)
  400 ====== (6)
  201 ====== (6)
  204 ====== (6)
  304 ====== (6)
  301 ====== (5)
  403 ====== (5)
  502 ====== (0)

HTTP Method Distribution:
  GET: 29
  POST: 28
  PUT: 25
  DELETE: 18

Popular Routes (Top 5):
  1. /api/users (13 times)
  2. /api/products (12 times)
  3. /index.html (11 times)
  4. /api/orders (11 times)
  5. /login (11 times)

[3/4] HTML The report has been generated: temp_report.html

--- Report Preview(first 20 lines) ---
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>Log Analysis Report</title>
<style>
        body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
        h1 {{ color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }}
        table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
        th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
        th {{ background: #4CAF50; color: white; }}
        .error {{ color: red; font-weight: bold; }}
        .summary {{ background: white; padding: 20px; border-radius: 5px; }}
</style></head><body>
<h1>Server Log Analysis Report</h1>
<div class="summary">
<p>Total Number of Requests: <strong>100</strong></p>
<p>Error Rate: <strong class="error">26.0%</strong></p>
</div>
... (total 49 lines)

[4/4] Temporary files have been cleared.

この包括的な例では、「ログアナライザー」の完全なワークフロー(シミュレーションデータの生成 → ログの解析 → 多次元分析 → HTMLレポートの生成 → クリーンアップ)を示しています。BufReader および BufWriter は、大容量ファイルを扱う際に大幅なパフォーマンス上の利点をもたらします。? オペレータは、すべての I/O 操作が簡潔かつ安全に行われることを保証します。Path メソッドは、クロスプラットフォームでのパス操作を実現します。ログを LogEntry 構造体にパースすることで、その後の分析コードがより明確になり、メンテナンスも容易になります。


(5) ▶ サンプル:題 5:総合演習—CSVの解析と統計(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: File I/O + Error Handling + Statistics
// ============================================

use std::fs;
use std::io::{Write, BufWriter};
use std::path::Path;

struct Record {
    name: String,
    score: u32,
    grade: String,
}

fn parse_csv_line(line: &str) -> Option<Record> {
    let parts: Vec<&str> = line.split(',').collect();
    if parts.len() != 3 { return None; }
    let score = parts[1].trim().parse::<u32>().ok()?;
    Some(Record {
        name: parts[0].trim().to_string(),
        score,
        grade: parts[2].trim().to_string(),
    })
}

fn analyze(records: &[Record]) -> (f64, u32, u32) {
    if records.is_empty() { return (0.0, 0, 0); }
    let sum: u32 = records.iter().map(|r| r.score).sum();
    let avg = sum as f64 / records.len() as f64;
    let max = records.iter().map(|r| r.score).max().unwrap();
    let min = records.iter().map(|r| r.score).min().unwrap();
    (avg, max, min)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let csv_data = "name,score,grade\nAlice,95,A\nBob,72,C\nCharlie,88,B\nDavid,58,F\nEve,91,A";
    let input_path = "temp_scores.csv";
    let report_path = "temp_report.txt";

    fs::write(input_path, csv_data)?;
    println!("CSV Written: {}", input_path);

    let content = fs::read_to_string(input_path)?;
    let records: Vec<Record> = content.lines()
        .skip(1)
        .filter_map(parse_csv_line)
        .collect();

    println!("\n=== Analysis Results ===");
    for r in &records {
        println!("{}: {} points, Level {}", r.name, r.score, r.grade);
    }

    let (avg, max, min) = analyze(&records);
    println!("\nAverage: {:.1}, Highest: {}, Lowest: {}", avg, max, min);

    let a_count = records.iter().filter(|r| r.grade == "A").count();
    let f_count = records.iter().filter(|r| r.grade == "F").count();

    let file = fs::File::create(report_path)?;
    let mut writer = BufWriter::new(file);
    writeln!(writer, "=== Grades Statistics Report ===")?;
    writeln!(writer, "Total number of people: {}", records.len())?;
    writeln!(writer, "Average Score: {:.1}", avg)?;
    writeln!(writer, "Highest Score: {}, Lowest score: {}", max, min)?;
    writeln!(writer, "A Level: {} people, F Level: {} people", a_count, f_count)?;
    writer.flush()?;
    println!("\nThe report has been submitted.: {}", report_path);

    let report = fs::read_to_string(report_path)?;
    println!("\n{}", report);

    fs::remove_file(input_path)?;
    fs::remove_file(report_path)?;
    println!("Temporary files have been cleared.");

    Ok(())
}

出力:

TEXT
CSV Written: temp_scores.csv

=== Analysis Results ===
Alice: 95 points, Level A
Bob: 72 points, Level C
Charlie: 88 points, Level B
David: 58 points, Level F
Eve: 91 points, Level A

Average: 80.8, Highest: 95, Lowest: 58

The report has been submitted.: temp_report.txt

=== Grades Statistics Report ===
Total number of people: 5
Average Score: 80.8
Highest Score: 95, Lowest score: 58
A Level: 2 people, F Level: 1 people

Temporary files have been cleared.

CSVの解析 → 統計処理 → レポート生成 → クリーンアップ――これがファイルI/Oのワークフローを完全に示しています。? すべてのI/Oエラーを伝播します;BufWriter パフォーマンス向上のために書き込みをバッファリングします;filter_map 無効な行を適切に除外します;fs::remove_file 一時ファイルをクリーンアップします。


❓ よくある質問

Q fs::read_to_stringBufReader の違いは何ですか?それぞれどのような場合に使用すべきですか?
A fs::read_to_string はファイル全体を一度にメモリに読み込みます(単純で力業的な方法)。一方、BufReader は行ごとに読み込みます(メモリを節約するため)。 小さなファイル(数MB未満)の場合は、read_to_string を使う方が簡単です。大きなファイル(数百MB以上)の場合は、BufReader を使用する必要があります。そうしないとメモリ不足になります。ログファイルは通常、数百KBから数MB程度ですので、どちらのオプションでも問題ありませんが、BufReader の方が洗練されています。
Q File::createOpenOptions::appendの違いは何ですか?
A File::createは常に新しいファイルを作成します(ファイルが存在する場合は、その内容を消去して上書きします)。一方、OpenOptions::appendはファイルの末尾に内容を追加します。 File::createOpenOptions::new().write(true).create(true).truncate(true)と同等であり、追加モードはOpenOptions::new().append(true).open(path)と同等です。追加モードは通常、ログ記録に使用され、上書きモードは通常、レポートの生成に使用されます。
Q PathPathBuf の違いは何ですか?なぜ2つ必要なのでしょうか?
A Path は不変のパススライス(&str に類似)であり、PathBuf は可変のパス文字列(String に類似)です。 PathBufは連結(.join())や変更(.push())が可能で、所有権の譲渡も行えますが、Pathは読み取り専用です。この2つの関係は、String&strの関係に似ています: PathBuf がデータを所有し、Path は借用参照です。関数の引数には通常 &Path(または AsRef<Path>)が使用され、変数は PathBuf を使用して格納されます。
Q ファイルI/Oにおいて、?演算子はどのように動作しますか?
A ?演算子はResultの値をチェックします。その値がOk(val)の場合はvalを抽出し、Err(e)の場合はErr(e.into())を早期に返します。 ファイルI/Oでは、ほぼすべての操作が Result<T, io::Error> を返します。? を使用すると、コードから match のネストを排除できます。具体的なエラーの詳細を気にしない場合は ? を、エラーの種類ごとに異なる処理が必要な場合は match を使用してください。
Q ファイル操作が完了した後、手動でファイルを閉じる必要がありますか?
A いいえ。Rustでは、Filedrop(スコープ外に出る)際に自動的にファイルを閉じます。Rustの所有権システムにより、File変数がスコープ外に出ると、Dropトレイトのdropメソッドが自動的に呼び出され、ファイルハンドルが閉じられます。同様に、BufWriterdrop時に自動的にバッファをflushします。(ファイルが閉じられる前にデータがディスクに書き込まれることを確実にしたい場合は)flush() を使用して手動でバッファをフラッシュするようにしてください。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐note.txt という名前のファイルを作成し、そのファイルに(writeln! マクロを使用して)3つの段落を書き込んだ後、ファイルの内容を読み込んで出力するプログラムを作成してください。最後に、そのファイルを削除してください。エラー処理には ? 演算子を使用し、main 関数を使用して std::io::Result<()> を返してください。

  2. 難易度 ⭐⭐: 「CSVリーダー」プログラムを作成してください。以下の内容を含むCSVファイルを作成してください(模擬データ):

    TEXT
    Name,Age,City
     Alice,28,New York
     Bob,32,London
     Charlie,25,Tokyo
    

    BufReader を使用して、1行ずつ読み込み、最初の行(ヘッダー)をスキップし、各行を解析して平均年齢を算出してください。結果を result.txt に書き出してください。最後に、すべての一時ファイルを削除してください。

  3. 難易度 ⭐⭐⭐:「ファイル検索および統計」ツールを作成してください。std::fs::read_dir を使用して指定されたディレクトリを再帰的にスキャンし、各ファイル拡張子ごとのファイル数をカウントします。カウント数の多い順に並べ替えたレポートを生成し、summary.txt に書き出してください。要件:(1) パスの処理には Path および PathBuf を使用すること;(2) レポートの出力には BufWriter を使用すること;(3) エラーの伝播には ? を使用すること;(4) 出力形式は、ファイル拡張子、件数、割合、および ASCII 棒グラフを含む表とする。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%