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 task

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%.

Challenge 2

Write std::string progress_bar(double fraction, int width) that draws [##### ] 50%: width characters between the brackets, the first floor(fraction * width) of them # and the rest spaces, then a space and the whole percentage right-aligned in 3 characters followed by %. Clamp fraction to 0 to 1 first. Use std::format with a dynamic width.

Challenge 3

Write std::string report(const std::vector<std::pair<std::string, std::chrono::milliseconds>>& tasks): one line per task, the name left-aligned in 10 characters and the time in seconds with 3 decimals right-aligned in 7, then s, and finally a total line in the same layout. Every line ends in \n.

Previous: Random numbers with <random>