error.h 14.4 KB
Newer Older
1
2
#ifndef _SPEL_ERROR_H_
#define _SPEL_ERROR_H_
3
4
5

#include <string>
#include <iostream>
6
#include <exception>
7
8
9
#include <set>
#include <utility>
#include <sstream>
10
#include <thread>
Damien Leroux's avatar
Damien Leroux committed
11
#include <mutex>
12
#include <condition_variable>
13
#include <vector>
14
#include <deque>
15
16
extern "C" {
#include <unistd.h>
Damien Leroux's avatar
WIP.    
Damien Leroux committed
17
#include <sys/ioctl.h>
18
#include <signal.h>
19
}
20
21
22

#define _WHITE "\x1b[37;1m"
#define _RED "\x1b[31;1m"
Damien Leroux's avatar
WIP.    
Damien Leroux committed
23
#define _GREEN "\x1b[32;1m"
24
25
26
27
#define _YELLOW "\x1b[33;1m"
#define _CYAN "\x1b[36;1m"
#define _NORMAL "\x1b[0;m"

28
29
30
31
32
33
34
#define _DBG_INDENT_MARK '\x11'
#define _DBG_DEDENT_MARK '\x12'
#define _DBG_INDENT_MARK_S "\x11"
#define _DBG_DEDENT_MARK_S "\x12"
#define _DBG_SYNC_MARK '\x13'
#define _DBG_SYNC_MARK_S "\x13"

35
36
#define MSG_HANDLER_IS_SYNCED

37
38
39
40
41
42
43
44
45
46
enum msg_channel { Out, Err, Log };

struct message_struc {
    msg_channel channel;
    std::string message;
};

typedef std::shared_ptr<message_struc> message_handle;

/*#define MAKE_MESSAGE(_dest_, _expr_) do { std::stringstream __s; __s << _expr_; _dest_ = __s.str(); } while (0)*/
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

static inline std::string __fetch_string(const std::ostream& os)
{
    return dynamic_cast<const std::stringstream*>(&os)->str();
}


static inline
std::ostream& operator << (std::ostream& os, const std::vector<double>& v)
{
    std::string sep = "";
    for (auto x: v) { os << sep << x; sep = " "; }
    return os;
}



#define MESSAGE(_expr_) __fetch_string(std::stringstream() << _expr_)
65
/*#define CREATE_MESSAGE(_var_, _channel_, _expr_) message_handle _var_(new message_struc {_channel_, MESSAGE(_expr_)});*/
Damien Leroux's avatar
Damien Leroux committed
66
67

#ifndef SPELL_UNSAFE_OUTPUT
68
#define CREATE_MESSAGE(_channel_, _what_) if (!msg_handler_t::quiet()) msg_handler_t::enqueue(message_handle{new message_struc {_channel_, _what_}});
Damien Leroux's avatar
Damien Leroux committed
69
#else
70
#define CREATE_MESSAGE(_channel_, _what_) if (!msg_handler_t::quiet()) switch(_channel_) { \
Damien Leroux's avatar
Damien Leroux committed
71
72
73
74
75
76
77
78
79
80
81
        case msg_channel::Out: \
            std::cout << _what_; \
            break; \
        case msg_channel::Err: \
            std::cerr << _what_; \
            break; \
        case msg_channel::Log: \
            std::clog << _what_; \
            break; \
    };
#endif
82
83


84
85
86
87
88
89
struct DirectOutputIsForbidden : public std::runtime_error {
    DirectOutputIsForbidden()
        : std::runtime_error("Direct output to std::cerr, cour, or clog is forbidden. Use CREATE_MESSAGE(channel, MESSAGE(expression)) instead.")
    {}
};

90
91

struct ostream_manager {
92
93
    /* FIXME indent/dedent must be managed by this very class... Maybe use \x1 for indent and \x2 for dedent... */

94
95
96
97
    /* inspired from http://stackoverflow.com/questions/22042414/c-stream-insert-string-after-newline */
    class HeaderInserter : public std::streambuf {
        std::streambuf* dest;
        bool start_of_line;
98
99
100
101
        /*int indent;*/
        std::vector<std::string> prefix;
        bool prefix_insert_mode;
        std::stringstream prefix_ss;
102
103
104
105
106
107
108
    protected:
        int overflow(int ch) override;

    public:
        HeaderInserter(std::streambuf* dest)
            : dest(dest)
            , start_of_line(true)
109
110
111
112
            /*, indent(0)*/
            , prefix()
            , prefix_insert_mode(false)
            , prefix_ss()
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        {}
    };

    class ForbidOutput : public std::streambuf {
    protected:
        int overflow(int) override
        {
            /* Direct output is forbidden */
            /*throw std::ios_base::failure("Direct output is forbidden");*/
            abort();
        }
    };

