Skip to content
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
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,26 @@ jobs:
run: cmake -B build -S tests/install_consumer -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake
- name: Build consumer project
run: cmake --build build

emscripten:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- run: git submodule update --init --recursive
- uses: mymindstorm/setup-emsdk@v14
with:
version: 'latest'
- name: Configure (Node target)
run: |
emcmake cmake -S . -B build-ems \
-DCMAKE_BUILD_TYPE=Release \
-DLOG_IT_CPP_BUILD_TESTS=ON \
-DLOGIT_EMSCRIPTEN=ON -DLOGIT_FORCE_ASYNC_OFF=ON
- name: Build
run: cmake --build build-ems --target ems_console ems_async_flush -j
- name: Run smoke tests
run: |
node --no-experimental-fetch build-ems/tests/ems_console.js
node --no-experimental-fetch build-ems/tests/ems_async_flush.js
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ option(LOGIT_USE_SUBMODULES "Allow bundled third_party fallback" OFF)
option(LOGIT_WITH_SYSLOG "Enable POSIX syslog backend" ON)
option(LOGIT_WITH_WIN_EVENT_LOG "Enable Windows Event Log backend" ON)

if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
option(LOGIT_EMSCRIPTEN "Build for Emscripten" ON)
endif()
option(LOGIT_FORCE_ASYNC_OFF "Force disable async logging" OFF)

