I/O, Filesystem, and Formatting
Overview
C++ provides comprehensive facilities for input/output operations, file system manipulation, and modern string formatting. This chapter covers everything from basic I/O to C++20’s formatting library.
┌────────────────────────────────────────────────────────┐
│ I/O AND FILESYSTEM TOOLS │
├────────────────────────────────────────────────────────┤
│ │
│ STREAMS │ FILESYSTEM │ FORMATTING │
│ ─────── │ ────────── │ ────────── │
│ • iostream │ • std::path │ • std::format │
│ • fstream │ • directory │ • printf-like │
│ • sstream │ • operations │ • Type-safe │
│ • manipulators │ • permissions │ • Custom types │
│ │
│ C++11: Move semantics for streams │
│ C++17: std::filesystem │
│ C++20: std::format, std::span │
│ C++23: std::print, formatting improvements │
│ │
└────────────────────────────────────────────────────────┘
Stream Basics
Standard Streams
#include <iostream>
int main() {
// Standard output stream
std::cout << "Hello, World!" << std::endl;
// Standard error stream (unbuffered)
std::cerr << "Error message" << std::endl;
// Standard log stream (buffered)
std::clog << "Log message" << std::endl;
// Standard input stream
int number;
std::cout << "Enter a number: ";
std::cin >> number;
return 0;
}
Stream State Flags
#include <iostream>
int main() {
int value;
std::cin >> value;
// Check stream state
if (std::cin.good()) {
std::cout << "Stream is good\n";
}
if (std::cin.fail()) {
std::cout << "Input failed\n";
std::cin.clear(); // Clear error flags
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
if (std::cin.eof()) {
std::cout << "End of file reached\n";
}
if (std::cin.bad()) {
std::cout << "Fatal stream error\n";
}
return 0;
}
Stream State Diagram
Stream States:
┌────────────────────────────────────────────────────────┐
│ │
│ goodbit (0) ──► All OK, ready for I/O │
│ eofbit ──► End-of-file reached │
│ failbit ──► I/O operation failed (recoverable) │
│ badbit ──► Fatal error (stream corrupted) │
│ │
│ good() → returns !fail() │
│ eof() → returns eofbit set │
│ fail() → returns failbit or badbit set │
│ bad() → returns badbit set │
│ │
└────────────────────────────────────────────────────────┘
File I/O
Reading and Writing Files
#include <fstream>
#include <iostream>
#include <string>
int main() {
// Writing to file
{
std::ofstream out("output.txt");
if (out.is_open()) {
out << "Hello, File!" << std::endl;
out << "Line 2" << std::endl;
out << 42 << " " << 3.14 << std::endl;
}
// File automatically closed when out goes out of scope
}
// Reading from file
{
std::ifstream in("output.txt");
if (in.is_open()) {
std::string line;
while (std::getline(in, line)) {
std::cout << line << std::endl;
}
}
}
// Read/write mode
{
std::fstream file("data.txt", std::ios::in | std::ios::out);
if (file.is_open()) {
// Read and write operations
}
}
return 0;
}
File Open Modes
#include <fstream>
int main() {
// Open modes
std::ofstream out1("file.txt", std::ios::out); // Write (default)
std::ofstream out2("file.txt", std::ios::app); // Append
std::ofstream out3("file.txt", std::ios::trunc); // Truncate
std::ifstream in("file.txt", std::ios::in); // Read (default)
std::fstream file1("file.txt", std::ios::in | std::ios::out); // R/W
std::fstream file2("file.txt", std::ios::binary); // Binary mode
std::fstream file3("file.txt", std::ios::ate); // Start at end
return 0;
}
Binary File I/O
#include <fstream>
#include <vector>
#include <cstring>
struct Record {
char name[50];
int age;
double salary;
};
int main() {
// Write binary data
{
std::ofstream out("data.bin", std::ios::binary);
Record rec;
std::strcpy(rec.name, "John Doe");
rec.age = 30;
rec.salary = 50000.0;
out.write(reinterpret_cast<const char*>(&rec), sizeof(Record));
}
// Read binary data
{
std::ifstream in("data.bin", std::ios::binary);
Record rec;
in.read(reinterpret_cast<char*>(&rec), sizeof(Record));
std::cout << "Name: " << rec.name << std::endl;
std::cout << "Age: " << rec.age << std::endl;
std::cout << "Salary: " << rec.salary << std::endl;
}
// Write array
{
std::vector<int> data = {1, 2, 3, 4, 5};
std::ofstream out("array.bin", std::ios::binary);
out.write(reinterpret_cast<const char*>(data.data()),
data.size() * sizeof(int));
}
return 0;
}
File Position Operations
#include <fstream>
int main() {
std::fstream file("data.txt", std::ios::in | std::ios::out);
// Get current position
std::streampos pos = file.tellg(); // Input position
std::streampos pos2 = file.tellp(); // Output position
// Seek to position
file.seekg(0, std::ios::beg); // Beginning
file.seekg(0, std::ios::end); // End
file.seekg(10, std::ios::cur); // 10 bytes forward from current
// Get file size
file.seekg(0, std::ios::end);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
return 0;
}
String Streams
std::stringstream
#include <sstream>
#include <string>
int main() {
// Output string stream
std::ostringstream oss;
oss << "Number: " << 42 << ", Pi: " << 3.14;
std::string result = oss.str();
// Input string stream
std::istringstream iss("42 3.14 hello");
int num;
double pi;
std::string word;
iss >> num >> pi >> word;
// Bidirectional string stream
std::stringstream ss;
ss << "Value: " << 100;
std::string str = ss.str();
// Clear and reuse
ss.str(""); // Clear contents
ss.clear(); // Clear state flags
ss << "New value";
return 0;
}
String Stream Use Cases
#include <sstream>
#include <vector>
// Convert to string
template<typename T>
std::string to_string_custom(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
// Parse CSV line
std::vector<std::string> parse_csv(const std::string& line) {
std::vector<std::string> result;
std::istringstream iss(line);
std::string field;
while (std::getline(iss, field, ',')) {
result.push_back(field);
}
return result;
}
// Concatenate with formatting
std::string build_message(int code, const std::string& msg) {
std::ostringstream oss;
oss << "[Error " << code << "] " << msg;
return oss.str();
}
Stream Manipulators
Standard Manipulators
#include <iostream>
#include <iomanip>
int main() {
// Boolean formatting
std::cout << std::boolalpha << true << std::endl; // "true"
std::cout << std::noboolalpha << true << std::endl; // "1"
// Integer base
std::cout << std::dec << 42 << std::endl; // Decimal: 42
std::cout << std::hex << 42 << std::endl; // Hex: 2a
std::cout << std::oct << 42 << std::endl; // Octal: 52
// Show base prefix
std::cout << std::showbase << std::hex << 42 << std::endl; // 0x2a
// Floating point
std::cout << std::fixed << 3.14159 << std::endl; // Fixed notation
std::cout << std::scientific << 3.14159 << std::endl; // Scientific
std::cout << std::defaultfloat << 3.14159 << std::endl;
// Precision
std::cout << std::setprecision(2) << 3.14159 << std::endl; // 3.14
// Width and fill
std::cout << std::setw(10) << 42 << std::endl; // " 42"
std::cout << std::setfill('0') << std::setw(5) << 42 << std::endl; // "00042"
// Alignment
std::cout << std::left << std::setw(10) << "left" << std::endl;
std::cout << std::right << std::setw(10) << "right" << std::endl;
return 0;
}
Creating Custom Manipulators
#include <iostream>
// Simple manipulator (no parameters)
std::ostream& bold(std::ostream& os) {
return os << "\033[1m";
}
std::ostream& reset(std::ostream& os) {
return os << "\033[0m";
}
// Manipulator with parameters
struct color {
int code;
color(int c) : code(c) {}
friend std::ostream& operator<<(std::ostream& os, const color& c) {
return os << "\033[38;5;" << c.code << "m";
}
};
int main() {
std::cout << bold << "Bold text" << reset << std::endl;
std::cout << color(196) << "Red text" << reset << std::endl;
return 0;
}
std::filesystem (C++17)
Path Operations
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// Create path
fs::path p1 = "/usr/local/bin";
fs::path p2 = "C:\\Users\\Name\\Documents";
fs::path p3 = p1 / "program"; // Path concatenation
// Path components
fs::path file = "/home/user/data/file.txt";
std::cout << "Root: " << file.root_name() << std::endl;
std::cout << "Parent: " << file.parent_path() << std::endl;
std::cout << "Filename: " << file.filename() << std::endl;
std::cout << "Stem: " << file.stem() << std::endl; // "file"
std::cout << "Extension: " << file.extension() << std::endl; // ".txt"
// Path manipulation
fs::path p = "/home/user";
p /= "documents"; // Append
p.replace_filename("new.txt");
p.replace_extension(".dat");
// Path queries
if (file.has_filename()) { /* ... */ }
if (file.has_extension()) { /* ... */ }
if (file.is_absolute()) { /* ... */ }
if (file.is_relative()) { /* ... */ }
return 0;
}
File Operations
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
int main() {
// Check existence
if (fs::exists("file.txt")) {
std::cout << "File exists\n";
}
// File size
uintmax_t size = fs::file_size("file.txt");
std::cout << "Size: " << size << " bytes\n";
// File type
if (fs::is_regular_file("file.txt")) { /* ... */ }
if (fs::is_directory("folder")) { /* ... */ }
if (fs::is_symlink("link")) { /* ... */ }
// Copy file
fs::copy("source.txt", "dest.txt");
fs::copy("source.txt", "dest.txt", fs::copy_options::overwrite_existing);
// Rename/move
fs::rename("old.txt", "new.txt");
// Remove
fs::remove("file.txt"); // Remove file
fs::remove_all("directory"); // Remove directory recursively
// Create directory
fs::create_directory("new_folder");
fs::create_directories("path/to/nested/folder");
// Current path
fs::path current = fs::current_path();
fs::current_path("/tmp"); // Change current directory
return 0;
}
Directory Iteration
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// Iterate directory (non-recursive)
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << std::endl;
if (entry.is_regular_file()) {
std::cout << " File, size: " << entry.file_size() << std::endl;
} else if (entry.is_directory()) {
std::cout << " Directory" << std::endl;
}
}
// Recursive iteration
for (const auto& entry : fs::recursive_directory_iterator(".")) {
std::cout << entry.path() << " (depth: "
<< entry.depth() << ")" << std::endl;
}
// Filtered iteration
for (const auto& entry : fs::directory_iterator(".")) {
if (entry.path().extension() == ".txt") {
std::cout << "Text file: " << entry.path() << std::endl;
}
}
return 0;
}
File Permissions
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path file = "example.txt";
// Get permissions
fs::perms p = fs::status(file).permissions();
// Check specific permissions
bool owner_read = (p & fs::perms::owner_read) != fs::perms::none;
bool owner_write = (p & fs::perms::owner_write) != fs::perms::none;
bool owner_exec = (p & fs::perms::owner_exec) != fs::perms::none;
// Set permissions
fs::permissions(file,
fs::perms::owner_all | fs::perms::group_read,
fs::perm_options::replace);
// Add permissions
fs::permissions(file,
fs::perms::owner_write,
fs::perm_options::add);
// Remove permissions
fs::permissions(file,
fs::perms::group_write,
fs::perm_options::remove);
return 0;
}
File Time Operations
#include <filesystem>
#include <chrono>
namespace fs = std::filesystem;
int main() {
fs::path file = "example.txt";
// Get last write time
auto ftime = fs::last_write_time(file);
// Convert to system time
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - fs::file_time_type::clock::now() +
std::chrono::system_clock::now()
);
std::time_t cftime = std::chrono::system_clock::to_time_t(sctp);
std::cout << "Last modified: " << std::ctime(&cftime);
// Set last write time
fs::last_write_time(file, fs::file_time_type::clock::now());
return 0;
}
std::format (C++20)
Basic Formatting
#include <format>
#include <string>
#include <iostream>
int main() {
// Basic formatting
std::string s1 = std::format("Hello, {}!", "World");
std::string s2 = std::format("Number: {}", 42);
std::string s3 = std::format("Pi: {}", 3.14159);
// Multiple arguments
std::string s4 = std::format("{} + {} = {}", 2, 3, 2+3);
// Positional arguments
std::string s5 = std::format("{1} {0}", "World", "Hello"); // "Hello World"
// Named arguments (C++20)
std::string s6 = std::format("{0} is {1} years old", "Alice", 25);
std::cout << s1 << std::endl;
return 0;
}
Format Specifications
#include <format>
#include <iostream>
int main() {
// Width and alignment
std::cout << std::format("{:10}", "left") << std::endl; // "left "
std::cout << std::format("{:<10}", "left") << std::endl; // "left "
std::cout << std::format("{:>10}", "right") << std::endl; // " right"
std::cout << std::format("{:^10}", "center") << std::endl; // " center "
// Fill character
std::cout << std::format("{:*<10}", "left") << std::endl; // "left******"
std::cout << std::format("{:0>5}", 42) << std::endl; // "00042"
// Sign
std::cout << std::format("{:+}", 42) << std::endl; // "+42"
std::cout << std::format("{: }", 42) << std::endl; // " 42"
// Number base
std::cout << std::format("{:x}", 255) << std::endl; // "ff"
std::cout << std::format("{:X}", 255) << std::endl; // "FF"
std::cout << std::format("{:#x}", 255) << std::endl; // "0xff"
std::cout << std::format("{:o}", 64) << std::endl; // "100"
std::cout << std::format("{:b}", 5) << std::endl; // "101"
// Floating point
std::cout << std::format("{:.2f}", 3.14159) << std::endl; // "3.14"
std::cout << std::format("{:.2e}", 3.14159) << std::endl; // "3.14e+00"
std::cout << std::format("{:.2g}", 3.14159) << std::endl; // "3.1"
// Precision and width
std::cout << std::format("{:10.2f}", 3.14159) << std::endl; // " 3.14"
return 0;
}
Custom Type Formatting
#include <format>
#include <iostream>
struct Point {
int x, y;
};
// Specialize std::formatter for Point
template<>
struct std::formatter<Point> {
constexpr auto parse(std::format_parse_context& ctx) {
return ctx.begin();
}
auto format(const Point& p, std::format_context& ctx) const {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
Point p{10, 20};
std::cout << std::format("Point: {}", p) << std::endl;
// Output: "Point: (10, 20)"
return 0;
}
std::print (C++23)
#include <print>
int main() {
// Direct printing (no need to create string)
std::print("Hello, {}!\n", "World");
std::print("Number: {}\n", 42);
// Print to file
std::print(std::cerr, "Error: {}\n", "Something went wrong");
// Equivalent to format + cout
// std::print(format_string, args...) == std::cout << std::format(...)
return 0;
}
Formatting Comparison
┌───────────────────────────────────────────────────────┐
│ Formatting Methods Comparison │
├───────────────────────────────────────────────────────┤
│ Method │ Type Safety │ Performance │ C++ Ver │
├─────────────────┼─────────────┼─────────────┼─────────┤
│ printf │ No │ Fast │ C │
│ std::cout << │ Yes │ Slow │ C++98 │
│ std::format │ Yes │ Fast │ C++20 │
│ std::print │ Yes │ Fast │ C++23 │
│ │
│ Recommendation: Use std::format/print in modern C++ │
└───────────────────────────────────────────────────────┘
Performance Tips
Buffering
#include <iostream>
int main() {
// Unbuffered (slow)
for (int i = 0; i < 10000; ++i) {
std::cout << i << std::endl; // Flushes every iteration
}
// Buffered (fast)
for (int i = 0; i < 10000; ++i) {
std::cout << i << '\n'; // No flush
}
std::cout << std::flush; // Single flush at end
// Disable sync with C stdio (faster)
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return 0;
}
Memory-Mapped Files (Platform-Specific)
// For very large files, consider memory-mapped I/O
// Platform-specific (POSIX mmap, Windows CreateFileMapping)
Best Practices
1. Always Check Stream State
std::ifstream file("data.txt");
if (!file.is_open()) {
std::cerr << "Failed to open file\n";
return 1;
}
2. Use RAII for Files
// GOOD: Automatic cleanup
{
std::ofstream file("data.txt");
file << "data";
} // File closed automatically
// BAD: Manual management
FILE* f = fopen("data.txt", "w");
fprintf(f, "data");
fclose(f); // Easy to forget
3. Prefer std::filesystem
// GOOD: Portable, safe
fs::path p = fs::current_path() / "file.txt";
if (fs::exists(p)) { /* ... */ }
// BAD: Platform-specific, unsafe
#ifdef _WIN32
// Windows code
#else
// POSIX code
#endif
4. Use std::format for Complex Strings
// GOOD: Clear, type-safe
auto msg = std::format("User {} logged in at {}", name, time);
// BAD: Error-prone
char buffer[256];
sprintf(buffer, "User %s logged in at %d", name.c_str(), time);
Common Patterns
Reading Entire File
#include <fstream>
#include <string>
#include <sstream>
// Read as string
std::string read_file(const std::string& filename) {
std::ifstream file(filename);
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
// Read lines
std::vector<std::string> read_lines(const std::string& filename) {
std::ifstream file(filename);
std::vector<std::string> lines;
std::string line;
while (std::getline(file, line)) {
lines.push_back(line);
}
return lines;
}
Safe File Writing
void write_file_safe(const std::string& filename, const std::string& data) {
std::string temp = filename + ".tmp";
// Write to temporary file
{
std::ofstream out(temp);
if (!out) throw std::runtime_error("Cannot write to temp file");
out << data;
if (!out) throw std::runtime_error("Write failed");
}
// Rename (atomic on most systems)
fs::rename(temp, filename);
}
Directory Tree Walking
void process_directory_tree(const fs::path& dir) {
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
try {
if (entry.is_regular_file()) {
std::cout << "File: " << entry.path()
<< " (" << entry.file_size() << " bytes)\n";
} else if (entry.is_directory()) {
std::cout << "Dir: " << entry.path() << "\n";
}
} catch (const fs::filesystem_error& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}
}
Complete Practical Example: Log File Analyzer
Here’s a comprehensive example integrating file I/O, filesystem operations, and formatting:
#include <iostream>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <string>
#include <vector>
#include <map>
#include <regex>
#include <chrono>
#include <iomanip>
#include <format> // C++20
namespace fs = std::filesystem;
// Log entry structure
struct LogEntry {
std::string timestamp;
std::string level;
std::string message;
std::string source_file;
int line_number;
};
// Log analyzer class
class LogAnalyzer {
private:
std::map<std::string, int> level_counts_;
std::map<std::string, int> file_counts_;
std::vector<LogEntry> error_entries_;
size_t total_lines_ = 0;
public:
// Parse log line
bool parse_line(const std::string& line, LogEntry& entry) {
// Format: [2024-12-04 10:30:45] [ERROR] [file.cpp:123] Message here
std::regex pattern(R"(\[([^\]]+)\]\s*\[(\w+)\]\s*\[([^:]+):(\d+)\]\s*(.+))");
std::smatch matches;
if (std::regex_match(line, matches, pattern)) {
entry.timestamp = matches[1];
entry.level = matches[2];
entry.source_file = matches[3];
entry.line_number = std::stoi(matches[4]);
entry.message = matches[5];
return true;
}
return false;
}
// Analyze a log file
void analyze_file(const fs::path& file_path) {
std::ifstream file(file_path);
if (!file.is_open()) {
std::cerr << "Failed to open: " << file_path << "\n";
return;
}
std::string line;
while (std::getline(file, line)) {
total_lines_++;
LogEntry entry;
if (parse_line(line, entry)) {
// Count by level
level_counts_[entry.level]++;
// Count by source file
file_counts_[entry.source_file]++;
// Store errors for detailed report
if (entry.level == "ERROR" || entry.level == "CRITICAL") {
error_entries_.push_back(entry);
}
}
}
}
// Analyze all log files in directory
void analyze_directory(const fs::path& dir_path) {
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
std::cerr << "Invalid directory: " << dir_path << "\n";
return;
}
std::cout << "Analyzing logs in: " << dir_path << "\n\n";
// Iterate through .log files
for (const auto& entry : fs::directory_iterator(dir_path)) {
if (entry.is_regular_file() && entry.path().extension() == ".log") {
std::cout << "Processing: " << entry.path().filename() << "\n";
analyze_file(entry.path());
}
}
}
// Generate formatted report
void generate_report(const std::string& output_file) {
std::ofstream out(output_file);
if (!out.is_open()) {
std::cerr << "Failed to create report file\n";
return;
}
// Write header with formatting
out << std::string(60, '=') << "\n";
out << std::setw(30) << "LOG ANALYSIS REPORT" << "\n";
out << std::string(60, '=') << "\n\n";
// Current timestamp
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
out << "Generated: " << std::put_time(std::localtime(&time),
"%Y-%m-%d %H:%M:%S") << "\n\n";
// Total lines
out << "Total log lines processed: " << total_lines_ << "\n\n";
// Log level distribution
out << "=== Log Level Distribution ===\n";
out << std::left;
for (const auto& [level, count] : level_counts_) {
double percentage = (100.0 * count) / total_lines_;
out << std::setw(15) << level
<< std::setw(8) << count
<< std::fixed << std::setprecision(2)
<< "(" << percentage << "%)\n";
}
out << "\n";
// Top source files
out << "=== Top Source Files ===\n";
// Convert to vector and sort
std::vector<std::pair<std::string, int>> sorted_files(
file_counts_.begin(), file_counts_.end()
);
std::sort(sorted_files.begin(), sorted_files.end(),
[](const auto& a, const auto& b) {
return a.second > b.second;
});
int top_n = std::min(10, (int)sorted_files.size());
for (int i = 0; i < top_n; ++i) {
out << std::setw(30) << sorted_files[i].first
<< std::setw(8) << sorted_files[i].second << " entries\n";
}
out << "\n";
// Error details
if (!error_entries_.empty()) {
out << "=== Error Details ===\n";
for (const auto& entry : error_entries_) {
out << "[" << entry.timestamp << "] "
<< entry.level << " in "
<< entry.source_file << ":" << entry.line_number << "\n"
<< " " << entry.message << "\n\n";
}
}
out << std::string(60, '=') << "\n";
std::cout << "\nReport generated: " << output_file << "\n";
}
// Export to CSV
void export_to_csv(const std::string& csv_file) {
std::ofstream out(csv_file);
if (!out.is_open()) {
std::cerr << "Failed to create CSV file\n";
return;
}
// CSV header
out << "Timestamp,Level,Source File,Line,Message\n";
// CSV data (escape commas in messages)
for (const auto& entry : error_entries_) {
// Escape message if it contains comma or quote
std::string escaped_msg = entry.message;
if (escaped_msg.find(',') != std::string::npos ||
escaped_msg.find('"') != std::string::npos) {
escaped_msg = "\"" + escaped_msg + "\"";
}
out << entry.timestamp << ","
<< entry.level << ","
<< entry.source_file << ","
<< entry.line_number << ","
<< escaped_msg << "\n";
}
std::cout << "CSV exported: " << csv_file << "\n";
}
// Print statistics to console with formatting
void print_statistics() {
std::cout << "\n" << std::string(50, '=') << "\n";
std::cout << "STATISTICS SUMMARY\n";
std::cout << std::string(50, '=') << "\n";
std::cout << std::format("Total lines: {:L}\n", total_lines_);
std::cout << "\nBy Level:\n";
for (const auto& [level, count] : level_counts_) {
double pct = (100.0 * count) / total_lines_;
std::cout << std::format(" {:<10} {:>6} ({:>5.1f}%)\n",
level, count, pct);
}
std::cout << std::format("\nErrors/Critical: {}\n", error_entries_.size());
std::cout << std::string(50, '=') << "\n";
}
};
// Create sample log files for demonstration
void create_sample_logs(const fs::path& dir) {
// Create directory if it doesn't exist
fs::create_directories(dir);
// Sample log 1
{
std::ofstream log1(dir / "app.log");
log1 << "[2024-12-04 10:00:00] [INFO] [main.cpp:10] Application started\n";
log1 << "[2024-12-04 10:00:05] [DEBUG] [init.cpp:25] Initializing modules\n";
log1 << "[2024-12-04 10:00:10] [WARNING] [config.cpp:45] Config file not found, using defaults\n";
log1 << "[2024-12-04 10:00:15] [ERROR] [database.cpp:88] Connection failed: timeout\n";
log1 << "[2024-12-04 10:00:20] [INFO] [main.cpp:50] Retrying connection\n";
}
// Sample log 2
{
std::ofstream log2(dir / "errors.log");
log2 << "[2024-12-04 10:01:00] [ERROR] [network.cpp:120] Socket error: connection refused\n";
log2 << "[2024-12-04 10:02:00] [CRITICAL] [memory.cpp:200] Out of memory\n";
log2 << "[2024-12-04 10:03:00] [ERROR] [parser.cpp:75] Invalid JSON format\n";
}
std::cout << "Sample log files created in: " << dir << "\n\n";
}
// Backup log files
void backup_logs(const fs::path& src_dir, const fs::path& backup_dir) {
fs::create_directories(backup_dir);
for (const auto& entry : fs::directory_iterator(src_dir)) {
if (entry.is_regular_file() && entry.path().extension() == ".log") {
// Create timestamped backup
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::ostringstream backup_name;
backup_name << entry.path().stem().string() << "_"
<< std::put_time(std::localtime(&time), "%Y%m%d_%H%M%S")
<< entry.path().extension().string();
fs::path backup_path = backup_dir / backup_name.str();
fs::copy_file(entry.path(), backup_path,
fs::copy_options::overwrite_existing);
std::cout << "Backed up: " << entry.path().filename()
<< " -> " << backup_path.filename() << "\n";
}
}
}
int main() {
std::cout << "=== Log File Analyzer ===\n\n";
// Setup directories
fs::path log_dir = "sample_logs";
fs::path backup_dir = "backup_logs";
fs::path report_dir = "reports";
// Create sample logs
create_sample_logs(log_dir);
// Analyze logs
LogAnalyzer analyzer;
analyzer.analyze_directory(log_dir);
// Print statistics
analyzer.print_statistics();
// Generate reports
fs::create_directories(report_dir);
analyzer.generate_report(report_dir / "analysis_report.txt");
analyzer.export_to_csv(report_dir / "errors.csv");
// Backup logs
backup_logs(log_dir, backup_dir);
// Show directory sizes
std::cout << "\n=== Directory Sizes ===\n";
for (const auto& dir : {log_dir, backup_dir, report_dir}) {
if (fs::exists(dir)) {
uintmax_t total_size = 0;
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
if (entry.is_regular_file()) {
total_size += entry.file_size();
}
}
std::cout << std::format("{:<15} {:>8} bytes\n",
dir.string(), total_size);
}
}
std::cout << "\n=== Analysis Complete ===\n";
return 0;
}
Concepts Demonstrated:
- File I/O:
std::ifstreamfor readingstd::ofstreamfor writingstd::getline()for line-by-line reading- Binary vs text mode
- std::filesystem:
directory_iteratorfor listing filescreate_directories()for directory creationcopy_file()for backupsfile_size()andis_regular_file()- Path manipulation
- Formatting:
std::format(C++20) for modern formattingstd::setw,std::setprecisionfor alignmentstd::put_timefor timestamp formatting- CSV generation with escaping
- String streams:
std::ostringstreamfor building stringsstd::istringstreamfor parsing
- Regex: Pattern matching for log parsing
- Stream manipulators: Formatting output
This example shows real-world file and filesystem operations!
Next Steps
- Next: Exception Handling →
- Previous: ← Async Programming
Part 16 of 22 - I/O, Filesystem, and Formatting