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);
286 if (!s_sink) {
return; }
287 spdlog::apply_all([](std::shared_ptr<spdlog::logger> l) {
288 auto& sinks = l->sinks();
290 std::remove(sinks.begin(), sinks.end(), s_sink),
323 if (log_dir.empty()) {
return; }
324 auto model_file = log_dir /
"session_model.log";
326 std::filesystem::create_directories(model_file.parent_path(), ec);
327 FILE* fp = fopen(model_file.string().c_str(),
"w");
328 if (fp) { fclose(fp); }
350 if (i + 1 >= text.size() || text[i + 1] !=
'[') {
355 while (i + 1 < text.size() && text[i + 1] !=
'm') {
359 if (i + 1 < text.size()) { ++i; }
370 static const auto tbl = []() {
371 std::array<const char*, 256> t{};
372 t[
static_cast<unsigned char>(
'\n')] =
"\\n";
373 t[
static_cast<unsigned char>(
'\r')] =
"\\r";
374 t[
static_cast<unsigned char>(
'\t')] =
"\\t";
375 t[
static_cast<unsigned char>(
'{')] =
"{{";
376 t[
static_cast<unsigned char>(
'}')] =
"}}";
391 const char* esc =
escape_table()[
static_cast<unsigned char>(c)];
392 if (!esc) {
return false; }
411 result.reserve(text.size() + text.size() / 8);
412 for (
size_t i = 0; i < text.size(); ++i) {
414 if (text[i] ==
'\033') {
escape_ansi(text, i, result); }
415 else { result += text[i]; }
432 const std::shared_ptr<spdlog::logger>& logger,
433 spdlog::level::level_enum level,
434 const std::string& label,
435 const std::string& text)
449 const std::shared_ptr<spdlog::logger>& logger,
450 const std::string& label,
453 logger->info(
"{}: {:.1f}ms", label, ms);
465 const std::shared_ptr<spdlog::logger>& logger,
469 double tok_s = (time_ms > 0.0)
470 ? (
static_cast<double>(count) / time_ms * 1000.0) : 0.0;
471 logger->info(
"{} tokens, {:.1f}ms, {:.1f} tok/s",
472 count, time_ms, tok_s);
485 const std::shared_ptr<spdlog::logger>& logger,
486 const std::string& label,
487 const std::string& key,
488 const std::string& value)
490 logger->info(
"{}: {} -> {}", label, key, value);
501 int handle_id,
const std::filesystem::path& log_dir)
503 if (handle_id == 0 || log_dir.empty()) {
return; }
504 if (!s_dispatcher) {
init(spdlog::level::info); }
506 auto log_file = log_dir /
"session.log";
507 std::filesystem::create_directories(log_file.parent_path());
509 std::make_shared<spdlog::sinks::basic_file_sink_mt>(
510 log_file.string(),
true);
511 file_sink->set_level(spdlog::level::trace);
512 file_sink->set_pattern(
513 "[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v");
514 s_dispatcher->register_sink(handle_id, file_sink);
518 auto model_file = log_dir /
"session_model.log";
519 FILE* fp = fopen(model_file.string().c_str(),
"w");
520 if (fp) { fclose(fp); }
522 spdlog::flush_on(spdlog::level::trace);
531 if (!s_dispatcher || handle_id == 0) {
return; }
532 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).
static void remove_console_sink()
Set up session logging for a project directory.
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.