C/C++ Arena

Step 7 of 7

Challenge: a log formatter

Every service writes logs, and consistent, sortable, greppable log lines matter when you're debugging production at 3 a.m. This challenge combines the module's tools: chrono to break a duration into parts, and std::format to lay them out.

Breaking a duration into fields works like breaking seconds into minutes and seconds, one unit at a time:

#include <chrono>
#include <format>
#include <iostream>
#include <string>

using namespace std::chrono_literals;

std::string pretty(std::chrono::seconds t) {
    auto d = std::chrono::duration_cast<std::chrono::days>(t);
    t -= d;
    auto h = std::chrono::duration_cast<std::chrono::hours>(t);
    t -= h;
    auto m = std::chrono::duration_cast<std::chrono::minutes>(t);
    t -= m;
    return std::format("{}d {:02}h {:02}m {:02}s", d.count(), h.count(), m.count(), t.count());
}

int main() {
    std::cout << pretty(93784s) << "\n";
    std::cout << pretty(59s) << "\n";
    std::cout << std::format("[{:<5}] boot\n[{:<5}] boot\n", "INFO", "ERROR");
}
1d 02h 03m 04s
0d 00h 00m 59s
[INFO ] boot
[ERROR] boot

The steps

  1. duration_cast to the biggest unit to get the whole number of that unit.
  2. Subtract it from the remaining time.
  3. Repeat with the next smaller unit.

What's left at the end is the smallest unit. For your log line, go hours, minutes, seconds, then what remains is milliseconds (pad to 3 digits with {:03}). Hours are not split into days, so they can exceed 24.

The level name

A switch over the enum class returns the name, and {:<5} pads INFO and WARN to five characters, so the messages line up in a column no matter the level.

Your turn: write std::string log_line(Level level, std::chrono::milliseconds uptime, std::string_view msg) producing:

[HH:MM:SS.mmm] LEVEL message

Example: log_line(Level::Warn, 65250ms, "disk 91%") is [00:01:05.250] WARN disk 91%.

Previous: Random numbers with <random>