Overview
StatusbarLog is a C++ utility for simultaneous logging and multiple stacked statusbar displays in terminal applications.
Features:
- Multiple stacked statusbars with configurable text, sizes, and positions
- Logging with severity levels:
ERROR, WARN, INFO, DEBUG
- Spinner animation for "busy" statusbars
- Cursor manipulation so log messages and statusbars do not overwrite each other
- Cross-platform design goals
Usage Example
Example code snippet (from docs/example/main.cpp):
#include <cstddef>
#include <iostream>
#include <string>
#include <thread>
#include "statusbarlog/statusbarlog.h"
int main() {
std::cout << "Start to be kept <- " << std::flush;
std::cout << "Temporary message that might be long" << std::flush;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Clean message" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
statusbar_log::LogInf(
kFilename,
"Starting test...");
std::cout << "\n\n";
int err = 0;
const int total_steps1 = 15;
const int total_steps2 = 100;
statusbar_log::StatusbarHandle h;
h, {2, 1},
{20, 10},
{"first: ", "second: "},
{" -- 15 total steps", " -- 100 total steps"}
);
if (err != 0) {
statusbar_log::LogErr(
kFilename,
"Failed to create statusbar. Errorcode %d", err);
return err;
}
for (std::size_t i = 0; i <= total_steps1; ++i) {
double percent = static_cast<double>(i) / total_steps1 * 100.0;
if (i % 10 == 0 && i != 0) {
statusbar_log::LogInf(
kFilename,
"10 Ticks reached");
}
for (std::size_t j = 0; j <= total_steps2; ++j) {
double percent = static_cast<double>(j) / total_steps2 * 100.0;
std::this_thread::sleep_for(std::chrono::milliseconds(30));
}
}
if (err != 0) {
statusbar_log::LogErr(
kFilename,
"Failed to destroy statusbar. Errorcode %d", err);
return err;
}
return 0;
}
int UpdateStatusbar(StatusbarHandle &statusbar_handle, const std::size_t idx, const double percent)
void SaveCursorPosition()
int CreateStatusbarHandle(StatusbarHandle &statusbar_handle, const std::vector< unsigned int > _positions, const std::vector< unsigned int > _bar_sizes, const std::vector< std::string > _prefixes, const std::vector< std::string > _postfixes)
void RestoreCursorPosition()
int DestroyStatusbarHandle(StatusbarHandle &statusbar_handle)
const std::string kFilename
Brief explenation
- Set compile-time log level (optional)
The log level is set by CMake when generating the header. This ensure only log messages with higher or equal log priority (ERROR, WARNING, INFO, DEBUG) to the log level will be printed. Override via: cmake -DSTATUSBARLOG_LOG_LEVEL=kLogLevelWrn ...
(all options: kLogLevelDbg, kLogLevelInf, kLogLevelWrn, kLogLevelErr, kLogLevelOff)
- Define filename for every cpp file in which you want to log
const std::string
kFilename =
"StatusbarLog_main.cpp";
- Now a simple log message can be done like:
statusbarlog::LogDbg(
kFilename,
"Funny debug message");
statusbarlog::LogInf(
kFilename,
"Starting test...");
statusbarlog::LogWrn(
kFilename,
"Couldn't obtain viscosity. Using 1.6e-5 m^2/s");
statusbarlog::LogErr(
kFilename,
"Failed to compute rhs");
- Create a stacked statusbar (here: two statusbars ontop of each other)
statusbarlog::StatusbarHandle handle;
std::vector<unsigned int>
positions = {2, 1};
std::vector<unsigned int>
bar_sizes = {20, 10};
std::vector<std::string>
prefixes = {
"first",
"second"};
std::vector<std::string>
postfixes = {
"20 long",
"10 long"};
int err_code = statusbarlog::CreateStatusbarHandle(
std::vector< unsigned int > positions
Vertical positions (1=topmost).
std::vector< unsigned int > bar_sizes
Total width (characters) of each bar.
std::vector< std::string > prefixes
Text displayed before each bar.
std::vector< std::string > postfixes
Text displayed after each bar.
- Updating a statusbar
statusbarlog::UpdateStatusbar(handle, 0, percent);
statusbarlog::UpdateStatusbar(handle, 1, percent);
Note: For printing the statusbar the first time just use the percentage 0.
- Log while updating
statusbarlog::LogInf(
kFilename,
"10 ticks reached");
The log messages now nicely display above the statusbar.
- Cleanup
int err_code = statusbarlog::DestroyStatusbarHandle(handle);
Building
prerequisites
All Platforms:
- C++20 capable compiler (Clang, GCC, or MSVC)
- CMake >= 3.20
All Platforms:
For Doxygen documentation:
- doxygen
- graphviz (for diagrams)
Linux (Debian/Ubuntu):
sudo apt install build-essential cmake
For Doxygen documentation:
sudo apt install doxygen graphviz
Linux (Arch):
sudo pacman -S base-devel cmake
For Doxygen documentation:
sudo pacman -S base-devel doxygen graphviz
Windows:
- Visual Studio 2019 or later with individual components:
- MSBuild
- Windows 11 SDK
- MSBuild support for LLVM (clang-cl) toolset
- C++ Clang compiler for Windows
- MSVC v143 - VS 2022 C++ ARM build tools (Latest)
- MSVC v143 - VS 2022 C++ ARM Spectre-mitigated libs (Latest)
- MSVC v143 - VS 2022 C++ ARM64/ARM64EC build tools (Latest)
- MSVC v143 - VS 2022 C++ ARM64/ARM64EC Spectre-mitigated libs (Latest)
- MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
- MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)
- C++ Cmake tools for Windows
- C++ Cmake tools for Linux (These are just the components i have installed, not all might be required and others might work)
- Cmake (can be installed manually or using visual studio)
- Ninja
winget install Ninja-build.Ninja
- For Dxygen documentation:
- Doxygen
- graphviz
winget install doxygen
winget install graphviz
- Don't forget to add the graphvize binaries to PATH! (usually located at
C:/Program Files/Graphviz/bin)
Building on Linux/macOS
mkdir -p build && cd build
cmake -S .. -B . build -DCMAKE_BUILD_TYPE=Release -DSTATUSBARLOG_LOG_LEVEL=kLogLevelInf
cmake --build . -j$(nproc) --config Release
Building on Windows
Method 1: Using Visual Studio Developer Command Prompt
open "Developer Command Promt for VS 2022" or similar
mkdir build
cd build
cmake -S .. -B . -DCMAKE_BUILD_TYPE=Release -DSTATUSBARLOG_LOG_LEVEL=kLogLevelInf
cmake --build . --config Release --parallel
Method 2: Using Ninja (Recommended)
mkdir build
cd build
cmake -S .. -B . -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DSTATUSBARLOG_LOG_LEVEL=kLogLevelInf
cmake --build . --parallel
Method 3: Using Visual Studio IDE
mkdir build
cd build
cmake -S .. -B . -G "Visual Studio 17 2022" -A x64
Then open the generated .sln in Visual Studio
Important CMake Options
| Option | Type | Default | Description |
CMAKE_BUILD_TYPE | STRING | Release | Standard CMake build type |
STATUSBARLOG_INSTALL | BOOL | OFF | Generate installation targets |
STATUSBARLOG_BUILD_TESTS | BOOL | OFF | Build test suite |
STATUSBARLOG_BUILD_TEST_MAIN | BOOL | OFF | Build test main executable |
STATUSBARLOG_LOG_LEVEL | STRING | kLogLevelDbg | Compile-time log level (kLogLevelOff, kLogLevelErr, kLogLevelWrn, kLogLevelInf, kLogLevelDbg) |
Example usage:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DSTATUSBARLOG_LOG_LEVEL=kLogLevelDbg
Or when consuming via add_subdirectory():
set(STATUSBARLOG_LOG_LEVEL kLogLevelWrn CACHE STRING "statusbarlog default")
add_subdirectory(path/to/statusbarlog)
Performance Notes for Windows
- Use Ninja generator for fastest build times
- MSVC compiler with */O2* and LTO provides best runtime performance
- Consider Profile-Guided Optimization (PGO) for maximum performance in release builds
Generating Documentation
- Ensure Doxygen and Graphviz are installed
- Adjust the Doxyfile to point to this mainpage:
INPUT = src include docs
FILE_PATTERNS = *.h *.hpp *.cpp *.md
MAINPAGE = docs/mainpage.md
EXCLUDE_PATTERNS = README.md
- Run:
HTML output is usually in docs/html.
TODO / Roadmap
- โ
CMake integration with configurable
STATUSBARLOG_LOG_LEVEL
- ๐ง Make usable as git submodule / cmake module
- ๐ง Stream selection for logs and statusbars
- ๐ง Optional flush control for performance
- โ๏ธ Add cross-platform support (Windows)
- ๐งช Expand unit test coverage:
- Destroying handles / invalid handles
- Out-of-bounds indices
- Mutex and thread safety
- Truncation and string sanitization
- Race conditions
- Edge cases and boundary tests
- ๐ Follow Google C++ Style Guide:
- Replace macros with
constexpr or inline functions
- Consistent naming: PascalCase for functions, UpperCamel for types
- Avoid global state
Unit Test Plan (Summary)
String & Utility Functions
TEST(StringSanitization, SanitizeString_NoChangesNeeded)
TEST(StringSanitization, SanitizeString_ControlCharacters)
TEST(StringSanitization, SanitizeString_UnicodeReplacement)
TEST(StringSanitization, SanitizeStringWithNewline_PreservesNewlines)
TEST(StringSanitization, SanitizeString_TruncatesLongStrings)
Terminal Utility Tests
TEST(TerminalUtils, GetTerminalWidth_Success)
TEST(TerminalUtils, GetTerminalWidth_FallbackToDefault)
TEST(TerminalUtils, ClearCurrentLine_NoCrash)
TEST(TerminalUtils, CursorPosition_SaveRestore)
TEST(TerminalUtils, FlushOutput_Behavior)
Integration Tests
TEST(Logging, LogWithActiveStatusbars)
TEST(Logging, LogDifferentLevels)
TEST(Logging, LogWithFormatting)
TEST(Logging, LogWithLongMessages_Truncation)
Concurrency
TEST(Concurrency, MultipleHandlesSimultaneousCreation)
TEST(Concurrency, UpdatesFromDifferentThreads_NoCrash)
TEST(Concurrency, LogWhileStatusbarActive)
Edge Cases
TEST(EdgeCases, RapidSequentialUpdates)
TEST(EdgeCases, ManyStatusbarsSimultaneously)
TEST(EdgeCases, EmptyVectors_ErrorHandling)
TEST(EdgeCases, VeryLongPrefixPostfix_Truncation)
TEST(EdgeCases, BoundaryPercentages_0_and_100)
```