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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,12 @@ jobs:
-DLOGIT_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
run: cmake --build build-ems --target ems_console ems_async_flush ems_single_thread_executor -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
node --no-experimental-fetch build-ems/tests/ems_single_thread_executor.js
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,40 @@ your workload needs a different baseline. See
[`docs/TaskExecutor.md`](docs/TaskExecutor.md) for a full breakdown and tuning
tips.

Logger backend `Config` structs can opt into `use_dedicated_executor=true` when
one slow sink must not delay other async loggers. On native builds this creates
one worker thread per configured logger, so use it deliberately for expensive or
isolated backends. Single-threaded Emscripten builds keep the same per-instance
queue semantics but drain cooperatively on the browser event loop.

You can always pass a configured backend through the generic macro:

```cpp
logit::ConsoleLogger::Config cfg;
cfg.async = true;
cfg.use_dedicated_executor = true;
cfg.queue_capacity = 1024;
cfg.queue_policy = logit::detail::QueuePolicy::Block;

LOGIT_ADD_LOGGER(
logit::ConsoleLogger,
(cfg),
logit::SimpleLogFormatter,
(LOGIT_CONSOLE_PATTERN)
);
```

Built-in helpers also expose config-first and short dedicated forms:

```cpp
LOGIT_ADD_CONSOLE_CONFIG(cfg, LOGIT_CONSOLE_PATTERN);
LOGIT_ADD_CONSOLE_DEDICATED(
LOGIT_CONSOLE_PATTERN,
1024,
logit::detail::QueuePolicy::DropNewest
);
```

## Features

- **Flexible Log Formatting**:
Expand Down
9 changes: 9 additions & 0 deletions docs/TaskExecutor.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ outlive static destructors inside logger components. Applications may call
`shutdown()` explicitly (for example during test teardown), but the singleton
remains valid until the process terminates.

Logger backends with `Config::use_dedicated_executor=true` own a
`SingleThreadExecutor` instead of using this singleton. Native builds create one
worker thread per configured logger, while single-threaded Emscripten builds use
a cooperative per-instance queue. `Logger::shutdown()` calls each backend's
`ILogger::shutdown()` hook before stopping the global executor so these
logger-owned workers drain and stop cleanly.

## 6. Emscripten (no pthreads)

When targeting Emscripten without pthread support:
Expand All @@ -118,6 +125,8 @@ When targeting Emscripten without pthread support:
* Tasks are executed by `emscripten_async_call`, which schedules a drain on the
browser event loop. This keeps logging compatible with the cooperative
execution model used in WebAssembly UI scenarios.
* Dedicated logger executors use the same cooperative scheduling model in this
build; no OS thread is created.
* Typical use cases: browser-hosted tools or demos that need asynchronous-style
logging without pulling in pthread support.

Expand Down
27 changes: 27 additions & 0 deletions docs/mainpage.dox
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,33 @@ LOGIT_SET_QUEUE_POLICY(LOGIT_QUEUE_BLOCK); // Block when full
\endcode
Available policies: `LOGIT_QUEUE_DROP_NEWEST`, `LOGIT_QUEUE_DROP_OLDEST`, `LOGIT_QUEUE_BLOCK`.

Backend `Config` structs can set `use_dedicated_executor=true` to isolate a slow
async sink from the global task executor. Native builds create one worker thread
per configured logger; single-threaded Emscripten builds use a cooperative
per-instance queue instead.

\code{.cpp}
logit::ConsoleLogger::Config cfg;
cfg.async = true;
cfg.use_dedicated_executor = true;
cfg.queue_capacity = 1024;
cfg.queue_policy = logit::detail::QueuePolicy::Block;

LOGIT_ADD_LOGGER(
logit::ConsoleLogger,
(cfg),
logit::SimpleLogFormatter,
(LOGIT_CONSOLE_PATTERN)
);

LOGIT_ADD_CONSOLE_CONFIG(cfg, LOGIT_CONSOLE_PATTERN);
LOGIT_ADD_CONSOLE_DEDICATED(
LOGIT_CONSOLE_PATTERN,
1024,
logit::detail::QueuePolicy::DropNewest
);
\endcode

\subsection stream_logging Stream-Based Logging

Use stream operators for complex messages.
Expand Down
20 changes: 15 additions & 5 deletions include/logit_cpp/logit/Logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,8 @@ namespace logit {
///
/// Ensures that all log messages are fully processed before continuing.
void wait() {
LoggerReadLock lock(m_loggers_mx);
for (const auto& strategy : m_loggers) {
const auto snapshot = get_all_strategy_snapshots();
for (const auto& strategy : snapshot) {
if (!strategy) continue;
strategy->logger->wait();
}
Expand All @@ -400,9 +400,14 @@ namespace logit {
/// Disables further logging, waits for asynchronous tasks to complete,
/// and shuts down TaskExecutor.
void shutdown() {
if (m_shutdown) return;
m_shutdown = true;
wait();
if (m_shutdown.exchange(true)) return;

const auto snapshot = get_all_strategy_snapshots();
for (const auto& strategy : snapshot) {
if (!strategy) continue;
std::lock_guard<std::mutex> exec_lock(strategy->exec_mx);
strategy->logger->shutdown();
}
detail::TaskExecutor::get_instance().shutdown();
}

Expand Down Expand Up @@ -441,6 +446,11 @@ namespace logit {
return std::shared_ptr<LoggerStrategy>();
}

std::vector<std::shared_ptr<LoggerStrategy>> get_all_strategy_snapshots() const {
LoggerReadLock lock(m_loggers_mx);
return m_loggers;
}

std::vector<std::shared_ptr<LoggerStrategy>> m_loggers; ///< Container for logger-formatter pairs.
mutable LoggerMutex m_loggers_mx; ///< Protects access to logger strategies.
std::atomic<bool> m_shutdown = ATOMIC_VAR_INIT(false); ///< Flag indicating if shutdown was requested.
Expand Down
16 changes: 16 additions & 0 deletions include/logit_cpp/logit/detail/QueuePolicy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once
#ifndef _LOGIT_DETAIL_QUEUE_POLICY_HPP_INCLUDED
#define _LOGIT_DETAIL_QUEUE_POLICY_HPP_INCLUDED

namespace logit { namespace detail {

/// \brief Queue overflow handling policy used by TaskExecutor and SingleThreadExecutor.
enum class QueuePolicy {
DropNewest, ///< Reject the incoming task when the queue is full.
DropOldest, ///< Drop the oldest queued task.
Block ///< Producers wait until capacity is available.
};

}} // namespace logit::detail

#endif // _LOGIT_DETAIL_QUEUE_POLICY_HPP_INCLUDED
Loading
Loading