if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
Expand All @@ -35,6 +40,18 @@ target_include_directories(log-it-cpp INTERFACE

target_link_libraries(log-it-cpp INTERFACE time_shield::time_shield)

if(LOGIT_EMSCRIPTEN)
set(LOGIT_WITH_SYSLOG OFF CACHE BOOL "" FORCE)
set(LOGIT_WITH_WIN_EVENT_LOG OFF CACHE BOOL "" FORCE)
target_compile_definitions(log-it-cpp INTERFACE LOGIT_EMSCRIPTEN=1)
target_compile_definitions(log-it-cpp INTERFACE LOGIT_DEFAULT_ASYNC_OFF=1)
target_link_options(log-it-cpp INTERFACE "-sEXPORTED_RUNTIME_METHODS=['UTF8ToString']")
endif()

if(LOGIT_FORCE_ASYNC_OFF)
target_compile_definitions(log-it-cpp INTERFACE LOGIT_DEFAULT_ASYNC_OFF=1)
endif()

if(LOGIT_WITH_SYSLOG AND (UNIX OR APPLE) AND NOT EMSCRIPTEN)
target_compile_definitions(log-it-cpp INTERFACE LOGIT_HAS_SYSLOG=1)
endif()
Expand Down
39 changes: 31 additions & 8 deletions include/logit_cpp/logit/detail/TaskExecutor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,27 @@
/// \file TaskExecutor.hpp
/// \brief Defines the TaskExecutor class, which manages task execution in a separate thread.

#include <functional>
#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
#include <deque>
#include <emscripten/emscripten.h>
#else
#include <thread>
#include <queue>
#include <mutex>
#include <functional>
#include <condition_variable>
#include <iostream>
#include <chrono>
#endif

namespace logit { namespace detail {

/// \brief Queue overflow handling policy.
enum class QueuePolicy { Drop, Block };

#if defined(__EMSCRIPTEN__)
#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)

/// \class TaskExecutor
/// \brief Simplified task executor for single-threaded environments.
/// \brief Simplified task executor for single-threaded Emscripten builds.
class TaskExecutor {
public:
static TaskExecutor& get_instance() {
Expand All @@ -30,11 +34,16 @@ namespace logit { namespace detail {
}

void add_task(std::function<void()> task) {
if (task) task();
if (!task) return;
const bool schedule = m_tasks.empty();
m_tasks.push_back(std::move(task));
if (schedule) {
emscripten_async_call(&TaskExecutor::drain_thunk, this, 0);
}
}

void wait() {}
void shutdown() {}
void wait() { drain(); }
void shutdown() { drain(); }

void set_max_queue_size(std::size_t) {}
void set_queue_policy(QueuePolicy) {}
Expand All @@ -46,6 +55,20 @@ namespace logit { namespace detail {
TaskExecutor& operator=(const TaskExecutor&) = delete;
TaskExecutor(TaskExecutor&&) = delete;
TaskExecutor& operator=(TaskExecutor&&) = delete;

std::deque<std::function<void()>> m_tasks;

static void drain_thunk(void* arg) {
static_cast<TaskExecutor*>(arg)->drain();
}

void drain() {
while (!m_tasks.empty()) {
auto task = std::move(m_tasks.front());
m_tasks.pop_front();
task();
}
}
};

#else
Expand Down Expand Up @@ -166,7 +189,7 @@ namespace logit { namespace detail {
TaskExecutor& operator=(TaskExecutor&&) = delete;
};

#endif // defined(__EMSCRIPTEN__)
#endif // defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)

}} // namespace logit::detail

Expand Down
156 changes: 74 additions & 82 deletions include/logit_cpp/logit/loggers/ConsoleLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@
#endif
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#if defined(LOGIT_EM_BROWSER_COLORS)
EM_JS(void, log_ansi_js, (int lvl, const char* cmsg, const char* cdefcolor), {
const msg = UTF8ToString(cmsg);
const def = UTF8ToString(cdefcolor);
const isNode = (typeof process !== 'undefined' && process.versions && process.versions.node);
const fn = lvl >= 5 ? console.error : (lvl == 4 ? console.warn : console.log);
if (isNode) { fn(msg); return; }
const map = {30:"black",31:"darkred",32:"darkgreen",33:"olive",34:"darkblue",35:"purple",36:"teal",37:"lightgray",
90:"gray",91:"red",92:"green",93:"yellow",94:"blue",95:"magenta",96:"cyan",97:"white"};
const re = /\x1b\[(\d+)m/g;
let last = 0, m, style = 'color:' + def;
const fmt = [];
const styles = [];
while ((m = re.exec(msg)) !== null) {
if (m.index > last) { fmt.push('%c' + msg.slice(last, m.index)); styles.push(style); }
style = 'color:' + (map[m[1]] || def);
last = re.lastIndex;
}
if (last < msg.length) { fmt.push('%c' + msg.slice(last)); styles.push(style); }
fn(fmt.join(''), ...styles);
});
#else
EM_JS(void, log_level, (int lvl, const char* msg), {
const s = UTF8ToString(msg);
const fn = lvl >= 5 ? console.error : (lvl == 4 ? console.warn : console.log);
fn(s);
});
#endif
#endif
#include <mutex>
#include <atomic>
Expand Down Expand Up @@ -89,8 +117,27 @@ namespace logit {
void log(const LogRecord& record, const std::string& message) override {
m_last_log_ts = record.timestamp_ms;
#ifdef __EMSCRIPTEN__
std::lock_guard<std::mutex> lock(m_mutex);
handle_ansi_colors_emscripten(message);
std::unique_lock<std::mutex> lock(m_mutex);
const int lvl = static_cast<int>(record.log_level);
if (!m_config.async) {
# if defined(LOGIT_EM_BROWSER_COLORS)
log_ansi_js(lvl, message.c_str(), text_color_to_css(m_config.default_color));
# else
log_level(lvl, message.c_str());
# endif
return;
}
auto msg_copy = std::string(message);
const auto def_color = m_config.default_color;
lock.unlock();
detail::TaskExecutor::get_instance().add_task([this, lvl, msg_copy, def_color]() {
std::lock_guard<std::mutex> inner_lock(m_mutex);
# if defined(LOGIT_EM_BROWSER_COLORS)
log_ansi_js(lvl, msg_copy.c_str(), text_color_to_css(def_color));
# else
log_level(lvl, msg_copy.c_str());
# endif
});
return;
#else
std::unique_lock<std::mutex> lock(m_mutex);
Expand Down Expand Up @@ -174,15 +221,10 @@ namespace logit {
/// \brief Waits for all asynchronous tasks to complete.
/// If asynchronous logging is enabled, waits for all pending log messages to be written.
void wait() override {
#ifdef __EMSCRIPTEN__
// Nothing to wait for in single-threaded mode
return;
#else
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_config.async) return;
lock.unlock();
detail::TaskExecutor::get_instance().wait();
#endif
}

private:
Expand All @@ -191,6 +233,31 @@ namespace logit {
std::atomic<int64_t> m_last_log_ts = ATOMIC_VAR_INIT(0);
std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));

# ifdef __EMSCRIPTEN__
/// \brief Convert TextColor to a CSS color name.
const char* text_color_to_css(TextColor color) const {
switch (color) {
case TextColor::Black: return "black";
case TextColor::DarkRed: return "darkred";
case TextColor::DarkGreen: return "darkgreen";
case TextColor::DarkYellow: return "olive";
case TextColor::DarkBlue: return "darkblue";
case TextColor::DarkMagenta: return "purple";
case TextColor::DarkCyan: return "teal";
case TextColor::LightGray: return "lightgray";
case TextColor::DarkGray: return "gray";
case TextColor::Red: return "red";
case TextColor::Green: return "green";
case TextColor::Yellow: return "yellow";
case TextColor::Blue: return "blue";
case TextColor::Magenta: return "magenta";
case TextColor::Cyan: return "cyan";
case TextColor::White: return "white";
default: return "inherit";
}
}
# endif

# if defined(_WIN32)

// Windows console colors
Expand Down Expand Up @@ -308,81 +375,6 @@ namespace logit {
}
# endif

# ifdef __EMSCRIPTEN__
/// \brief Convert TextColor to a CSS color name for Emscripten console output.
const char* text_color_to_css(TextColor color) const {
switch (color) {
case TextColor::Black: return "black";
case TextColor::DarkRed: return "darkred";
case TextColor::DarkGreen: return "darkgreen";
case TextColor::DarkYellow: return "olive";
case TextColor::DarkBlue: return "darkblue";
case TextColor::DarkMagenta: return "purple";
case TextColor::DarkCyan: return "teal";
case TextColor::LightGray: return "lightgray";
case TextColor::DarkGray: return "gray";
case TextColor::Red: return "red";
case TextColor::Green: return "green";
case TextColor::Yellow: return "yellow";
case TextColor::Blue: return "blue";
case TextColor::Magenta: return "magenta";
case TextColor::Cyan: return "cyan";
case TextColor::White: return "white";
default: return "inherit";
}
}

/// \brief Map ANSI code to a CSS color name.
std::string css_color_from_ansi(const std::string& code) const {
int value = std::stoi(code);
switch (value) {
case 30: return "black";
case 31: return "darkred";
case 32: return "darkgreen";
case 33: return "olive";
case 34: return "darkblue";
case 35: return "purple";
case 36: return "teal";
case 37: return "lightgray";
case 90: return "gray";
case 91: return "red";
case 92: return "green";
case 93: return "yellow";
case 94: return "blue";
case 95: return "magenta";
case 96: return "cyan";
case 97: return "white";
default: return text_color_to_css(m_config.default_color);
}
}

/// \brief Handle ANSI color codes when compiling with Emscripten.
void handle_ansi_colors_emscripten(const std::string& message) const {
std::string current_color = text_color_to_css(m_config.default_color);
std::string::size_type start = 0;
std::string::size_type pos = 0;

while ((pos = message.find("\033[", start)) != std::string::npos) {
if (pos > start) {
std::string part = message.substr(start, pos - start);
EM_ASM_({ console.log('%c' + UTF8ToString($0), 'color: ' + UTF8ToString($1)); }, part.c_str(), current_color.c_str());
}
std::string::size_type end_pos = message.find('m', pos);
if (end_pos != std::string::npos) {
std::string ansi_code = message.substr(pos + 2, end_pos - pos - 2);
current_color = css_color_from_ansi(ansi_code);
start = end_pos + 1;
} else {
break;
}
}

if (start < message.size()) {
std::string part = message.substr(start);
EM_ASM_({ console.log('%c' + UTF8ToString($0), 'color: ' + UTF8ToString($1)); }, part.c_str(), current_color.c_str());
}
}
# endif // __EMSCRIPTEN__

/// \brief Resets the console text color to the default.
void reset_color() {
Expand Down
8 changes: 7 additions & 1 deletion include/logit_cpp/logit/loggers/FileLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ namespace logit {
void wait() override {}

private:
void warn() const { std::cerr << "FileLogger is not supported under Emscripten" << std::endl; }
void warn() const {
static bool warned = false;
if (!warned) {
warned = true;
std::cerr << "FileLogger is not supported under Emscripten" << std::endl;
}
}
std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
};

Expand Down
8 changes: 7 additions & 1 deletion include/logit_cpp/logit/loggers/UniqueFileLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ namespace logit {
void wait() override {}

private:
void warn() const { std::cerr << "UniqueFileLogger is not supported under Emscripten" << std::endl; }
void warn() const {
static bool warned = false;
if (!warned) {
warned = true;
std::cerr << "UniqueFileLogger is not supported under Emscripten" << std::endl;
}
}
std::atomic<int> m_log_level = ATOMIC_VAR_INIT(static_cast<int>(LogLevel::LOG_LVL_TRACE));
};

Expand Down
Loading
Loading