    std::streambuf* old_cout_rdbuf;
    std::streambuf* old_clog_rdbuf;
    std::streambuf* old_cerr_rdbuf;
    HeaderInserter hi;
    ForbidOutput forbid;
    std::ostream cerr, cout, clog;

    ostream_manager()
        : old_cout_rdbuf(std::cout.rdbuf())
        , old_clog_rdbuf(std::clog.rdbuf())
        , old_cerr_rdbuf(std::cerr.rdbuf())
        , hi(old_cout_rdbuf)
        , forbid()
        , cerr(old_cerr_rdbuf), cout(old_cout_rdbuf), clog(&hi)
    {
141
#ifndef SPELL_UNSAFE_OUTPUT
142
143
144
        std::clog.rdbuf(&forbid);
        std::cout.rdbuf(&forbid);
        std::cerr.rdbuf(&forbid);
145
#endif
146
147
148
149
    }

    ~ostream_manager()
    {
150
#ifndef SPELL_UNSAFE_OUTPUT
151
152
153
        std::clog.rdbuf(old_clog_rdbuf);
        std::cout.rdbuf(old_cout_rdbuf);
        std::cerr.rdbuf(old_cerr_rdbuf);
154
#endif
155
156
157
158
159
160
161
162
163
    }
};

struct message_queue : public ostream_manager {
    typedef std::mutex mutex_type;
    typedef std::unique_lock<mutex_type> scoped_lock_type;

    std::deque<message_handle> m_queue;
    mutex_type m_mutex;
164
    mutex_type m_stream_mutex;
165
    std::condition_variable m_condition;
Damien Leroux's avatar
Damien Leroux committed
166
    std::condition_variable m_flush_condition;
167

168
169
170
    static void catch_SIGSEG(int signo);
    static sighandler_t& old_sighandler() { static sighandler_t _; return _; }

171
172
173
174
175
176
177
178
179
180
181
182
    bool m_stop;

    std::thread m_thread;

    message_queue()
        : ostream_manager()
        , m_queue()
        , m_mutex()
        , m_condition()
        , m_stop(false)
        , m_thread([this] () { run(); })
    {
183
        /*old_sighandler() = signal(SIGSEGV, catch_SIGSEG);*/
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    }

    ~message_queue()
    {
        {
            scoped_lock_type lock(m_mutex);
            m_stop = true;
        }
        m_condition.notify_one();
        m_thread.join();
    }

    void enqueue(const message_handle& mh)
    {
        scoped_lock_type slt(m_mutex);
        m_queue.push_back(mh);
        m_condition.notify_one();
    }

203
204
    void lock_stream() { m_stream_mutex.lock(); }
    void unlock_stream() { m_stream_mutex.unlock(); }
205

206
    void run();
Damien Leroux's avatar
Damien Leroux committed
207
208
209
210
211
212
213
214

    void wait_for_flush()
    {
        scoped_lock_type lock(m_mutex);
        while (!m_queue.empty()) {
            m_flush_condition.wait(lock);
        }
    }
215
216
217
};


218
struct msg_handler_t {
219
#ifdef MSG_HANDLER_IS_SYNCED
220
221
    typedef std::recursive_mutex lock_type;
    typedef std::unique_lock<lock_type> scoped_lock_type;
222
223
224
225
226
227
228
229
230
#else
    typedef struct {
        void lock() {}
        void unlock() {}
    } lock_type;
    typedef struct _slt {
        _slt(lock_type&) {}
    } scoped_lock_type;
#endif
231

232
233
234
235
    struct state_t {
        bool color;
        std::set<std::string> workarounds;
        int count;
236
        int debug_indent;
237
        bool debug_enabled;
238
        bool quiet;
239
        std::vector<std::function<void()>> hooks;
240
        message_queue queue;
241
242
243
244
245
246

        const char* error() { ++count; return color ? _RED : ""; }
        const char* warning() { return color ? _YELLOW : ""; }
        const char* info() { return color ? _CYAN : ""; }
        const char* normal() { return color ? _NORMAL : ""; }

247
        state_t()
248
            : color(!!isatty(fileno(stdout))), workarounds(), count(0), debug_indent(0), debug_enabled(true), quiet(false), hooks()
249
            , queue()
250
        {
251
            /*std::cout << "Message handler instance created." << std::endl;*/
252
        }
253
254
		~state_t()
        {}
255
256
        void check(bool fatal);
        void reset();
257
        void run_hooks() { for (auto& f: hooks) { f(); } }
258
259
260
261
262
263
264
265
266
267
268
269
270
271
    };

    static state_t& instance() { static state_t _; return _; }

    static void set_color(bool _) { instance().color = _; }
    static bool color() { return instance().color; }

    static const char* e() { return instance().error(); }
    static const char* w() { return instance().warning(); }
    static const char* i() { return instance().info(); }
    static const char* n() { return instance().normal(); }

