Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve logging color style #1739

Merged
merged 7 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading