#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