Loading...
Searching...
No Matches
EonLogger.h
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12#pragma once
13
14#include "quill/Backend.h"
15#include "quill/Frontend.h"
16#include "quill/LogMacros.h"
17#include "quill/Logger.h"
18#include "quill/sinks/ConsoleSink.h"
19#include "quill/sinks/FileSink.h"
20#include <atomic>
21#include <filesystem>
22#include <mutex>
23#include <source_location>
24#include <string>
25#include <string_view>
26#include <system_error>
27
28namespace eonc::log {
29
30// Logger name constants for internal use
31namespace detail {
32inline constexpr std::string_view kDefaultLoggerName{"combi"};
33inline constexpr std::string_view kTracebackLoggerName{"_traceback"};
34} // namespace detail
35
44[[nodiscard]] inline quill::Logger *get() noexcept {
45 static std::atomic<quill::Logger *> cached_logger{nullptr};
46 if (quill::Logger *l = cached_logger.load(std::memory_order_relaxed)) {
47 return l;
48 }
49
50 if (auto *logger = quill::Frontend::get_logger(
51 std::string(detail::kDefaultLoggerName))) {
52 cached_logger.store(logger, std::memory_order_relaxed);
53 return logger;
54 }
55
56 // Fallback for Python bindings/tests that didn't run ClientEON's main()
57 static quill::Logger *fallback = []() -> quill::Logger * {
58 try {
59 quill::Backend::start();
60 } catch (...) {
61 } // Ignore if backend already started
62
63 try {
64 auto console_sink =
65 quill::Frontend::create_or_get_sink<quill::ConsoleSink>(
66 "fallback_console");
67 return quill::Frontend::create_or_get_logger(
68 "combi", std::move(console_sink),
69 quill::PatternFormatterOptions{"%(message)"},
70 quill::ClockSourceType::System);
71 } catch (...) {
72 return nullptr;
73 }
74 }();
75 if (fallback)
76 cached_logger.store(fallback, std::memory_order_relaxed);
77 return fallback;
78}
79
88[[nodiscard]] inline quill::Logger *traceback() noexcept {
89 static std::atomic<quill::Logger *> cached{nullptr};
90 if (quill::Logger *l = cached.load(std::memory_order_relaxed)) {
91 return l;
92 }
93 if (auto *logger = quill::Frontend::get_logger(
94 std::string(detail::kTracebackLoggerName))) {
95 cached.store(logger, std::memory_order_relaxed);
96 return logger;
97 }
98 return nullptr;
99}
100
110[[nodiscard]] inline quill::Logger *
111get_file(std::string_view name, std::string_view filename,
112 std::string_view pattern = "%(message)") {
113 // If the logger already exists, return it immediately.
114 // Quill strictly checks configuration matches on recreation and throws if
115 // PatternFormatterOptions differ, which crashes instances (like
116 // Dimer/Optimizers) that are created multiple times in loops (e.g., NEB).
117 if (auto *existing = quill::Frontend::get_logger(std::string(name))) {
118 return existing;
119 }
120 static std::mutex file_logger_mutex;
121 std::lock_guard<std::mutex> lock(file_logger_mutex);
122 if (auto *existing = quill::Frontend::get_logger(std::string(name))) {
123 return existing;
124 }
125
126 try {
127 // Durable absolute path: optimizers open "_lbfgs.log" etc. under CWD.
128 // Job integration fixtures chdir into /tmp/eon_test_job_* then delete that
129 // tree when the case ends, while Quill's process-lifetime FileSink still
130 // flushes — "fopen failed after 3 attempts". Keep logs under a stable
131 // temp subdir instead of the disposable workdir.
132 namespace fs = std::filesystem;
133 fs::path path{std::string(filename)};
134 if (!path.is_absolute()) {
135 static const fs::path log_dir = [] {
136 fs::path d = fs::temp_directory_path() / "eon_logs";
137 std::error_code ec;
138 fs::create_directories(d, ec);
139 return d;
140 }();
141 path = log_dir / path.filename();
142 }
143 quill::FileSinkConfig cfg;
144 cfg.set_open_mode('w');
145 auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
146 path.string(), cfg);
147 return quill::Frontend::create_or_get_logger(
148 std::string(name), std::move(file_sink),
149 quill::PatternFormatterOptions{std::string(pattern)},
150 quill::ClockSourceType::System);
151 } catch (...) {
152 return get(); // Fallback to default combi logger if creation fails
153 }
154}
155
171struct Scoped {
172 [[no_unique_address]] quill::Logger *logger{nullptr};
173
174 Scoped() noexcept : logger(get()) {}
175
176 // Implicit conversion to Logger* for seamless use with QUILL_LOG_* macros
177 operator quill::Logger *() const noexcept { return logger; }
178
179 // Support QUILL_LOG_ macro pointer dereference
180 quill::Logger *operator->() const noexcept { return logger; }
181
182 // Allow dynamic re-assignment (e.g. switching to _traceback logger)
183 Scoped &operator=(quill::Logger *l) noexcept {
184 logger = l;
185 return *this;
186 }
187
188 // Explicit access if needed
189 [[nodiscard]] quill::Logger *ptr() const noexcept { return logger; }
190};
191
208 [[no_unique_address]] quill::Logger *logger{nullptr};
209
210 FileScoped(std::string_view name, std::string_view filename,
211 std::string_view pattern = "%(message)") noexcept
212 : logger(get_file(name, filename, pattern)) {}
213
214 operator quill::Logger *() const noexcept { return logger; }
215 // Support QUILL_LOG_ macro pointer dereference
216 quill::Logger *operator->() const noexcept { return logger; }
217 FileScoped &operator=(quill::Logger *l) noexcept {
218 logger = l;
219 return *this;
220 }
221 [[nodiscard]] quill::Logger *ptr() const noexcept { return logger; }
222};
223
224} // namespace eonc::log
225
226// Force MSVC to evaluate __VA_ARGS__ properly
227#define EONC_EXPAND(x) x
228
237#define EONC_LOG_TRACE(...) \
238 do { \
239 if (auto *l = eonc::log::get()) { \
240 EONC_EXPAND(QUILL_LOG_TRACE_L1(l, __VA_ARGS__)); \
241 } \
242 } while (0)
243#define EONC_LOG_DEBUG(...) \
244 do { \
245 if (auto *l = eonc::log::get()) { \
246 EONC_EXPAND(QUILL_LOG_DEBUG(l, __VA_ARGS__)); \
247 } \
248 } while (0)
249#define EONC_LOG_INFO(...) \
250 do { \
251 if (auto *l = eonc::log::get()) { \
252 EONC_EXPAND(QUILL_LOG_INFO(l, __VA_ARGS__)); \
253 } \
254 } while (0)
255#define EONC_LOG_WARNING(...) \
256 do { \
257 if (auto *l = eonc::log::get()) { \
258 EONC_EXPAND(QUILL_LOG_WARNING(l, __VA_ARGS__)); \
259 } \
260 } while (0)
261#define EONC_LOG_ERROR(...) \
262 do { \
263 if (auto *l = eonc::log::get()) { \
264 EONC_EXPAND(QUILL_LOG_ERROR(l, __VA_ARGS__)); \
265 } \
266 } while (0)
267#define EONC_LOG_CRITICAL(...) \
268 do { \
269 if (auto *l = eonc::log::get()) { \
270 EONC_EXPAND(QUILL_LOG_CRITICAL(l, __VA_ARGS__)); \
271 } \
272 } while (0)
constexpr std::string_view kTracebackLoggerName
Definition EonLogger.h:33
constexpr std::string_view kDefaultLoggerName
Definition EonLogger.h:32
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
quill::Logger * traceback() noexcept
Get or create the "_traceback" logger for traceback logging.
Definition EonLogger.h:88
quill::Logger * get_file(std::string_view name, std::string_view filename, std::string_view pattern="%(message)")
Get or create a named logger with a file sink.
Definition EonLogger.h:111
quill::Logger * logger
Definition EonLogger.h:208
FileScoped(std::string_view name, std::string_view filename, std::string_view pattern="%(message)") noexcept
Definition EonLogger.h:210
quill::Logger * operator->() const noexcept
Definition EonLogger.h:216
quill::Logger * ptr() const noexcept
Definition EonLogger.h:221
FileScoped & operator=(quill::Logger *l) noexcept
Definition EonLogger.h:217
Scoped() noexcept
Definition EonLogger.h:174
quill::Logger * ptr() const noexcept
Definition EonLogger.h:189
quill::Logger * operator->() const noexcept
Definition EonLogger.h:180
quill::Logger * logger
Definition EonLogger.h:172
Scoped & operator=(quill::Logger *l) noexcept
Definition EonLogger.h:183