    static void check(bool fatal) { instance().check(fatal); }
    static void reset() { instance().reset(); }
272

273
274
275
    static void hook(std::function<void()>&& f) { instance().hooks.push_back(f); }
    static void run_hooks() { instance().run_hooks(); }

276
277
278
279
    static void indent() { instance().debug_indent += 3; }
    static void dedent() { instance().debug_indent -= 3; }
    static int get_indent() { return instance().debug_indent; }

280
    static void enqueue(const message_handle& mh) { instance().queue.enqueue(mh); }
Damien Leroux's avatar
Damien Leroux committed
281
    static void wait_for_flush() { instance().queue.wait_for_flush(); }
282

Damien Leroux's avatar
WIP.    
Damien Leroux committed
283
284
285
286
287
288
289
    static int termcols()
    {
        struct winsize w;
        ioctl(0, TIOCGWINSZ, &w);
        return w.ws_col;
    }

290
    static bool& debug_enabled() { return instance().debug_enabled; }
291
    static bool& quiet() { return instance().quiet; }
292

293
    static lock_type mutex;
294
295
};

296
297
298
299
inline
int ostream_manager::HeaderInserter::overflow(int ch)
{
    int retval = 0;
300
    if (ch == _DBG_INDENT_MARK) {
301
302
303
304
305
306
307
308
        /*indent += 3;*/
        prefix_insert_mode = !prefix_insert_mode;
        if (prefix_insert_mode) {
            prefix_ss.clear();
            prefix_ss.str(std::string());
        } else {
            prefix.push_back(prefix_ss.str());
        }
309
    } else if (ch == _DBG_DEDENT_MARK) {
310
        if (prefix_insert_mode) {
311
            overflow(_DBG_INDENT_MARK);
312
313
314
315
316
317
318
        }
        if (prefix.size()) {
            prefix.pop_back();
        }
        /*indent -= 3 * (indent > 0);*/
    } else if (prefix_insert_mode) {
        prefix_ss << (char) ch;
319
    } else if (ch != traits_type::eof()) {
320
        if (start_of_line) {
321
322
323
324
            for (const std::string& s: prefix) {
                for (const char c: s) {
                    dest->sputc(c);
                }
325
            }
326
327
328
            /*for (int i = 0; i < indent; ++i) {*/
                /*dest->sputc(' ');*/
            /*}*/
329
330
331
332
333
334
335
        }
        retval = dest->sputc( ch );
        start_of_line = ch == '\n';
    }
    return retval;
}

336
337
338
inline
void message_queue::run()
{
Damien Leroux's avatar
Damien Leroux committed
339
    message_handle  next;
340
341
    while (true)
    {
Damien Leroux's avatar
Damien Leroux committed
342
343
        {
            scoped_lock_type lock(m_mutex);
344

Damien Leroux's avatar
Damien Leroux committed
345
            m_condition.wait(lock, [this]() { return m_stop || !m_queue.empty(); });
346

Damien Leroux's avatar
Damien Leroux committed
347
348
349
            /*while (!m_stop && m_queue.empty()) {*/
                /*m_condition.wait(lock);*/
            /*}*/
350

Damien Leroux's avatar
Damien Leroux committed
351
352
353
            if (m_stop && m_queue.empty()) {
                return;
            }
354

Damien Leroux's avatar
Damien Leroux committed
355
356
            next = m_queue.front();
            m_queue.pop_front();
357

Damien Leroux's avatar
Damien Leroux committed
358
        }
359
        if (next->message == _DBG_SYNC_MARK_S) {
Damien Leroux's avatar
Damien Leroux committed
360
361
362
            m_flush_condition.notify_one();
            continue;
        } else if (next->message.size() == 0) {
363
364
            continue;
        }
Damien Leroux's avatar
Damien Leroux committed
365
366
367
        std::ostream& channel = next->channel == msg_channel::Out ? cout
                              : next->channel == msg_channel::Log ? clog
                              : cerr;
368
369
370
371
372
373
374
        lock_stream();
        channel << next->message << std::flush;
        unlock_stream();
        msg_handler_t::run_hooks();
    }
}

375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#define MSG_ERROR(_msg_expr_, _workaround_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(msg_handler_t::e() << "[ERR] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
        std::stringstream s; s << _workaround_expr_;\
        if (s.str().size()) { msg_handler_t::instance().workarounds.insert(s.str()); }\
} while (0)

#define MSG_WARNING(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Out, MESSAGE(msg_handler_t::w() << "[WRN] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
} while (0)

#define MSG_INFO(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Out, MESSAGE(msg_handler_t::i() << "[MSG] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
} while (0)

#define MSG_DEBUG(_msg_expr_) \
    do {\
394
395
396
397
        if (msg_handler_t::debug_enabled()) {\
            CREATE_MESSAGE(msg_channel::Log, MESSAGE(_msg_expr_ << std::endl));\
        }\
    } while (0);
398

Damien Leroux's avatar
Damien Leroux committed
399
#define MSG_QUEUE_FLUSH() do { \
400
    CREATE_MESSAGE(msg_channel::Log, _DBG_SYNC_MARK_S); \
Damien Leroux's avatar
Damien Leroux committed
401
402
403
    msg_handler_t::wait_for_flush(); \
} while (0)

404
#if 0
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
#define MSG_ERROR(_msg_expr_, _workaround_expr_) \
    do {\
        {msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cerr << msg_handler_t::e() << "[ERR] " << _msg_expr_ << msg_handler_t::n() << std::endl;}\
        std::stringstream s; s << _workaround_expr_;\
        if (s.str().size()) { msg_handler_t::instance().workarounds.insert(s.str()); }\
        msg_handler_t::run_hooks();\
} while (0)

#define MSG_WARNING(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cerr << msg_handler_t::w() << "[WRN] " << _msg_expr_ << msg_handler_t::n() << std::endl;\
        msg_handler_t::run_hooks();\
    } while(0)

#define MSG_INFO(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cout << msg_handler_t::i() << "[MSG] " << _msg_expr_ << msg_handler_t::n() << std::endl;\
        msg_handler_t::run_hooks();\
    } while(0)

#define MSG_DEBUG(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
431
        std::clog << _msg_expr_ << std::endl;\
432
433
        msg_handler_t::run_hooks();\
    } while(0)
434
#endif
435

436
437
438
#define MSG_DEBUG_INDENT CREATE_MESSAGE(msg_channel::Log, _DBG_INDENT_MARK_S "   " _DBG_INDENT_MARK_S)
#define MSG_DEBUG_INDENT_EXPR(_str_) CREATE_MESSAGE(msg_channel::Log, MESSAGE(_DBG_INDENT_MARK << _str_ << _DBG_INDENT_MARK))
#define MSG_DEBUG_DEDENT CREATE_MESSAGE(msg_channel::Log, _DBG_DEDENT_MARK_S)
439

440

441
442
443
/*#ifndef _SPELL_ERROR_H_MSGQ_CATCH_SIGSEG_*/
/*#define _SPELL_ERROR_H_MSGQ_CATCH_SIGSEG_*/
inline
444
445
446
447
448
449
450
void message_queue::catch_SIGSEG(int)
{
    MSG_DEBUG("**SEGFAULT DETECTED**");
    MSG_QUEUE_FLUSH();
    signal(SIGSEGV, old_sighandler());
    raise(SIGSEGV);
}
451
/*#endif*/
452

453
454
455
456
457
458
struct scoped_indent {
    scoped_indent() { MSG_DEBUG_INDENT; }
    scoped_indent(const std::string& str) { MSG_DEBUG_INDENT_EXPR(str); }
    ~scoped_indent() { MSG_DEBUG_DEDENT; }
};

459

460
461
inline void msg_handler_t::state_t::check(bool fatal)
{
462
    msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);
463
    if (count > 0) {
464
465
466
467
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(
                info() << "[MSG] " << count << " error"
                << (count > 1 ? "s were" : " was")
                << " reported. Suggestions to fix this:" << std::endl));
468
        for (auto& w: workarounds) {
469
            CREATE_MESSAGE(msg_channel::Err, MESSAGE(info() << "      - " << w << normal() << std::endl));
470
471
        }
        if (fatal) {
472
            CREATE_MESSAGE(msg_channel::Out, MESSAGE(normal() <<"At least one fatal error encountered. Aborting process." << std::endl));
473
474
475
476
477
478
479
480
481
482
            exit(-count);
        } else {
            reset();
        }
    }
}

inline void msg_handler_t::state_t::reset()
{
    if (workarounds.size()) {
483
        MSG_WARNING(workarounds.size() << " workarounds silently discarded");
484
485
486
487
488
489
490
    }
    count = 0;
    workarounds.clear();
}

#define WHITE (msg_handler_t::instance().color ? _WHITE : "")
#define YELLOW (msg_handler_t::instance().color ? _YELLOW : "")
491
#define GREEN (msg_handler_t::instance().color ? _GREEN : "")
492
493
494
495
#define RED (msg_handler_t::instance().color ? _RED : "")
#define CYAN (msg_handler_t::instance().color ? _CYAN : "")
#define NORMAL (msg_handler_t::instance().color ? _NORMAL : "")

496
497
498
499
500
501
#ifdef NDEBUG
#define DUMP_FILE_LINE()
#else
#define DUMP_FILE_LINE() MSG_DEBUG(__FILE__ << ':' << __LINE__)
#endif

502
503
#endif