Skip to content

Commit

Permalink
Improve logging color style (#1739)
Browse files Browse the repository at this point in the history
* Improve logging color style

* Improve code style

* Fix ci

* Fix ci

* Fix ci

* Fixup

* Fix ci
  • Loading branch information
halx99 authored Mar 9, 2024
1 parent 12f0298 commit 7297181
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 109 deletions.
178 changes: 89 additions & 89 deletions core/base/Console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ AX_API void setLogOutput(ILogOutput* output)
s_logOutput = output;
}

AX_API std::string_view makeLogPrefix(LogBufferType&& stack_buffer, LogLevel level)
AX_API LogItem& preprocessLog(LogItem&& item)
{
if (s_logFmtFlags != LogFmtFlag::Null)
{
Expand All @@ -110,59 +110,100 @@ AX_API std::string_view makeLogPrefix(LogBufferType&& stack_buffer, LogLevel lev
# define xmol_getpid() (uintptr_t)::getpid()
# define xmol_gettid() (uintptr_t)::pthread_self()
#endif

size_t offset = 0;
if (bitmask::any(s_logFmtFlags, LogFmtFlag::Level))
auto wptr = item.prefix_buffer_;
const auto buffer_size = sizeof(item.prefix_buffer_);
auto& prefix_size = item.prefix_size_;
if (bitmask::any(s_logFmtFlags, LogFmtFlag::Level | LogFmtFlag::Colored))
{
char levelName;
switch (level)
std::string_view levelName;
switch (item.level_)
{
case LogLevel::Debug:
levelName = 'D';
levelName = "D/"sv;
break;
case LogLevel::Info:
levelName = 'I';
levelName = "I/"sv;
break;
case LogLevel::Warn:
levelName = 'W';
levelName = "W/"sv;
break;
case LogLevel::Error:
levelName = 'E';
levelName = "E/"sv;
break;
default:
levelName = '?';
levelName = "?/"sv;
}
offset += fmt::format_to_n(stack_buffer.data(), stack_buffer.size(), "{}/", levelName).size;

#if !defined(__APPLE__) && !defined(__ANDROID__)
if (bitmask::any(s_logFmtFlags, LogFmtFlag::Colored))
{
constexpr auto colorCodeOfLevel = [](LogLevel level) ->std::string_view
{
switch (level)
{
case LogLevel::Info:
return "\x1b[92m"sv;
case LogLevel::Warn:
return "\x1b[33m"sv;
case LogLevel::Error:
return "\x1b[31m"sv;
default:
return std::string_view{};
}
};

auto colorCode = colorCodeOfLevel(item.level_);
if (!colorCode.empty())
{
item.writePrefix(colorCode);
item.has_style_ = true;
}
}
#endif
item.writePrefix(levelName);
}
if (bitmask::any(s_logFmtFlags, LogFmtFlag::TimeStamp))
{
struct tm ts = {0};
auto tv_msec = yasio::clock<yasio::system_clock_t>();
auto tv_sec = static_cast<time_t>(tv_msec / std::milli::den);
localtime_r(&tv_sec, &ts);
offset += fmt::format_to_n(stack_buffer.data() + offset, stack_buffer.size() - offset,
"[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}]", ts.tm_year + 1900,
ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec,
static_cast<int>(tv_msec % std::milli::den))
.size;
prefix_size += fmt::format_to_n(wptr + prefix_size, buffer_size - prefix_size,
"[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}]", ts.tm_year + 1900,
ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec,
static_cast<int>(tv_msec % std::milli::den))
.size;
}
if (bitmask::any(s_logFmtFlags, LogFmtFlag::ProcessId))
offset += fmt::format_to_n(stack_buffer.data() + offset, stack_buffer.size() - offset, "[PID:{:x}]",
xmol_getpid())
.size;
prefix_size +=
fmt::format_to_n(wptr + prefix_size, buffer_size - prefix_size, "[PID:{:x}]", xmol_getpid()).size;
if (bitmask::any(s_logFmtFlags, LogFmtFlag::ThreadId))
offset += fmt::format_to_n(stack_buffer.data() + offset, stack_buffer.size() - offset, "[TID:{:x}]",
xmol_gettid())
.size;
return std::string_view{stack_buffer.data(), offset};
prefix_size +=
fmt::format_to_n(wptr + prefix_size, buffer_size - prefix_size, "[TID:{:x}]", xmol_gettid()).size;
}
else
return std::string_view{};
return item;
}

AX_DLL void printLog(std::string&& message, LogLevel level, size_t prefixSize, const char* tag)
static int hints = 0;

AX_DLL void outputLog(LogItem& item, const char* tag)
{
#if defined(__ANDROID__)
int prio;
switch (item.level_)
{
case LogLevel::Info:
prio = ANDROID_LOG_INFO;
break;
case LogLevel::Warn:
prio = ANDROID_LOG_WARN;
break;
case LogLevel::Error:
prio = ANDROID_LOG_ERROR;
break;
default:
prio = ANDROID_LOG_DEBUG;
}
struct trim_one_eol
{
explicit trim_one_eol(std::string& v) : value(v)
Expand All @@ -180,92 +221,51 @@ AX_DLL void printLog(std::string&& message, LogLevel level, size_t prefixSize, c
std::string& value;
bool trimed{false};
};
int prio;
switch (level)
{
case LogLevel::Info:
prio = ANDROID_LOG_INFO;
break;
case LogLevel::Warn:
prio = ANDROID_LOG_WARN;
break;
case LogLevel::Error:
prio = ANDROID_LOG_ERROR;
break;
default:
prio = ANDROID_LOG_DEBUG;
}
__android_log_print(prio, tag, "%s", static_cast<const char*>(trim_one_eol{message}) + prefixSize);
__android_log_print(prio, tag, "%s",
static_cast<const char*>(trim_one_eol{item.qualified_message_} + item.prefix_size_));
#else
AX_UNUSED_PARAM(prefixSize);
AX_UNUSED_PARAM(tag);
# if defined(_WIN32)
if (::IsDebuggerPresent())
OutputDebugStringW(ntcvt::from_chars(message).c_str());
OutputDebugStringW(ntcvt::from_chars(item.message()).c_str());
# endif

std::optional<fmt::text_style> color;
# if !defined(__APPLE__)
if (bitmask::any(s_logFmtFlags, LogFmtFlag::Colored))
// write normal color text to console
# if defined(_WIN32)
auto hStdout = ::GetStdHandle(item.level_ != LogLevel::Error ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
if (hStdout)
{
switch (level)
{
case LogLevel::Info:
color.emplace(fmt::fg(fmt::terminal_color::bright_green));
break;
case LogLevel::Warn:
color.emplace(fmt::fg(fmt::terminal_color::yellow));
break;
case LogLevel::Error:
color.emplace(fmt::fg(fmt::terminal_color::red));
break;
default:;
}
// print to console if possible
// since we use win32 API, the ::fflush call doesn't required.
// see: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers#return-value
::WriteFile(hStdout, item.qualified_message_.c_str(), static_cast<DWORD>(item.qualified_message_.size()),
nullptr, nullptr);
}
# endif

if (!color.has_value())
{ // write normal color text to console
# if defined(_WIN32)
auto hStdout = ::GetStdHandle(level != LogLevel::Error ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
if (hStdout)
{
// print to console if possible
// since we use win32 API, the ::fflush call doesn't required.
// see: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers#return-value
::WriteFile(hStdout, message.c_str(), static_cast<DWORD>(message.size()), nullptr, nullptr);
}
# else
// Linux, Mac, iOS, etc
auto fd = ::fileno(level != LogLevel::Error ? stdout : stderr);
::write(fd, message.c_str(), message.size());
// Linux, Mac, iOS, etc
auto fd = ::fileno(item.level_ != LogLevel::Error ? stdout : stderr);
::write(fd, item.qualified_message_.c_str(), item.qualified_message_.size());
# endif
}
else
{
fmt::print(color.value(), "{}", message);
}
#endif

if (s_logOutput)
s_logOutput->write(std::move(message), level);
s_logOutput->write(item.message(), item.level_);
}

#pragma endregion

AX_API void print(const char* format, ...)
{
va_list args;

va_start(args, format);
auto buf = StringUtils::vformat(format, args);
auto message = StringUtils::vformat(format, args);
va_end(args);

printLog(std::move(buf += '\n'), LogLevel::Debug, 0, "axmol debug info");
if (!message.empty())
outputLog(LogItem::vformat(FMT_COMPILE("{}{}\n"), preprocessLog(LogItem{LogLevel::Silent}), message),
"axmol debug info");
}

// FIXME: Deprecated
// void CCLog(const char * format, ...);

//
// Utility code
//
Expand Down
80 changes: 60 additions & 20 deletions core/base/Console.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,61 @@ enum class LogFmtFlag
};
AX_ENABLE_BITMASK_OPS(LogFmtFlag);

class LogItem
{
friend AX_API LogItem& preprocessLog(LogItem&& logItem);
friend AX_API void outputLog(LogItem& item, const char* tag);

public:
static constexpr auto COLOR_PREFIX_SIZE = 5; // \x1b[00m
static constexpr auto COLOR_QUALIFIER_SIZE = COLOR_PREFIX_SIZE + 3; // \x1b[m

explicit LogItem(LogLevel lvl) : level_(lvl) {}
LogItem(const LogItem&) = delete;

LogLevel level() const { return level_; }

std::string_view message() const
{
return has_style_ ? std::string_view{qualified_message_.data() + COLOR_PREFIX_SIZE,
qualified_message_.size() - COLOR_QUALIFIER_SIZE}
: std::string_view{qualified_message_};
}

template <typename _FmtType, typename... _Types>
inline static LogItem& vformat(_FmtType&& fmt, LogItem& item, _Types&&... args)
{
item.qualified_message_ = fmt::format(std::forward<_FmtType>(fmt), std::string_view{item.prefix_buffer_, item.prefix_size_},
std::forward<_Types>(args)...);

item.qualifier_size_ = item.prefix_size_ + 1 /*for \n*/;
if (item.has_style_)
{
item.qualified_message_.append("\x1b[m"sv);
item.qualifier_size_ += (COLOR_QUALIFIER_SIZE - COLOR_PREFIX_SIZE);
}
return item;
}

private:
void writePrefix(std::string_view data)
{
memcpy(prefix_buffer_ + prefix_size_, data.data(), data.size());
prefix_size_ += data.size();
}
LogLevel level_;
bool has_style_{false};
size_t prefix_size_{0}; // \x1b[00mD/[2024-02-29 00:00:00.123][PID:][TID:]
size_t qualifier_size_{0}; // prefix_size_ + \x1b[m (optional) + \n
std::string qualified_message_;
char prefix_buffer_[128];
};

class ILogOutput
{
public:
virtual ~ILogOutput() {}
virtual void write(std::string&&, LogLevel) = 0;
virtual void write(std::string_view message, LogLevel) = 0;
};

/* @brief control log level */
Expand All @@ -95,31 +145,21 @@ AX_API void setLogFmtFlag(LogFmtFlag flags);
/* @brief set log output */
AX_API void setLogOutput(ILogOutput* output);

/*
* @brief lowlevel print log message
* @param message the message to print
* @level the level of current log item see also LogLevel
* @prefixSize the prefix size
* @tag optional, the log tag of current log item
*/
AX_API void printLog(std::string&& message, LogLevel level, size_t prefixSize, const char* tag);
/* @brief internal use */
AX_API LogItem& preprocessLog(LogItem&& logItem);

/* @brief internal use */
AX_API void outputLog(LogItem& item, const char* tag);

template <typename _FmtType, typename... _Types>
inline void printLogT(LogLevel level, _FmtType&& fmt, std::string_view prefix, _Types&&... args)
inline void printLogT(_FmtType&& fmt, LogItem& item, _Types&&... args)
{
if (getLogLevel() <= level)
{
auto message = fmt::format(std::forward<_FmtType>(fmt), prefix, std::forward<_Types>(args)...);
printLog(std::move(message), level, prefix.size(), "axmol");
}
if (item.level() >= getLogLevel())
outputLog(LogItem::vformat(std::forward<_FmtType>(fmt), item, std::forward<_Types>(args)...), "axmol");
}

using LogBufferType = std::array<char, 128>;
// for internal use make log prefix: D/[2024-02-29 00:00:00.123][PID:][TID:]
AX_API std::string_view makeLogPrefix(LogBufferType&& stack_buffer, LogLevel level);

#define AXLOG_WITH_LEVEL(level, fmtOrMsg, ...) \
ax::printLogT(level, FMT_COMPILE("{}" fmtOrMsg "\n"), ax::makeLogPrefix(ax::LogBufferType{}, level), ##__VA_ARGS__)
ax::printLogT(FMT_COMPILE("{}" fmtOrMsg "\n"), ax::preprocessLog(ax::LogItem{level}), ##__VA_ARGS__)

#define AXLOGD(fmtOrMsg, ...) AXLOG_WITH_LEVEL(ax::LogLevel::Debug, fmtOrMsg, ##__VA_ARGS__)
#define AXLOGI(fmtOrMsg, ...) AXLOG_WITH_LEVEL(ax::LogLevel::Info, fmtOrMsg, ##__VA_ARGS__)
Expand Down

0 comments on commit 7297181

Please sign in to comment.