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
175 : logger(get()) {}
176
177 // Implicit conversion to Logger* for seamless use with QUILL_LOG_* macros
178 operator quill::Logger *() const noexcept { return logger; }
179
180 // Support QUILL_LOG_ macro pointer dereference
181 quill::Logger *operator->() const noexcept { return logger; }
182
183 // Allow dynamic re-assignment (e.g. switching to _traceback logger)
184 Scoped &operator=(quill::Logger *l) noexcept {
185 logger = l;
186 return *this;
187 }
188
189 // Explicit access if needed
190 [[nodiscard]] quill::Logger *ptr() const noexcept { return logger; }
191};
192
209 [[no_unique_address]] quill::Logger *logger{nullptr};
210
211 FileScoped(std::string_view name, std::string_view filename,
212 std::string_view pattern = "%(message)") noexcept
213 : logger(get_file(name, filename, pattern)) {}
214
215 operator quill::Logger *() const noexcept { return logger; }
216 // Support QUILL_LOG_ macro pointer dereference
217 quill::Logger *operator->() const noexcept { return logger; }
218 FileScoped &operator=(quill::Logger *l) noexcept {
219 logger = l;
220 return *this;
221 }
222 [[nodiscard]] quill::Logger *ptr() const noexcept { return logger; }
223};
224
225} // namespace eonc::log
226
227// Force MSVC to evaluate __VA_ARGS__ properly
228#define EONC_EXPAND(x) x
229
238#define EONC_LOG_TRACE(...) \
239 do { \
240 if (auto *l = eonc::log::get()) { \
241 EONC_EXPAND(QUILL_LOG_TRACE_L1(l, __VA_ARGS__)); \
242 } \
243 } while (0)
244#define EONC_LOG_DEBUG(...) \
245 do { \
246 if (auto *l = eonc::log::get()) { \
247 EONC_EXPAND(QUILL_LOG_DEBUG(l, __VA_ARGS__)); \
248 } \
249 } while (0)
250#define EONC_LOG_INFO(...) \
251 do { \
252 if (auto *l = eonc::log::get()) { \
253 EONC_EXPAND(QUILL_LOG_INFO(l, __VA_ARGS__)); \
254 } \
255 } while (0)
256#define EONC_LOG_WARNING(...) \
257 do { \
258 if (auto *l = eonc::log::get()) { \
259 EONC_EXPAND(QUILL_LOG_WARNING(l, __VA_ARGS__)); \
260 } \
261 } while (0)
262#define EONC_LOG_ERROR(...) \
263 do { \
264 if (auto *l = eonc::log::get()) { \
265 EONC_EXPAND(QUILL_LOG_ERROR(l, __VA_ARGS__)); \
266 } \
267 } while (0)
268#define EONC_LOG_CRITICAL(...) \
269 do { \
270 if (auto *l = eonc::log::get()) { \
271 EONC_EXPAND(QUILL_LOG_CRITICAL(l, __VA_ARGS__)); \
272 } \
273 } 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:209
FileScoped(std::string_view name, std::string_view filename, std::string_view pattern="%(message)") noexcept
Definition EonLogger.h:211
quill::Logger * operator->() const noexcept
Definition EonLogger.h:217
quill::Logger * ptr() const noexcept
Definition EonLogger.h:222
FileScoped & operator=(quill::Logger *l) noexcept
Definition EonLogger.h:218
Scoped() noexcept
Definition EonLogger.h:174
quill::Logger * ptr() const noexcept
Definition EonLogger.h:190
quill::Logger * operator->() const noexcept
Definition EonLogger.h:181
quill::Logger * logger
Definition EonLogger.h:172
Scoped & operator=(quill::Logger *l) noexcept
Definition EonLogger.h:184