├── .gitignore ├── test ├── Makefile └── test_logger.cpp ├── README.md ├── LICENSE └── logger.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | test_logger: test_logger.cpp ../logger.h 2 | g++ -Wall -O3 -otest_logger -I.. test_logger.cpp -std=c++11 -lpthread 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logger 2 | A very simple logging facility for C++11, header-only, single-file 3 | 4 | Just `#include "logger.h"` and then go for `INFO(...)`, `WARNING(...)` or `ERROR(...)`. 5 | 6 | Default logging is synchronous and goes to `stderr`, but it's easy to filter, send to multiple destination and having the logging being done asynchronously so that program execution is not slowed down by logging. 7 | 8 | Default context is source/line of the log command. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Andrea Griffini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/test_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.h" 2 | #include 3 | #include 4 | 5 | using namespace logger; 6 | 7 | struct String : RefCounted { 8 | std::string s; 9 | 10 | String(const std::string& s) : s(s) { 11 | INFO("String(\"%s\")", s.c_str()); 12 | } 13 | 14 | ~String() { 15 | INFO("~String(\"%s\")", s.c_str()); 16 | } 17 | }; 18 | 19 | struct SlowLogger : Logger { 20 | Ref L; 21 | SlowLogger(Ref L) : L(L) {} 22 | void log(const Entry& e) { 23 | std::this_thread::sleep_for(std::chrono::seconds(1)); 24 | L->log(e); 25 | } 26 | }; 27 | 28 | int main(int argc, const char *argv[]) { 29 | root() = new AsyncLogger(new SlowLogger(root())); 30 | Ref s = new String("This is a test"); 31 | INFO("Refcount/1 = %i", int(s->refcount)); 32 | Ref s2 = s; 33 | INFO("Refcount/2 = %i", int(s->refcount)); 34 | s2 = nullptr; 35 | INFO("Refcount/3 = %i", int(s->refcount)); 36 | s = nullptr; 37 | INFO("Done!"); 38 | while (root()->refcount > 1) { 39 | printf("waiting....\n"); 40 | std::this_thread::sleep_for(std::chrono::milliseconds(250)); 41 | } 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /logger.h: -------------------------------------------------------------------------------- 1 | #if !defined(LOG_H_INCLUDED) 2 | #define LOG_H_INCLUDED 3 | 4 | /***************************************************************************** 5 | 6 | MIT License 7 | 8 | Copyright (c) 2016 Andrea Griffini 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | ******************************************************************************/ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | namespace logger { 46 | 47 | // Intrusive refcounting utility 48 | struct RefCounted { 49 | std::atomic_int refcount; 50 | RefCounted() : refcount(0) {} 51 | }; 52 | 53 | template 54 | struct Ref { 55 | T *p; 56 | 57 | template 58 | Ref(U *p) : p(p) { 59 | if (p) p->refcount++; 60 | } 61 | 62 | template 63 | Ref(const Ref& other) : p(other.p) { 64 | if (p) p->refcount++; 65 | } 66 | 67 | void swap(Ref& other) { 68 | std::swap(p, other.p); 69 | } 70 | 71 | template 72 | Ref& operator=(const Ref& other) { 73 | swap(Ref(other.p)); 74 | return *this; 75 | } 76 | 77 | template 78 | Ref& operator=(U *u) { 79 | Ref(u).swap(*this); 80 | return *this; 81 | } 82 | 83 | Ref(const Ref& other) : p(other.p) { 84 | if (p) p->refcount++; 85 | } 86 | 87 | Ref& operator=(const Ref& other) { 88 | swap(Ref(other.p)); 89 | return *this; 90 | } 91 | 92 | Ref& operator=(std::nullptr_t) { 93 | if (p && --p->refcount==0) delete p; 94 | p = nullptr; 95 | return *this; 96 | } 97 | 98 | ~Ref() { 99 | if (p && --p->refcount==0) delete p; 100 | } 101 | 102 | T* operator->() { 103 | return p; 104 | } 105 | 106 | const T* operator->() const { 107 | return p; 108 | } 109 | 110 | T& operator*() { 111 | return *p; 112 | } 113 | 114 | const T& operator*() const { 115 | return *p; 116 | } 117 | 118 | operator bool() const { 119 | return p; 120 | } 121 | }; 122 | 123 | // String formatting (why oh why isn't this part of std??) 124 | std::string stringf(const char *fmt, ...) { 125 | std::vector buffer(256); 126 | va_list args, cp; 127 | va_start(args, fmt); 128 | va_copy(cp, args); 129 | int sz = vsnprintf(&buffer[0], buffer.size(), fmt, args); 130 | if (sz >= int(buffer.size())) { 131 | buffer.resize(sz + 1); 132 | vsnprintf(&buffer[0], buffer.size(), fmt, cp); 133 | } 134 | va_end(cp); 135 | va_end(args); 136 | return &buffer[0]; 137 | } 138 | 139 | inline std::map& severities(){ 140 | static std::map s{ 141 | {0, "info"}, 142 | {100, "warning"}, 143 | {200, "error"}, 144 | {1000, "fatal error"}}; 145 | return s; 146 | } 147 | 148 | inline std::string sevname(int severity) { 149 | auto& s = severities(); 150 | auto it = s.find(severity); 151 | if (it == s.end()) it = s.find(severity/100*100); 152 | if (it != s.end()) return it->second; 153 | return stringf("severity=%i", severity); 154 | } 155 | 156 | struct Entry { 157 | double time; 158 | int severity; 159 | std::string context; 160 | std::string message; 161 | }; 162 | 163 | typedef std::function Formatter; 164 | typedef std::function Filter; 165 | 166 | struct Logger : RefCounted { 167 | Logger() {} 168 | virtual ~Logger() {} 169 | virtual void log(const Entry& e) = 0; 170 | Logger(const Logger&) = delete; 171 | Logger& operator=(const Logger&) = delete; 172 | }; 173 | 174 | inline Formatter default_formatter() { 175 | return [](const Entry& e) -> std::string { 176 | char ctimebuf[30]; 177 | time_t t = e.time; 178 | ctime_r(&t, ctimebuf); ctimebuf[strlen(ctimebuf)-1] = '\0'; 179 | return stringf("%s - %s: (%s) -- %s", 180 | ctimebuf, 181 | sevname(e.severity).c_str(), 182 | e.context.c_str(), 183 | e.message.c_str()); 184 | }; 185 | } 186 | 187 | // Spread logging to multiple destinations 188 | struct MultiLogger : Logger { 189 | std::vector> Ls; 190 | void log(const Entry& e) { 191 | for (auto& x : Ls) x->log(e); 192 | } 193 | }; 194 | 195 | // Abstract filtering 196 | struct FilteringLogger : Logger { 197 | Ref L; 198 | Filter f; 199 | FilteringLogger(Ref L, Filter f) 200 | : L(L), f(f) 201 | {} 202 | void log(const Entry& e) { 203 | if (f(e)) L->log(e); 204 | } 205 | }; 206 | 207 | // Severity filtering 208 | inline Ref severityFilter(Ref L, int low, int high=-1) { 209 | return new FilteringLogger{L, [low, high](const Entry& e) { 210 | return e.severity >= low && (high == -1 || e.severity <= high); 211 | }}; 212 | } 213 | 214 | // Logging to memory (in Entry form) 215 | struct MemLogger : Logger { 216 | std::deque q; 217 | int max_size; 218 | std::mutex m; 219 | 220 | MemLogger(int max_size=-1) : max_size(max_size) {} 221 | void log(const Entry& e) { 222 | std::lock_guard lock(m); 223 | q.push_back(e); 224 | while (max_size != -1 && int(q.size()) > max_size) { 225 | q.pop_front(); 226 | } 227 | } 228 | }; 229 | 230 | // Log to stdio files 231 | struct FileLogger : Logger { 232 | FILE *file; 233 | Formatter formatter; 234 | bool autoclose; 235 | std::mutex m; 236 | 237 | FileLogger(FILE *file, const Formatter& formatter = default_formatter(), bool autoclose = false) 238 | : file(file), formatter(formatter), autoclose(autoclose) 239 | { } 240 | 241 | ~FileLogger() { 242 | if (autoclose) fclose(file); 243 | } 244 | 245 | void log(const Entry& e) { 246 | std::lock_guard lock(m); 247 | fprintf(file, "%s\n", formatter(e).c_str()); 248 | } 249 | }; 250 | 251 | // Log asynchronously 252 | struct AsyncLogger : Logger { 253 | std::deque q; 254 | Ref L; 255 | std::mutex m; 256 | std::thread worker; 257 | 258 | AsyncLogger(Ref L) : L(L) {} 259 | 260 | void log(const Entry& e) { 261 | std::lock_guard lock(m); 262 | q.push_back(e); 263 | if (q.size() == 1) { 264 | if (worker.joinable()) worker.join(); 265 | Ref me(this); 266 | worker = std::thread([me, this](){ 267 | m.lock(); 268 | while (q.size() > 0) { 269 | Entry& e = q.front(); 270 | m.unlock(); 271 | L->log(e); 272 | m.lock(); 273 | q.pop_front(); 274 | } 275 | worker.detach(); 276 | m.unlock(); 277 | }); 278 | } 279 | } 280 | }; 281 | 282 | // By default logging is synchronous and goes to stderr 283 | inline Ref& root() { 284 | static Ref L = new FileLogger(stderr); 285 | return L; 286 | } 287 | 288 | inline double now() { 289 | // This is how you get seconds since epoch (!) 290 | return std::chrono::duration_cast 291 | (std::chrono::system_clock::now().time_since_epoch()).count() / 1000.; 292 | // (my rationalization for this level of type obsession crap 293 | // is that the proposal was done on April 1st but the committee 294 | // didn't realize it was just a joke). 295 | } 296 | } 297 | 298 | #define LOGSTRINGIFY(x) #x 299 | #define LOGTOSTRING(x) LOGSTRINGIFY(x) 300 | #define LOG(severity, ...) \ 301 | logger::root()->log(logger::Entry{logger::now(), \ 302 | severity, \ 303 | __FILE__ ":" LOGTOSTRING(__LINE__), \ 304 | logger::stringf(__VA_ARGS__)}) 305 | 306 | #define INFO(...) LOG( 0, __VA_ARGS__) 307 | #define WARNING(...) LOG( 100, __VA_ARGS__) 308 | #define ERROR(...) LOG( 200, __VA_ARGS__) 309 | #define FATAL(...) LOG(1000, __VA_ARGS__) 310 | 311 | #endif 312 | --------------------------------------------------------------------------------