error.h 18.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Spell-QTL  Software suite for the QTL analysis of modern datasets.
 * Copyright (C) 2016,2017  Damien Leroux <damien.leroux@inra.fr>, Sylvain Jasson <sylvain.jasson@inra.fr>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

18
19
#ifndef _SPEL_ERROR_H_
#define _SPEL_ERROR_H_
20
21
22

#include <string>
#include <iostream>
23
#include <fstream>
24
#include <exception>
25
26
27
#include <set>
#include <utility>
#include <sstream>
28
#include <thread>
Damien Leroux's avatar
Damien Leroux committed
29
#include <mutex>
30
#include <condition_variable>
31
#include <vector>
32
#include <deque>
33
34
extern "C" {
#include <unistd.h>
Damien Leroux's avatar
WIP.    
Damien Leroux committed
35
#include <sys/ioctl.h>
36
#include <signal.h>
37
}
38
39
40

#define _WHITE "\x1b[37;1m"
#define _RED "\x1b[31;1m"
Damien Leroux's avatar
WIP.    
Damien Leroux committed
41
#define _GREEN "\x1b[32;1m"
42
43
44
45
#define _YELLOW "\x1b[33;1m"
#define _CYAN "\x1b[36;1m"
#define _NORMAL "\x1b[0;m"

46
47
48
49
50
51
52
#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"

53
54
#define MSG_HANDLER_IS_SYNCED

55
56
57
58
59
enum msg_channel { Out, Err, Log };

struct message_struc {
    msg_channel channel;
    std::string message;
60
    message_struc(msg_channel c, std::string&& msg) : channel(c), message(std::move(msg)) {}
61
62
63
64
65
};

typedef std::shared_ptr<message_struc> message_handle;

/*#define MAKE_MESSAGE(_dest_, _expr_) do { std::stringstream __s; __s << _expr_; _dest_ = __s.str(); } while (0)*/
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

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_)
84
/*#define CREATE_MESSAGE(_var_, _channel_, _expr_) message_handle _var_(new message_struc {_channel_, MESSAGE(_expr_)});*/
Damien Leroux's avatar
Damien Leroux committed
85
86

#ifndef SPELL_UNSAFE_OUTPUT
87
#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
88
#else
89
#define CREATE_MESSAGE(_channel_, _what_) if (!msg_handler_t::quiet()) switch(_channel_) { \
Damien Leroux's avatar
Damien Leroux committed
90
91
92
93
94
95
96
97
98
99
100
        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
101
102


103
104
105
106
107
108
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.")
    {}
};

109
110

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

113
114
115
116
    /* 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;
117
118
119
120
        /*int indent;*/
        std::vector<std::string> prefix;
        bool prefix_insert_mode;
        std::stringstream prefix_ss;
121
122
123
124
125
126
127
    protected:
        int overflow(int ch) override;

    public:
        HeaderInserter(std::streambuf* dest)
            : dest(dest)
            , start_of_line(true)
128
129
130
131
            /*, indent(0)*/
            , prefix()
            , prefix_insert_mode(false)
            , prefix_ss()
132
        {}
133
134

        void rdbuf(std::streambuf* rb) { dest = rb; }
135
136
137
138
139
140
141
142
    };

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

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    /* from http://wordaligned.org/articles/cpp-streambufs */
    class teebuf: public std::streambuf
    {
    public:
        teebuf(std::streambuf * sb1, std::streambuf * sb2) : sb1(sb1), sb2(sb2) {}
    private:
        virtual int overflow(int c)
        {
            if (c == EOF) {
                return !EOF;
            } else {
                int const r1 = sb1->sputc(c);
                int const r2 = sb2->sputc(c);
                return r1 == EOF || r2 == EOF ? EOF : c;
            }
        }

        virtual int sync() {
            int const r1 = sb1->pubsync();
            int const r2 = sb2->pubsync();
            return r1 == 0 && r2 == 0 ? 0 : -1;
        }
    private:
        std::streambuf * sb1;
        std::streambuf * sb2;
    };

175
176
177
178
179
180
181
    std::streambuf* old_cout_rdbuf;
    std::streambuf* old_clog_rdbuf;
    std::streambuf* old_cerr_rdbuf;
    HeaderInserter hi;
    ForbidOutput forbid;
    std::ostream cerr, cout, clog;

182
183
    std::vector<teebuf> tees;

184
185
186
187
188
189
190
    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)
191
        , tees()
192
    {
193
        tees.reserve(3);
194
#ifndef SPELL_UNSAFE_OUTPUT
195
196
197
        std::clog.rdbuf(&forbid);
        std::cout.rdbuf(&forbid);
        std::cerr.rdbuf(&forbid);
198
199
#else
        std::clog.rdbuf(&hi);
200
#endif
201
202
203
204
    }

    ~ostream_manager()
    {
205
#ifndef SPELL_UNSAFE_OUTPUT
206
207
208
        std::clog.rdbuf(old_clog_rdbuf);
        std::cout.rdbuf(old_cout_rdbuf);
        std::cerr.rdbuf(old_cerr_rdbuf);
209
#endif
210
    }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226

    void
        redirect(std::ostream& os)
        {
            hi.rdbuf(os.rdbuf());
            cerr.rdbuf(os.rdbuf());
            cout.rdbuf(os.rdbuf());
        }

    void
        restore()
        {
            hi.rdbuf(old_clog_rdbuf);
            cerr.rdbuf(old_cerr_rdbuf);
            cout.rdbuf(old_cout_rdbuf);
        }
227
228
229
230
231
232
233
234
235
236
237
238
239

    void
    tee(std::ostream& os)
    {
        restore();
        tees.clear();
        tees.emplace_back(old_cout_rdbuf, os.rdbuf());
        cout.rdbuf(&tees.back());
        tees.emplace_back(old_cerr_rdbuf, os.rdbuf());
        cerr.rdbuf(&tees.back());
        tees.emplace_back(old_clog_rdbuf, os.rdbuf());
        hi.rdbuf(&tees.back());
    }
240
241
};

242
243


244
245
246
247
248
249
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;
250
    mutex_type m_stream_mutex;
251
    std::condition_variable m_condition;
Damien Leroux's avatar
Damien Leroux committed
252
    std::condition_variable m_flush_condition;
253

254
255
256
    static void catch_SIGSEG(int signo);
    static sighandler_t& old_sighandler() { static sighandler_t _; return _; }

257
258
259
260
261
262
263
264
265
266
267
268
    bool m_stop;

    std::thread m_thread;

    message_queue()
        : ostream_manager()
        , m_queue()
        , m_mutex()
        , m_condition()
        , m_stop(false)
        , m_thread([this] () { run(); })
    {
269
        /*old_sighandler() = signal(SIGSEGV, catch_SIGSEG);*/
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
    }

    ~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();
    }

289
290
    void lock_stream() { m_stream_mutex.lock(); }
    void unlock_stream() { m_stream_mutex.unlock(); }
291

292
    void run();
Damien Leroux's avatar
Damien Leroux committed
293
294
295
296
297
298
299
300

    void wait_for_flush()
    {
        scoped_lock_type lock(m_mutex);
        while (!m_queue.empty()) {
            m_flush_condition.wait(lock);
        }
    }
301
302
303
};


304
305


306
struct msg_handler_t {
307
#ifdef MSG_HANDLER_IS_SYNCED
308
309
    typedef std::recursive_mutex lock_type;
    typedef std::unique_lock<lock_type> scoped_lock_type;
310
311
312
313
314
315
316
317
318
#else
    typedef struct {
        void lock() {}
        void unlock() {}
    } lock_type;
    typedef struct _slt {
        _slt(lock_type&) {}
    } scoped_lock_type;
#endif
319

320
321
322
323
    struct state_t {
        bool color;
        std::set<std::string> workarounds;
        int count;
324
        int debug_indent;
325
        bool debug_enabled;
326
        bool quiet;
327
        std::vector<std::function<void()>> hooks;
328
        std::ostream raw_cout;
329
        message_queue queue;
330
        std::unique_ptr<std::ofstream> log_file;
331
332
333
334
335
336

        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 : ""; }

337
        state_t()
338
            : color(!!isatty(fileno(stdout))), workarounds(), count(0), debug_indent(0), debug_enabled(true), quiet(false), hooks()
339
            , raw_cout(std::cout.rdbuf()), queue(), log_file(nullptr)
340
        {
341
            /*std::cout << "Message handler instance created." << std::endl;*/
342
        }
343
		~state_t() { close_log_file(); }
344
345
        void check(bool fatal);
        void reset();
346
        void run_hooks() { for (auto& f: hooks) { f(); } }
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        void close_log_file()
        {
            if (log_file) {
                queue.restore();
                log_file->close();
                log_file.reset(nullptr);
            }

        }
        void set_log_file(const std::string& path)
        {
            close_log_file();
            log_file.reset(new std::ofstream(path, std::ios_base::out|std::ios_base::app));
            queue.tee(*log_file);
        }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
    };

    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(); }
376

377
378
379
    static void hook(std::function<void()>&& f) { instance().hooks.push_back(f); }
    static void run_hooks() { instance().run_hooks(); }

380
    static void indent() { instance().debug_indent += 3; }
381
382
//    static void dedent() { instance().debug_indent -= 3; }
//    static int get_indent() { return instance().debug_indent; }
383

384
    static void enqueue(const message_handle& mh) { instance().queue.enqueue(mh); }
Damien Leroux's avatar
Damien Leroux committed
385
    static void wait_for_flush() { instance().queue.wait_for_flush(); }
386

Damien Leroux's avatar
WIP.    
Damien Leroux committed
387
388
389
390
391
392
393
    static int termcols()
    {
        struct winsize w;
        ioctl(0, TIOCGWINSZ, &w);
        return w.ws_col;
    }

394
    static bool& debug_enabled() { return instance().debug_enabled; }
395
    static bool& quiet() { return instance().quiet; }
396

397
    static lock_type mutex;
398
399
400

    static void redirect(std::ostream& os) { instance().queue.redirect(os); }
    static void restore() { instance().queue.restore(); }
401
    static void set_log_file(const std::string& path) { instance().set_log_file(path); }
402
403
};

404
405


406
407
408
409
inline
int ostream_manager::HeaderInserter::overflow(int ch)
{
    int retval = 0;
410
    if (ch == _DBG_INDENT_MARK) {
411
412
413
414
415
416
417
418
        /*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());
        }
419
    } else if (ch == _DBG_DEDENT_MARK) {
420
        if (prefix_insert_mode) {
421
            overflow(_DBG_INDENT_MARK);
422
423
424
425
426
427
428
        }
        if (prefix.size()) {
            prefix.pop_back();
        }
        /*indent -= 3 * (indent > 0);*/
    } else if (prefix_insert_mode) {
        prefix_ss << (char) ch;
429
    } else if (ch != traits_type::eof()) {
430
        if (start_of_line) {
431
432
            for (const std::string& s: prefix) {
                for (const char c: s) {
433
434
                     dest->sputc(c);
//                    dest->overflow(c);
435
                }
436
            }
437
438
439
            /*for (int i = 0; i < indent; ++i) {*/
                /*dest->sputc(' ');*/
            /*}*/
440
        }
441
442
         retval = dest->sputc(ch);
//        retval = dest->overflow(ch);
443
444
445
446
447
        start_of_line = ch == '\n';
    }
    return retval;
}

448
449
450
inline
void message_queue::run()
{
Damien Leroux's avatar
Damien Leroux committed
451
    message_handle  next;
452
453
    while (true)
    {
Damien Leroux's avatar
Damien Leroux committed
454
455
        {
            scoped_lock_type lock(m_mutex);
456

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

Damien Leroux's avatar
Damien Leroux committed
459
460
461
            /*while (!m_stop && m_queue.empty()) {*/
                /*m_condition.wait(lock);*/
            /*}*/
462

Damien Leroux's avatar
Damien Leroux committed
463
464
465
            if (m_stop && m_queue.empty()) {
                return;
            }
466

Damien Leroux's avatar
Damien Leroux committed
467
468
            next = m_queue.front();
            m_queue.pop_front();
469

Damien Leroux's avatar
Damien Leroux committed
470
        }
471
        if (next->message == _DBG_SYNC_MARK_S) {
Damien Leroux's avatar
Damien Leroux committed
472
473
474
            m_flush_condition.notify_one();
            continue;
        } else if (next->message.size() == 0) {
475
476
            continue;
        }
Damien Leroux's avatar
Damien Leroux committed
477
478
479
        std::ostream& channel = next->channel == msg_channel::Out ? cout
                              : next->channel == msg_channel::Log ? clog
                              : cerr;
480
481
482
483
484
485
486
        lock_stream();
        channel << next->message << std::flush;
        unlock_stream();
        msg_handler_t::run_hooks();
    }
}

487
488
489
490
491
492
493
494
495
496
#ifdef SPELL_UNSAFE_OUTPUT

#define MSG_ERROR(_msg_expr_, _workaround_expr_) do { std::cerr << _msg_expr_ << std::endl; } while (0)
#define MSG_WARNING(_msg_expr_)  do { std::cout << _msg_expr_ << std::endl; } while (0)
#define MSG_INFO(_msg_expr_) do { std::cout << _msg_expr_ << std::endl; } while (0)
#define MSG_QUEUE_FLUSH()
#define MSG_DEBUG(_msg_expr_) do { std::clog << _msg_expr_ << std::endl; } while (0)

#else

497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
#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)

514
515
516
517
518
#define MSG_QUEUE_FLUSH() do { \
    CREATE_MESSAGE(msg_channel::Log, _DBG_SYNC_MARK_S); \
    msg_handler_t::wait_for_flush(); \
} while (0)

519
520
#define MSG_DEBUG(_msg_expr_) \
    do {\
521
522
523
524
        if (msg_handler_t::debug_enabled()) {\
            CREATE_MESSAGE(msg_channel::Log, MESSAGE(_msg_expr_ << std::endl));\
        }\
    } while (0);
525

526
#endif
Damien Leroux's avatar
Damien Leroux committed
527

528
#if 0
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
#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);\
555
        std::clog << _msg_expr_ << std::endl;\
556
557
        msg_handler_t::run_hooks();\
    } while(0)
558
#endif
559

560
561
562
#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)
563

564

565
566
567
/*#ifndef _SPELL_ERROR_H_MSGQ_CATCH_SIGSEG_*/
/*#define _SPELL_ERROR_H_MSGQ_CATCH_SIGSEG_*/
inline
568
569
570
571
572
573
574
void message_queue::catch_SIGSEG(int)
{
    MSG_DEBUG("**SEGFAULT DETECTED**");
    MSG_QUEUE_FLUSH();
    signal(SIGSEGV, old_sighandler());
    raise(SIGSEGV);
}
575

576
/*#endif*/
577

578
579
580
581
582
583
struct scoped_indent {
    scoped_indent() { MSG_DEBUG_INDENT; }
    scoped_indent(const std::string& str) { MSG_DEBUG_INDENT_EXPR(str); }
    ~scoped_indent() { MSG_DEBUG_DEDENT; }
};

584

585
586
inline void msg_handler_t::state_t::check(bool fatal)
{
587
    msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);
588
    if (count > 0) {
589
590
591
592
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(
                info() << "[MSG] " << count << " error"
                << (count > 1 ? "s were" : " was")
                << " reported. Suggestions to fix this:" << std::endl));
593
        for (auto& w: workarounds) {
594
            CREATE_MESSAGE(msg_channel::Err, MESSAGE(info() << "      - " << w << normal() << std::endl));
595
596
        }
        if (fatal) {
597
            CREATE_MESSAGE(msg_channel::Out, MESSAGE(normal() <<"At least one fatal error encountered. Aborting process." << std::endl));
598
599
600
601
602
603
604
605
606
607
            exit(-count);
        } else {
            reset();
        }
    }
}

inline void msg_handler_t::state_t::reset()
{
    if (workarounds.size()) {
608
        MSG_WARNING(workarounds.size() << " workarounds silently discarded");
609
610
611
612
613
614
615
    }
    count = 0;
    workarounds.clear();
}

#define WHITE (msg_handler_t::instance().color ? _WHITE : "")
#define YELLOW (msg_handler_t::instance().color ? _YELLOW : "")
616
#define GREEN (msg_handler_t::instance().color ? _GREEN : "")
617
618
619
620
#define RED (msg_handler_t::instance().color ? _RED : "")
#define CYAN (msg_handler_t::instance().color ? _CYAN : "")
#define NORMAL (msg_handler_t::instance().color ? _NORMAL : "")

621
622
623
624
625
626
#ifdef NDEBUG
#define DUMP_FILE_LINE()
#else
#define DUMP_FILE_LINE() MSG_DEBUG(__FILE__ << ':' << __LINE__)
#endif

627
628
#endif