Skip to content

Instantly share code, notes, and snippets.

@anatoly-spb
Last active April 26, 2023 06:58
Show Gist options
  • Save anatoly-spb/3e000186075efb7d05f078d42a5f9bf0 to your computer and use it in GitHub Desktop.
Save anatoly-spb/3e000186075efb7d05f078d42a5f9bf0 to your computer and use it in GitHub Desktop.
Pretty Thread Logger
/*
* Pretty thread logger
*
* Copyright (C) 2020 Anatoly Shirokov.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef PRETTY_THREAD_LOGGER_H
#define PRETTY_THREAD_LOGGER_H
#pragma once
#if !__cplusplus || __cplusplus < 201703L
# error C++17 expected
#endif
#include <algorithm>
#include <iomanip>
#include <deque>
#include <limits>
#include <map>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
namespace pretty_thread_logger
{
enum class logging_level
{
fatal,
error,
warning,
notification,
info,
debug
};
namespace details
{
struct record
{
std::thread::id thread_id;
std::chrono::system_clock::time_point time;
logging_level level;
std::string text;
};
struct inmemory_log_recorder
{
mutable std::mutex m_mutex;
std::size_t m_thread_column_width = 60;
std::chrono::system_clock::time_point m_start = std::chrono::system_clock::now();
std::deque<record> m_records;
void set_thread_column_width(std::size_t width)
{
m_thread_column_width = width;
}
template <typename...Args>
std::string gather_string(Args&&...args)
{
thread_local std::ostringstream stream;
stream.str("");
stream.clear();
((stream << std::forward<Args>(args)), ...);
return stream.str();
}
template <typename...Args>
inmemory_log_recorder& log(logging_level level, Args&&...args)
{
const auto time = std::chrono::system_clock::now();
record* rec = nullptr;
{
std::lock_guard<std::mutex> guard{ m_mutex };
rec = &m_records.emplace_back(record{});
}
rec->thread_id = std::this_thread::get_id();
rec->time = time;
rec->level = level;
rec->text = gather_string(std::forward<Args>(args)...);
return *this;
}
std::ostream& print(std::ostream& stream)
{
std::lock_guard<std::mutex> guard{ m_mutex };
if (m_records.empty())
return stream;
const auto less = [](const record& a, const record& b)
{
return a.time < b.time;
};
std::sort(std::begin(m_records), std::end(m_records), less);
std::map<std::thread::id, std::size_t> thread_index_map;
std::size_t thread_count = 0;
for (const auto& rec : m_records)
{
const auto it = thread_index_map.find(rec.thread_id);
if (it == thread_index_map.end())
thread_index_map[rec.thread_id] = thread_count++;
}
std::vector<const record*> one_moment_records{ thread_count };
const std::size_t thread_column_width = m_thread_column_width;
const std::string thread_column_filler(thread_column_width, ' ');
const std::size_t time_field_width = std::numeric_limits<std::chrono::system_clock::duration::rep>::digits10;
const std::string time_field_spacer(time_field_width, ' ');
const auto get_view = [](const std::string& text, std::size_t start, std::size_t finish)
{
if (text.size() < finish) {
finish = text.size();
}
return std::string_view(text.c_str() + start, finish - start);
};
const auto print_one_moment_records = [&](std::chrono::system_clock::time_point time)
{
const auto offset = std::chrono::duration_cast<std::chrono::nanoseconds>(time - m_start);
stream << std::setw(time_field_width) << std::right << std::setfill(' ') << offset.count();
std::size_t row = 0;
auto done = true;
do
{
done = true;
for (auto& record : one_moment_records)
{
stream << ' ';
const auto start = row * thread_column_width;
if (record && record->text.size() > start)
{
const auto finish = start + thread_column_width;
if (record->text.size() > finish)
{
done = false;
}
stream << std::setw(thread_column_width) << std::left << std::setfill(' ') << get_view(record->text, start, finish);
}
else
{
stream << thread_column_filler;
}
}
if (!done)
{
row += 1;
stream << std::endl;
stream << time_field_spacer;
}
} while (!done);
stream << std::endl;
};
const auto* p = &m_records[0];
for (const auto& c : m_records)
{
if (less(*p, c))
{
print_one_moment_records(p->time);
std::fill(std::begin(one_moment_records), std::end(one_moment_records), nullptr);
}
one_moment_records[thread_index_map[c.thread_id]] = p = &c;
}
print_one_moment_records(p->time);
std::fill(std::begin(one_moment_records), std::end(one_moment_records), nullptr);
return stream;
}
};
inline inmemory_log_recorder& global_inmemory_log_recorder()
{
static inmemory_log_recorder logger;
return logger;
}
}
template <typename...Args>
void info(Args&&...args)
{
details::global_inmemory_log_recorder().log(logging_level::info, std::forward<Args>(args)...);
}
inline void show(std::ostream& stream)
{
details::global_inmemory_log_recorder().print(stream);
}
inline void set_thread_column_width(std::size_t width)
{
details::global_inmemory_log_recorder().set_thread_column_width(width);
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment