news 2026/11/2 10:42:10

C++多平台日志库,支持wasm

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++多平台日志库,支持wasm


#include <memory>
#include <string>
#include <unordered_set>


Log.h:

class Log
{
public:
using SP = std::shared_ptr<Log>;
enum class Level {
eInfo,
eWarning,
eError,
};
struct Message {
Level m_level; // 日志级别
const char *m_module; // 所属模块
std::string_view m_tag; // 自定义打印标签
const char *m_file; // 文件
std::size_t m_lineNumber; // 行编号
const char *m_func; // 函数
std::size_t m_verboseLevel; // 详细级别
std::string_view m_message; // 消息内容
};
Log();
static Log *instance();
static void release();

public:
void log(const Message &str) const;
bool connectToWebSocket(const std::string &url);
void closeWebSocket();

void setUserIdentity(const std::string &userIdentity);

private:
std::string getCurrentTime(bool bLocal, bool bIncludeMs) const;
const char *getLevelStr(Level level) const;
std::string formatLine(const Message &message) const;
void outputJsonValue(std::string &line, const std::string &key, const std::string_view &value) const;

private:
std::string m_userIdentity;
};

#if _MSC_VER // Visual C++ =
# define MY_FUNC __FUNCSIG__
#elif __GUNC__ // GCC
# define MY_FUNC __PRETTY_FUNCTION__
#elif __INTEL_COMPILER // Intel C++
# define MY_FUNC __PRETTY_FUNCTION__
#elif (defined(__clang__) && (__clang__ == 1)) // Clang++
# define MY_FUNC __PRETTY_FUNCTION__
#else
# if defined(__func__)
# define MY_FUNC __func__
# else
# define MY_FUNC ""
# endif
#endif

#define MESSAGE_DEFAULT(level, msg) \
{ \
level, MODULE_NAME_DEF, "", __FILE__, __LINE__, MY_FUNC, 0, msg \
}
#define MESSAGE_TAG_DEFAULT(level, tag, msg) \
{ \
level, MODULE_NAME_DEF, tag, __FILE__, __LINE__, MY_FUNC, 0, msg \
}
#define MESSAGE_DEFAULT_VERBOSE(level, msg, verbose) \
{ \
level, MODULE_NAME_DEF, "", __FILE__, __LINE__, MY_FUNC, verbose, msg \
}

#define LOG_INFO(message) Log::instance()->log(MESSAGE_DEFAULT(Log::Level::eInfo, message));

#define LOG_WARN(message) Log::instance()->log(MESSAGE_DEFAULT(Log::Level::eWarning, message));

#define LOG_ERROR(message) Log::instance()->log(MESSAGE_DEFAULT(Log::Level::eError, message));

#define LOG_INFO_TAG(tag, message) \
Log::instance()->log(MESSAGE_TAG_DEFAULT(Log::Level::eInfo, tag, message));

#define LOG_WARN_TAG(tag, message) \
Log::instance()->log(MESSAGE_TAG_DEFAULT(Log::Level::eWarning, tag, message));

#define LOG_ERROR_TAG(tag, message) \
Log::instance()->log(MESSAGE_TAG_DEFAULT(Log::Level::eError, tag, message));
#endif

Log.cpp:

#include "Log.h"

#include <algorithm>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>

#ifdef __EMSCRIPTEN__
# include <emscripten/emscripten.h>
# include <emscripten/html5.h>
# include <emscripten/websocket.h>
# include <stdio.h>
# include <stdlib.h>

EMSCRIPTEN_WEBSOCKET_T gWebSocket = -1;

EM_BOOL WebSocketOpen(int eventType, const EmscriptenWebSocketOpenEvent *e, void *userData)
{
printf("open(eventType=%d, userData=%ld)\n", eventType, (long)userData);

emscripten_websocket_send_utf8_text(e->socket, "hello on the other side");

char data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
emscripten_websocket_send_binary(e->socket, data, sizeof(data));

// emscripten_websocket_close(e->socket, 0, 0);
return 0;
}

EM_BOOL WebSocketClose(int eventType, const EmscriptenWebSocketCloseEvent *e, void *userData)
{
printf("close(eventType=%d, wasClean=%d, code=%d, reason=%s, userData=%ld)\n",
eventType,
e->wasClean,
e->code,
e->reason,
(long)userData);
return 0;
}

EM_BOOL WebSocketError(int eventType, const EmscriptenWebSocketErrorEvent *e, void *userData)
{
printf("error(eventType=%d, userData=%ld)\n", eventType, (long)userData);
return 0;
}

EM_BOOL WebSocketMessage(int eventType, const EmscriptenWebSocketMessageEvent *e, void *userData)
{
printf("message(eventType=%d, userData=%ld, data=%p, numBytes=%d, isText=%d)\n",
eventType,
(long)userData,
e->data,
e->numBytes,
e->isText);
if (e->isText)
printf("text data: \"%s\"\n", e->data);
else {
printf("binary data:");
for (int i = 0; i < e->numBytes; ++i)
printf(" %02X", e->data[i]);
printf("\n");

// emscripten_websocket_delete(e->socket);
// exit(0);
}
return 0;
}

#endif

static Log* m_instance = nullptr;

Log* Log::instance(){
if(!m_instance)
m_instance = new Log();
return m_instance;
}

void Log::release(){
if(m_instance)
delete m_instance;
m_instance = nullptr;
}

Log::Log() {}

/*
* 用户具体信息
*/
void Log::setUserIdentity(const std::string &userIdentity)
{
m_userIdentity = userIdentity;
}

std::string Log::getCurrentTime(bool bLocal, bool bIncludeMs) const
{
std::stringstream ss;
auto timePoint = std::chrono::system_clock::now();
auto tSeconds = std::chrono::duration_cast<std::chrono::seconds>(timePoint.time_since_epoch());
#if (defined __EMSCRIPTEN__)
// 用WebAssembly得方式打印时间
std::time_t tt = std::chrono::system_clock::to_time_t(timePoint);
std::tm *ptm = std::localtime(&tt);
ss << std::put_time(ptm, "%Y-%m-%d %H:%M:%S");
#elif defined(WINDOWS_DEV_MODE) || defined(QT_PLATFORM)
auto secNow = tSeconds.count();
tm tmNow;
if(bLocal)
localtime_s(&tmNow, &secNow);
else
gmtime_s(&tmNow, &secNow);
ss << std::put_time(&tmNow, "%Y-%m-%d %H:%M:%S");
#else
// 用WebAssembly得方式打印时间
std::time_t tt = std::chrono::system_clock::to_time_t(timePoint);
std::tm *ptm = std::localtime(&tt);
ss << std::put_time(ptm, "%Y-%m-%d %H:%M:%S");
#endif

if(bIncludeMs) {
auto tMilli = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint.time_since_epoch());
auto ms = tMilli - tSeconds;
ss << "." << std::setfill('0') << std::setw(3) << ms.count();
}
// ss << std::put_time(std::localtime(&timeT), "%X,");
return ss.str();
}

const char *Log::getLevelStr(Level level) const
{
switch (level) {
case Log::Level::eError:
return "ERROR";
case Log::Level::eWarning:
return "WARNING";
case Log::Level::eInfo:
return "INFO";
default:
return "UNKNOWN";
}
}

bool Log::connectToWebSocket(const std::string &url)
{
#if (defined __EMSCRIPTEN__)
if (!emscripten_websocket_is_supported()) {
printf("WebSockets are not supported, cannot continue!\n");
return false;
}
closeWebSocket();
EmscriptenWebSocketCreateAttributes attr;
emscripten_websocket_init_create_attributes(&attr);

attr.url = url.c_str();
gWebSocket = emscripten_websocket_new(&attr);
if (gWebSocket <= 0) {
printf("WebSocket creation failed, error code %d!\n", (EMSCRIPTEN_RESULT)gWebSocket);
return false;
}
emscripten_websocket_set_onopen_callback(gWebSocket, (void *)42, WebSocketOpen);
emscripten_websocket_set_onclose_callback(gWebSocket, (void *)43, WebSocketClose);
emscripten_websocket_set_onerror_callback(gWebSocket, (void *)44, WebSocketError);
emscripten_websocket_set_onmessage_callback(gWebSocket, (void *)45, WebSocketMessage);
#endif
return true;
}

void Log::closeWebSocket()
{
#if (defined __EMSCRIPTEN__)
if (gWebSocket > 0) {
emscripten_websocket_close(gWebSocket, 0, 0); // 关闭旧的连接
// emscripten_websocket_delete(0);
}
gWebSocket = -1;
#endif
}

void Log::log(const Message &str) const
{
auto logLine = formatLine(str);
#if (defined __EMSCRIPTEN__)
// 0 (CONNECTING): The connection is still being established.
// 1 (OPEN): The connection is open and ready to send/receive data.
// 2 (CLOSING): The connection is in the process of closing.
// 3 (CLOSED): The connection has been closed or could not be opened.
unsigned short readyState = 0;
if (gWebSocket > 0)
emscripten_websocket_get_ready_state(gWebSocket, &readyState);
if (1 != readyState || gWebSocket <= 0) {
if (Level::eError == str.m_level)
emscripten_console_error(logLine.c_str());
else if (Level::eWarning == str.m_level)
emscripten_console_warn(logLine.c_str());
else
emscripten_console_log(logLine.c_str());
} else {
emscripten_websocket_send_utf8_text(gWebSocket, logLine.c_str());
}
#else
if (Level::eError == str.m_level)
std::cerr << logLine << std::endl;
else
std::cout << logLine << std::endl;
#endif
}

void Log::outputJsonValue(std::string &line, const std::string &key, const std::string_view &value) const
{
if (line.empty())
line.append("{");
else
line.append(", ");
line.append("\"");
line.append(key);
line.append("\"");
line.append(":");
line.append("\"");
line.append(value);
line.append("\"");
}

/**
* 日志行输出
* @param message
* @return
*/
std::string Log::formatLine(const Message& message) const
{
std::string line;
#if (defined __EMSCRIPTEN__)
std::string now = getCurrentTime(true, true);
outputJsonValue(line, "TIME", now);
#endif
if (!message.m_tag.empty())
outputJsonValue(line, "Tag", message.m_tag);
if (!m_userIdentity.empty())
outputJsonValue(line, "user", m_userIdentity);
outputJsonValue(line, "MODULE", message.m_module);

#if (defined __EMSCRIPTEN__)
std::string newFileNamePath = message.m_file;
std::replace(newFileNamePath.begin(), newFileNamePath.end(), '\\', '/'); // windows下符号替换
outputJsonValue(line, "FILE", newFileNamePath);

outputJsonValue(line, "FUNC", message.m_func);
outputJsonValue(line, "LINE", std::to_string(message.m_lineNumber));
outputJsonValue(line, "LEVEL", getLevelStr(message.m_level));
// outputJsonValue(line, "VERBOSE", std::to_string(message.m_verboseLevel));
#endif
outputJsonValue(line, "MSG", message.m_message);
line.append("}");

#if (defined __EMSCRIPTEN__)
if (Level::eError == message.m_level) {
char buffer[2048];
auto len = emscripten_get_callstack(EM_LOG_JS_STACK | EM_LOG_C_STACK, buffer, 2048);
std::string_view stringView(buffer, len);
outputJsonValue(line, "callstack", std::string{stringView});
line.append(", \"callstack\":\n");
}
#endif
return line;
}

使用示例:
#include "Log.h"

void main() {
LOG_INFO("setHotZoneMode invalid mode");
}

GitHub - ArthurSonzogni/quill: Asynchronous Low Latency Logging Library


创作不易,小小的支持一下吧!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 10:41:21

流注放电仿真

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 10:40:52

毫米波雷达室内感知平台PLFM_RADAR:从点云到业务告警的完整实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 10:40:43

SPI通信隔离设计:地环路干扰分析与数字隔离器选型实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 10:40:43

工业级SPI隔离设计:时序重构与抗干扰实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 10:39:50

FPGA+STM32协同实现PLFM相控阵雷达系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 10:39:26

工业SPI通信电气隔离设计实战:MAX14431与STM32F723ZE硬核组合

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华