9#include <spdlog/sinks/basic_file_sink.h>
10#include <spdlog/sinks/stdout_color_sinks.h>
11#include <spdlog/sinks/sink.h>
17#include <unordered_map>
18#include <unordered_set>
20namespace entropic::log {
22static std::once_flag s_init_flag;
23static std::shared_ptr<spdlog::sinks::stderr_color_sink_mt> s_sink;
43 void log(
const spdlog::details::log_msg& msg)
override {
44 std::shared_ptr<spdlog::sinks::sink> target;
47 std::lock_guard lk(mu_);
48 auto it = sinks_.find(
id);
49 if (it != sinks_.end()) { target = it->second; }
51 if (target) { target->log(msg); }
56 std::vector<std::shared_ptr<spdlog::sinks::sink>> all;
58 std::lock_guard lk(mu_);
59 all.reserve(sinks_.size());
60 for (
auto& [_, s] : sinks_) { all.push_back(s); }
62 for (
auto& s : all) { s->flush(); }
67 std::lock_guard lk(mu_);
69 for (
auto& [_, s] : sinks_) { s->set_pattern(pattern); }
78 std::lock_guard lk(mu_);
79 if (!sinks_.empty()) {
84 auto it = sinks_.begin();
85 it->second->set_formatter(std::move(f));
91 std::lock_guard lk(mu_);
92 sinks_[id] = std::move(s);
93 if (!pattern_.empty()) { sinks_[id]->set_pattern(pattern_); }
98 std::lock_guard lk(mu_);
105 std::lock_guard lk(mu_);
106 std::vector<int> ids;
107 ids.reserve(sinks_.size());
108 for (
auto& [
id, _] : sinks_) { ids.push_back(
id); }
113 mutable std::mutex mu_;
114 std::unordered_map<int, std::shared_ptr<spdlog::sinks::sink>> sinks_;
115 std::string pattern_;
118static std::shared_ptr<HandleAwareSink> s_dispatcher;
126static std::atomic<bool> s_console_disabled{
false};
132static std::mutex s_session_paths_mu;
133static std::unordered_set<std::string> s_session_paths;
145void init(spdlog::level::level_enum level) {
146 std::call_once(s_init_flag, [level]() {
147 s_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
148 s_sink->set_level(level);
155 s_dispatcher = std::make_shared<HandleAwareSink>();
156 s_dispatcher->set_level(level);
157 auto root = std::make_shared<spdlog::logger>(
159 spdlog::sinks_init_list{s_sink, s_dispatcher});
160 spdlog::set_default_logger(root);
161 spdlog::set_level(level);
162 spdlog::set_pattern(
"[%Y-%m-%d %H:%M:%S.%e] [%n] [%^%l%$] %v");
180 std::filesystem::create_directories(path.parent_path());
181 auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(
182 path.string(),
true);
183 file_sink->set_level(spdlog::level::trace);
184 file_sink->set_pattern(
"[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v");
187 spdlog::default_logger()->sinks().push_back(file_sink);
190 spdlog::apply_all([&file_sink](std::shared_ptr<spdlog::logger> l) {
191 l->sinks().push_back(file_sink);
195 spdlog::flush_on(spdlog::level::trace);
211std::shared_ptr<spdlog::logger>
get(
const std::string& name) {
212 auto logger = spdlog::get(name);
217 init(spdlog::level::info);
219 auto default_logger = spdlog::default_logger();
220 auto new_logger = std::make_shared<spdlog::logger>(
221 name, default_logger->sinks().begin(),
222 default_logger->sinks().end());
226 if (s_console_disabled.load() && s_sink) {
227 auto& sinks = new_logger->sinks();
229 std::remove(sinks.begin(), sinks.end(), s_sink),
232 new_logger->set_level(default_logger->level());
233 spdlog::register_logger(new_logger);
247 s_console_disabled.store(
true);
253 auto strip = [](std::shared_ptr<spdlog::logger> l) {
254 auto& sinks = l->sinks();
256 std::remove(sinks.begin(), sinks.end(), s_sink),
259 if (
auto dl = spdlog::default_logger()) {
262 spdlog::apply_all(strip);
293 if (log_dir.empty()) {
return; }
294 auto model_file = log_dir /
"session_model.log";
296 std::filesystem::create_directories(model_file.parent_path(), ec);
297 FILE* fp = fopen(model_file.string().c_str(),
"w");
298 if (fp) { fclose(fp); }
320 if (i + 1 >= text.size() || text[i + 1] !=
'[') {
325 while (i + 1 < text.size() && text[i + 1] !=
'm') {
329 if (i + 1 < text.size()) { ++i; }
340 static const auto tbl = []() {
341 std::array<const char*, 256> t{};
342 t[
static_cast<unsigned char>(
'\n')] =
"\\n";
343 t[
static_cast<unsigned char>(
'\r')] =
"\\r";
344 t[
static_cast<unsigned char>(
'\t')] =
"\\t";
345 t[
static_cast<unsigned char>(
'{')] =
"{{";
346 t[
static_cast<unsigned char>(
'}')] =
"}}";
361 const char* esc =
escape_table()[
static_cast<unsigned char>(c)];
362 if (!esc) {
return false; }
381 result.reserve(text.size() + text.size() / 8);
382 for (
size_t i = 0; i < text.size(); ++i) {
384 if (text[i] ==
'\033') {
escape_ansi(text, i, result); }
385 else { result += text[i]; }
402 const std::shared_ptr<spdlog::logger>& logger,
403 spdlog::level::level_enum level,
404 const std::string& label,
405 const std::string& text)
419 const std::shared_ptr<spdlog::logger>& logger,
420 const std::string& label,
423 logger->info(
"{}: {:.1f}ms", label, ms);
435 const std::shared_ptr<spdlog::logger>& logger,
439 double tok_s = (time_ms > 0.0)
440 ? (
static_cast<double>(count) / time_ms * 1000.0) : 0.0;
441 logger->info(
"{} tokens, {:.1f}ms, {:.1f} tok/s",
442 count, time_ms, tok_s);
455 const std::shared_ptr<spdlog::logger>& logger,
456 const std::string& label,
457 const std::string& key,
458 const std::string& value)
460 logger->info(
"{}: {} -> {}", label, key, value);
471 int handle_id,
const std::filesystem::path& log_dir)
473 if (handle_id == 0 || log_dir.empty()) {
return; }
474 if (!s_dispatcher) {
init(spdlog::level::info); }
476 auto log_file = log_dir /
"session.log";
477 std::filesystem::create_directories(log_file.parent_path());
479 std::make_shared<spdlog::sinks::basic_file_sink_mt>(
480 log_file.string(),
true);
481 file_sink->set_level(spdlog::level::trace);
482 file_sink->set_pattern(
483 "[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v");
484 s_dispatcher->register_sink(handle_id, file_sink);
488 auto model_file = log_dir /
"session_model.log";
489 FILE* fp = fopen(model_file.string().c_str(),
"w");
490 if (fp) { fclose(fp); }
492 spdlog::flush_on(spdlog::level::trace);
501 if (!s_dispatcher || handle_id == 0) {
return; }
502 s_dispatcher->unregister_sink(handle_id);
Per-handle file sink dispatcher (installed on default logger).
void set_pattern(const std::string &pattern) override
Apply pattern to all registered sinks.
void flush() override
Flush every registered handle sink.
void unregister_sink(int id)
Remove a handle id's sink registration.
void register_sink(int id, std::shared_ptr< spdlog::sinks::sink > s)
Register/replace the sink for a handle id.
std::vector< int > registered_ids() const
Snapshot of registered ids (tests + diagnostics).
void set_formatter(std::unique_ptr< spdlog::formatter > f) override
Spdlog's set_formatter shim.
void log(const spdlog::details::log_msg &msg) override
Route message to the current thread's handle file sink.
~HandleLogScope()
Exit scope; restores previous handle_id.
HandleLogScope(int handle_id)
Enter scope; sets thread current handle_id.
static thread_local int t_current_handle_id
Thread-local current handle id (0 = no scope active).
bool escape_char(char c, std::string &result)
Append escaped form of c if special, else return false.
void escape_ansi(const std::string &text, size_t &i, std::string &result)
Consume an ANSI CSI sequence starting at the ESC byte.
static const std::array< const char *, 256 > & escape_table()
256-entry table mapping bytes to escape strings (nullptr = passthrough).
spdlog initialization and logger access.
ENTROPIC_EXPORT std::string escape_content(const std::string &text)
Escape content for safe spdlog formatting.
ENTROPIC_EXPORT void add_file_sink(const std::filesystem::path &path)
Add a file sink to all loggers (truncated per run).
ENTROPIC_EXPORT int current_handle_id()
gh#59 (v2.3.1): query the current thread's handle_id.
ENTROPIC_EXPORT void log_tokens(const std::shared_ptr< spdlog::logger > &logger, int count, double time_ms)
Log token count with throughput.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
ENTROPIC_EXPORT void init(spdlog::level::level_enum level=spdlog::level::info)
Initialize the logging subsystem.
ENTROPIC_EXPORT void set_console_enabled(bool enabled)
Enable or disable the stderr console sink process-wide.
ENTROPIC_EXPORT void register_handle_log(int handle_id, const std::filesystem::path &log_dir)
gh#59 (v2.3.1): register a per-handle session.log file.
ENTROPIC_EXPORT void log_content(const std::shared_ptr< spdlog::logger > &logger, spdlog::level::level_enum level, const std::string &label, const std::string &text)
Log full content with escaping applied.
ENTROPIC_EXPORT void setup_session(const std::filesystem::path &log_dir)
Set up session logging for a project directory.
ENTROPIC_EXPORT void log_decision(const std::shared_ptr< spdlog::logger > &logger, const std::string &label, const std::string &key, const std::string &value)
Log a decision/routing event.
ENTROPIC_EXPORT void unregister_handle_log(int handle_id)
gh#59 (v2.3.1): release a handle's session.log file sink.
ENTROPIC_EXPORT void log_timing(const std::shared_ptr< spdlog::logger > &logger, const std::string &label, double ms)
Log a timing measurement.