CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
logging.cpp
Go to the documentation of this file.
1
6#include "logging.h"
7
8#include <algorithm>
9#include <chrono>
10#include <cstdlib>
11#include <ctime>
12#include <iostream>
13
14#if defined(_WIN32)
15#include <io.h>
16#define CPPXDIC_ISATTY(fd) _isatty(fd)
17#define CPPXDIC_FILENO(f) _fileno(f)
18#else
19#include <unistd.h>
20#define CPPXDIC_ISATTY(fd) ::isatty(fd)
21#define CPPXDIC_FILENO(f) ::fileno(f)
22#endif
23
24namespace cppxdic {
25namespace log {
26
27namespace {
28
29std::string toLower(std::string s) {
30 std::transform(s.begin(), s.end(), s.begin(),
31 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
32 return s;
33}
34
35const char* baseName(const char* path) {
36 if (!path) return "";
37 const char* base = path;
38 for (const char* p = path; *p; ++p) {
39 if (*p == '/' || *p == '\\') base = p + 1;
40 }
41 return base;
42}
43
44const char* colorFor(Level l) {
45 switch (l) {
46 case Level::Trace:
47 return "\033[37m"; // grey
48 case Level::Debug:
49 return "\033[36m"; // cyan
50 case Level::Info:
51 return "\033[32m"; // green
52 case Level::Warn:
53 return "\033[33m"; // yellow
54 case Level::Error:
55 return "\033[31m"; // red
56 default:
57 return "";
58 }
59}
60const char* colorReset() {
61 return "\033[0m";
62}
63
64std::string timestamp() {
65 using namespace std::chrono;
66 const auto now = system_clock::now();
67 const auto t = system_clock::to_time_t(now);
68 const auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count() % 1000;
69 std::tm tm_buf{};
70#if defined(_WIN32)
71 localtime_s(&tm_buf, &t);
72#else
73 localtime_r(&t, &tm_buf);
74#endif
75 char buf[32];
76 std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_buf);
77 char out[40];
78 std::snprintf(out, sizeof(out), "%s.%03d", buf, static_cast<int>(ms));
79 return out;
80}
81
82std::string rtrim(const std::string& s) {
83 std::size_t end = s.size();
84 while (end > 0) {
85 const char c = s[end - 1];
86 if (c == '\n' || c == '\r' || c == ' ' || c == '\t')
87 --end;
88 else
89 break;
90 }
91 return s.substr(0, end);
92}
93
94} // namespace
95
96Level levelFromString(const std::string& s, Level fallback) {
97 const std::string v = toLower(s);
98 if (v == "trace") return Level::Trace;
99 if (v == "debug") return Level::Debug;
100 if (v == "info") return Level::Info;
101 if (v == "warn" || v == "warning") return Level::Warn;
102 if (v == "error" || v == "err") return Level::Error;
103 if (v == "off" || v == "none" || v == "silent") return Level::Off;
104 return fallback;
105}
106
107const char* levelName(Level l) {
108 switch (l) {
109 case Level::Trace:
110 return "TRACE";
111 case Level::Debug:
112 return "DEBUG";
113 case Level::Info:
114 return "INFO ";
115 case Level::Warn:
116 return "WARN ";
117 case Level::Error:
118 return "ERROR";
119 case Level::Off:
120 return "OFF ";
121 }
122 return "?????";
123}
124
126 static Logger logger;
127 return logger;
128}
129
130Logger::Logger() {
131 color_ = CPPXDIC_ISATTY(CPPXDIC_FILENO(stderr)) != 0;
132}
133
134void Logger::ensureEnv() {
135 if (!env_done_) {
136 env_done_ = true;
138 }
139}
140
142 if (const char* lvl = std::getenv("CPPXDIC_LOG_LEVEL")) {
143 console_level_ = levelFromString(lvl, console_level_);
144 }
145 if (const char* con = std::getenv("CPPXDIC_LOG_CONSOLE")) {
146 const std::string v = toLower(con);
147 console_enabled_ = !(v == "0" || v == "false" || v == "off" || v == "no");
148 }
149 if (const char* path = std::getenv("CPPXDIC_LOG_FILE")) {
150 if (path[0] != '\0') file_.open(path, std::ios::out | std::ios::app);
151 }
152}
153
155 std::lock_guard<std::mutex> lk(mtx_);
156 console_level_ = l;
157}
158
160 std::lock_guard<std::mutex> lk(mtx_);
161 file_level_ = l;
162}
163
165 std::lock_guard<std::mutex> lk(mtx_);
166 return console_level_;
167}
168
170 std::lock_guard<std::mutex> lk(mtx_);
171 return file_level_;
172}
173
174bool Logger::setFile(const std::string& path) {
175 std::lock_guard<std::mutex> lk(mtx_);
176 if (file_.is_open()) file_.close();
177 if (path.empty()) return true;
178 file_.open(path, std::ios::out | std::ios::app);
179 return file_.is_open();
180}
181
183 std::lock_guard<std::mutex> lk(mtx_);
184 console_enabled_ = on;
185}
186
188 std::lock_guard<std::mutex> lk(mtx_);
189 verbose_format_ = on;
190}
191
193 std::lock_guard<std::mutex> lk(mtx_);
194 ensureEnv();
195 if (l == Level::Off) return false;
196 const bool to_console = console_enabled_ && l >= console_level_;
197 const bool to_file = file_.is_open() && l >= file_level_;
198 return to_console || to_file;
199}
200
201void Logger::write(Level l, const char* file, int line, const std::string& msg) {
202 std::lock_guard<std::mutex> lk(mtx_);
203 ensureEnv();
204 if (l == Level::Off) return;
205
206 const std::string text = rtrim(msg);
207 const char* fname = baseName(file);
208
209 if (console_enabled_ && l >= console_level_) {
210 std::ostream& os = (l >= Level::Warn) ? std::cerr : std::cout;
211 if (color_) os << colorFor(l);
212 os << "[" << levelName(l) << "]";
213 if (color_) os << colorReset();
214 if (verbose_format_) os << " " << timestamp() << " " << fname << ":" << line;
215 os << " " << text << "\n";
216 }
217
218 if (file_.is_open() && l >= file_level_) {
219 file_ << timestamp() << " [" << levelName(l) << "] " << fname << ":" << line << " " << text
220 << "\n";
221 file_.flush();
222 }
223}
224
228
229bool setFile(const std::string& path) {
230 return Logger::instance().setFile(path);
231}
232
233void configureFromOptions(const Options& opts) {
234 Logger& lg = Logger::instance();
235
236 // 1+2. Apply environment first (establishes the baseline console level/file).
237 lg.configureFromEnv();
238
239 // 3+4. Explicit level from config/CLI overrides the env baseline.
240 Level level = lg.consoleLevel();
241 if (opts.console_level_set) level = opts.console_level;
242
243 // Relative adjustments: -V lowers the threshold, -q raises it.
244 auto step_down = [](Level x, int n) {
245 int v = static_cast<int>(x) - n;
246 if (v < static_cast<int>(Level::Trace)) v = static_cast<int>(Level::Trace);
247 return static_cast<Level>(v);
248 };
249 if (opts.verbose > 0) level = step_down(level, opts.verbose);
250 if (opts.quiet) level = Level::Warn;
251
252 // Debug mode forces at least Debug verbosity and turns on rich formatting.
253 if (opts.debug && level > Level::Debug) level = Level::Debug;
254 lg.setVerboseFormat(opts.debug);
255
256 lg.setConsoleLevel(level);
257
258 if (!opts.log_file.empty()) lg.setFile(opts.log_file);
259
260 // Propagate to the ncorr library (it reads these on first use). setenv keeps
261 // the engine's logs aligned with this process's level and file without a
262 // compile-time dependency on ncorr's logger.
263 const char* name = levelName(level);
264 std::string lname(name);
265 // Trim trailing padding spaces from the fixed-width name.
266 while (!lname.empty() && lname.back() == ' ') lname.pop_back();
267 std::transform(lname.begin(), lname.end(), lname.begin(),
268 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
269#if !defined(_WIN32)
270 setenv("NCORR_LOG_LEVEL", lname.c_str(), 1);
271 if (!opts.log_file.empty()) setenv("NCORR_LOG_FILE", opts.log_file.c_str(), 1);
272#endif
273}
274
275} // namespace log
276} // namespace cppxdic
Process-wide singleton logger.
Definition logging.h:52
Level fileLevel() const
Definition logging.cpp:169
void setVerboseFormat(bool on)
Include timestamp + source location in console output too (the file sink always includes them).
Definition logging.cpp:187
void setFileLevel(Level l)
Definition logging.cpp:159
void setConsoleLevel(Level l)
Definition logging.cpp:154
bool setFile(const std::string &path)
Open (or replace) the file sink.
Definition logging.cpp:174
bool enabled(Level l)
True if a message at l would reach any sink.
Definition logging.cpp:192
Level consoleLevel() const
Definition logging.cpp:164
void configureFromEnv()
Apply CPPXDIC_LOG_LEVEL / CPPXDIC_LOG_FILE / CPPXDIC_LOG_CONSOLE.
Definition logging.cpp:141
static Logger & instance()
Definition logging.cpp:125
void write(Level l, const char *file, int line, const std::string &msg)
Emit a fully-formed message (trailing newlines trimmed) at l.
Definition logging.cpp:201
void setConsoleEnabled(bool on)
Enable or disable console output entirely.
Definition logging.cpp:182
#define CPPXDIC_ISATTY(fd)
Definition logging.cpp:20
#define CPPXDIC_FILENO(f)
Definition logging.cpp:21
Lightweight, dependency-free logging facility for CPPxDIC.
void configureFromOptions(const Options &opts)
Apply env vars, then the supplied options (CLI/config), to the singleton.
Definition logging.cpp:233
void setLevel(Level l)
Convenience accessors.
Definition logging.cpp:225
Level
Severity levels, ordered from most to least verbose.
Definition logging.h:40
bool setFile(const std::string &path)
Definition logging.cpp:229
Level levelFromString(const std::string &s, Level fallback=Level::Info)
Parse a level name (case-insensitive): trace, debug, info, warn|warning, error, off.
Definition logging.cpp:96
const char * levelName(Level l)
Short, fixed-width display name for a level (e.g. "INFO ").
Definition logging.cpp:107
DataSerializer - Abstract base class for format-agnostic DIC data I/O.
Aggregated options resolved from env/config/CLI, applied in one call.
Definition logging.h:98
bool console_level_set
true if an explicit level was chosen
Definition logging.h:100
std::string log_file
empty = no file sink
Definition logging.h:101
int verbose
-V count: lowers threshold per step
Definition logging.h:104
bool debug
debug mode (Debug level + verbose format)
Definition logging.h:102
bool quiet
force console threshold up to Warn
Definition logging.h:103