/* vim:set ts=2 sw=2 sts=2 et: */ /** * \author Marcus Holland-Moritz (github@mhxnet.de) * \copyright Copyright (c) Marcus Holland-Moritz * * This file is part of dwarfs. * * dwarfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * dwarfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with dwarfs. If not, see . */ #include #include #include #include #include #include #include #ifdef DWARFS_STACKTRACE_ENABLED #include #if !(FOLLY_USE_SYMBOLIZER && FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF) #error "folly symbolizer is unavailable" #endif #endif #include #include #include #include #include #include namespace dwarfs { namespace { constexpr std::array, 6> log_level_map = {{ {"error", logger::ERROR}, {"warn", logger::WARN}, {"info", logger::INFO}, {"verbose", logger::VERBOSE}, {"debug", logger::DEBUG}, {"trace", logger::TRACE}, }}; } std::ostream& operator<<(std::ostream& os, logger::level_type const& optval) { return os << logger::level_name(optval); } std::istream& operator>>(std::istream& is, logger::level_type& optval) { std::string s; is >> s; optval = logger::parse_level(s); return is; } logger::level_type logger::parse_level(std::string_view level) { // don't parse FATAL here, it's a special case for (auto const& [name, lvl] : log_level_map) { if (level == name) { return lvl; } } DWARFS_THROW(runtime_error, fmt::format("invalid logger level: {}", level)); } std::string_view logger::level_name(level_type level) { for (auto const& [name, lvl] : log_level_map) { if (level == lvl) { return name; } } DWARFS_THROW(runtime_error, fmt::format("invalid logger level: {}", static_cast(level))); } std::string logger::all_level_names() { std::string result; for (auto const& m : log_level_map) { if (!result.empty()) { result += ", "; } result += m.first; } return result; } null_logger::null_logger() { set_policy(); } stream_logger::stream_logger(logger_options const& options) : stream_logger(std::cerr, options) {} stream_logger::stream_logger(std::ostream& os, logger_options const& options) : stream_logger(std::make_shared(), os, options) {} stream_logger::stream_logger(std::shared_ptr term, std::ostream& os, logger_options const& logopts) : os_(os) , color_(term->is_tty(os) && term->is_fancy()) , enable_stack_trace_{getenv_is_enabled("DWARFS_LOGGER_STACK_TRACE")} , with_context_(logopts.with_context ? logopts.with_context.value() : logopts.threshold >= logger::VERBOSE) , term_{std::move(term)} { set_threshold(logopts.threshold); } void stream_logger::preamble(std::ostream&) {} void stream_logger::postamble(std::ostream&) {} std::string_view stream_logger::get_newline() const { return "\n"; } void stream_logger::write_nolock(std::string_view output) { if (&os_ == &std::cerr) { fmt::print(stderr, "{}", output); } else { os_ << output; } } void stream_logger::write(level_type level, const std::string& output, char const* file, int line) { if (level <= threshold_ || level == FATAL) { auto t = get_current_time_string(); std::string_view prefix; std::string_view suffix; auto newline = get_newline(); if (color_) { switch (level) { case FATAL: case ERROR: prefix = term_->color(termcolor::BOLD_RED); suffix = term_->color(termcolor::NORMAL); break; case WARN: prefix = term_->color(termcolor::BOLD_YELLOW); suffix = term_->color(termcolor::NORMAL); break; case VERBOSE: prefix = term_->color(termcolor::DIM_CYAN); suffix = term_->color(termcolor::NORMAL); break; case DEBUG: prefix = term_->color(termcolor::DIM_YELLOW); suffix = term_->color(termcolor::NORMAL); break; case TRACE: prefix = term_->color(termcolor::GRAY); suffix = term_->color(termcolor::NORMAL); break; default: break; } } #ifdef DWARFS_STACKTRACE_ENABLED std::string stacktrace; std::vector st_lines; if (enable_stack_trace_ || level == FATAL) { using namespace folly::symbolizer; Symbolizer symbolizer(LocationInfoMode::FULL); FrameArray<8> addresses; getStackTraceSafe(addresses); symbolizer.symbolize(addresses); folly::symbolizer::StringSymbolizePrinter printer( color_ ? folly::symbolizer::SymbolizePrinter::COLOR : 0); printer.println(addresses, 3); stacktrace = printer.str(); split_to(stacktrace, '\n', st_lines); if (st_lines.back().empty()) { st_lines.pop_back(); } } #endif char lchar = logger::level_char(level); std::string context; size_t context_len = 0; if (with_context_ && file) { context = get_logger_context(file, line); context_len = context.size(); if (color_) { context = folly::to( suffix, term_->color(termcolor::DIM_MAGENTA), context, term_->color(termcolor::NORMAL), prefix); } } std::string tmp; folly::small_vector lines; if (output.find('\r') != std::string::npos) { tmp.reserve(output.size()); std::copy_if(output.begin(), output.end(), std::back_inserter(tmp), [](char c) { return c != '\r'; }); split_to(tmp, '\n', lines); } else { split_to(output, '\n', lines); } if (lines.back().empty()) { lines.pop_back(); } std::ostringstream oss; bool clear_ctx = true; for (auto l : lines) { oss << prefix << lchar << ' ' << t << ' ' << context << l << suffix << newline; if (clear_ctx) { std::fill(t.begin(), t.end(), '.'); context.assign(context_len, ' '); clear_ctx = false; } } #ifdef DWARFS_STACKTRACE_ENABLED for (auto l : st_lines) { oss << l << newline; } #endif std::lock_guard lock(mx_); std::ostringstream oss2; preamble(oss2); oss2 << oss.str(); postamble(oss2); write_nolock(oss2.str()); } if (level == FATAL) { std::abort(); } } void stream_logger::set_threshold(level_type threshold) { threshold_ = threshold; if (threshold >= level_type::DEBUG) { set_policy(); } else { set_policy(); } } std::string get_logger_context(char const* path, int line) { return fmt::format("[{0}:{1}] ", basename(path), line); } std::string get_current_time_string() { using namespace std::chrono; auto now = floor(system_clock::now()); return fmt::format("{:%H:%M:%S}", now); } } // namespace dwarfs