DebStream.cc 24.5 KB
Newer Older
<
1
2
3
4
5
6
7
8
9
10
// (C) Copyright 2014 by Autodesk, Inc.
//
// The information contained herein is confidential, proprietary
// to Autodesk,  Inc.,  and considered a trade secret as defined
// in section 499C of the penal code of the State of California.
// Use of  this information  by  anyone  other  than  authorized
// employees of Autodesk, Inc.  is granted  only under a written
// non-disclosure agreement,  expressly  prescribing  the  scope
// and manner of such use.

11
#include "DebUtils.hh"
12
#include "DebDefault.hh"
13
#include "Base/Code/CodeLink.hh"
14
#include "Base/Utils/ThrowError.hh"
15
#include "Base/Utils/Environment.hh"
16
#include "Base/Test/IChecksum.hh"
17
#include "Base/Test/ChecksumDebugEvent.hh"
18
19
20

#ifdef DEB_ON

21
22
23
24
25
#include <string>
#include <fstream>
#include <time.h>
#include <vector>
#include <iostream>
26
#include <map>
27
28
29
30
#include <memory>
#include <list>
#include <map>
#include <sstream>
31
32
33
34
35
36
#include <cstring>
#include <time.h>

#ifndef WIN32
  #define sprintf_s snprintf
#endif
37

38
39
40
namespace {

// TODO: make this use std::string; check for html extension; case insensitive
41
bool is_html_filename(const char* const str)
42
{
43
  if (str == nullptr) return false;
44
  const char* dot = strrchr(str, '.');
45
46
  if (dot == nullptr) return false;
  ++dot;
47
  return (!strncmp(dot, "htm", 3)) || (!strncmp(dot, "HTM", 3)) ;
48
}
49

50
}
51

52
namespace Debug {
53
54

class FunctionCallSequence
55
{
56
  std::string func_name_;
57
  std::vector<Enter*> debs_;  // These may not have sequential counts when multithreaded.
58
59

public:
60
  FunctionCallSequence(const char* _func_name, Enter* _deb)
61
    : func_name_(_func_name)
62
  {
63
64
65
    debs_.push_back(_deb);
  }
  ~FunctionCallSequence() {}
66

67
  bool add(const char* _func_name, Enter* _deb)
68
69
  {
    if (func_name_ == _func_name)
70
    {
71
      debs_.push_back(_deb);
72
      return true;
73
    }
74
75
    return false;
  }
76

77
78
79
  bool pop()
  {
    if (debs_.size() > 1)
80
    {
81
82
      debs_.pop_back();
      return true;
83
    }
84
85
86
    debs_.clear();
    return false;
  }
87

88
89
90
91
92
  int number_calls() const
  {
    if (!this) return 0;
    return (int)debs_.size();
  }
93

94
95
96
  int count(int i = 0) const
  {
    int num = number_calls();
97
    if (i < num) return debs_[num - 1 - i]->nmbr_;
98
99
    return -1;
  }
100

101
102
103
104
  const char* name() const
  {
    return func_name_.c_str();
  }
105

106
107
108
109
110
111
  // Replace interior of < > in function name with . for brevity
  void compact_name(std::string& str) const
  {
    int cnt = 0;
    const char* ptr = func_name_.c_str();
    while (ptr && (*ptr != '\0'))
112
    {
113
114
115
116
      char c = *ptr;
      if (c == '>') --cnt;
      if (cnt == 0) str.append(1, c);
      if (c == '<')
117
      {
118
119
        if (cnt == 0) str.append(".");
        ++cnt;
120
      }
121
      ++ptr;
122
    }
123
  }
124

125
126
127
128
129
130
131
  // Get single call stack element string
  void get(std::string& _str, const bool _strip_angled, bool _with_counts) const
  {
    if (_strip_angled) compact_name(_str);
    else _str.append(name());
    _str.append("[");
    if (_with_counts)
132
    {
133
134
135
136
137
      int num = number_calls();
      int prev = -2;
      int seq_cnt = 0;
      for (int i = 0; i < num; ++i)
      {
138
        int cnt = debs_[i]->nmbr_;
139
        if (cnt != prev + 1)
140
        {
141
          char buffer[64];
142

143
144
145
146
          if (seq_cnt > 0)
          {
            _str.append("-");
            sprintf_s(buffer, sizeof(buffer), "%i", prev);
147
            _str.append(buffer);
148
          }
149
150
151
152
153
154
155
156
157
158
159
160
161
162
          if (i > 0) _str.append(",");
          sprintf_s(buffer, sizeof(buffer), "%i", cnt);
          _str.append(buffer);
          seq_cnt = 0;
        }
        else
          ++seq_cnt;
        prev = cnt;
      } // endfor i
    } // endif _with_counts
    else
      _str.append("*");
    _str.append("]");
  } // endfunc get
163

164
  void get_indent(std::string& _str, File* _dfile, bool _is_html);
165

166
}; // endclass FunctionCallSequence
167

168
//////////////////////////////////////////////////////////////////////////
169
170
171
172
173
174
175
class CallStack
{
  std::vector<FunctionCallSequence> calls_;
  int depth_;
public:
  CallStack() : depth_(0) {}
  ~CallStack() {}
176

177
  void add(const char* _func_name, Enter* _deb)
178
179
180
181
182
  {
    if (calls_.empty() || !calls_.back().add(_func_name, _deb))
      calls_.push_back(FunctionCallSequence(_func_name, _deb));
    ++depth_;
  }
183

184
185
186
187
188
189
  void pop()
  {
    if (!calls_.back().pop())
      calls_.pop_back();
    --depth_;
  }
190

191
  const FunctionCallSequence* call(int _up = 0) const
192
193
194
195
196
  {
    int num = (int)calls_.size();
    if (_up < num) return &calls_[num - 1 - _up];
    return nullptr;
  }
197

198
199
200
201
202
  // Read a particular call stack element
  bool read(int _up, const char*& _funcname, int& _count)
  {
    const FunctionCallSequence* fcs = call(_up);
    if (fcs != nullptr)
203
    {
204
205
206
      _funcname = fcs->name();
      _count = fcs->count(0); // Return most recent deb_enter_count
      return true;
207
    }
208
209
    return false;
  }
210

211
212
213
214
215
216
  // Get a full call stack sting.
  // returns number of call stack function elements
  int get(std::string& _str, bool _with_counts = true) const
  {
    int num = (int)calls_.size();
    for (int i = 0; i < num; ++i)
217
    {
218
219
      if (i > 0) _str.append("->");
      calls_[i].get(_str, true, _with_counts);
220
    }
221
222
    return num;
  }
223

224
225
226
227
  int depth() const
  {
    return depth_;
  }
228

229
  bool get_indent(std::string& _str, File* _dfile, const bool is_html);
230
}; // endclass CallStack
231

232
//////////////////////////////////////////////////////////////////////////
233
class File
234
235
{
public:
236
237
238
239
240
241
242
243
244
245
  File(const char* _flnm = nullptr, 
    const uint _flags = Stream::APPEND | Stream::RETAIN) 
    : flags_(_flags), lev_(5), num_flush_(0)
  {
    if (_flnm != nullptr) 
      read_debug_config();// TODO: not sure if this is the right location
    set_filename(_flnm);
    indent_size_ = 3;
    at_line_start_ = false; // Don't want to indent header
  }
246

247
248
249
250
  CallStack& call_stack()
  {
    return call_stack_;
  }
251

252
253
  bool is_kept_open() const
  {
254
    return 0 != (flags_ & Stream::KEEP_OPEN);
255
256
257
  }
  bool is_html() const
  {
258
    return 0 != (flags_ & Stream::HTML);
259
260
261
  }
  bool is_retained() const
  {
262
    return 0 != (flags_ & Stream::RETAIN);
263
264
265
  }
  bool is_appended() const
  {
266
    return 0 != (flags_ & Stream::APPEND);
267
  }
268
  // Only applies to HTML DEB_out
269
270
271
272
273
274
275
276
  bool is_white_on_black() const
  {
    return true;
  }
  int indent_size()
  {
    return indent_size_;
  }
277

278
279
280
281
  bool file_is_open() const
  {
    return file_stream_.is_open();
  }
282

283
284
285
286
  int priority() const
  {
    return priority_;
  }
287

288
289
290
291
  bool fork_to_cout()
  {
    return false;
  }
292

293
294
295
296
  bool fork_to_cerr()
  {
    return true;
  }
297

298
  const char* filename() const
299
  {
300
    if (this && (!flnm_.empty())) return flnm_.c_str();
301
302
    return nullptr;
  }
303

304
305
306
307
  void clear()
  {
    current_.clear();
    output_.clear();
308
    flnm_.clear();
309
  }
310

311
312
313
314
315
316
317
  char prev_char() const
  {
    if (!current_.empty()) return current_.back();
    if (!output_.empty()) return output_.back();
    return '\0';
  }

318
  void indent(bool _full_text)
319
  {
320
    std::string str;
321
    if (call_stack().get_indent(str, this,  _full_text && is_html()))
322
      current_.append(str);
323
324
  }

325
  void line_break(bool _with_indent = false)
326
  {
327
    if (is_html()) current_.append("<br>");   // Don't bother with matching </br>
328
329
    current_.append("\n", 1);

330
331
    if (_with_indent) indent(false);
    else at_line_start_ = true;
332
333
  }

334
335
336
337
338
339
  void set_level(int _lev)
  {
    lev_ = _lev;
  }

  void print_direct(const std::string& _s)
340
341
342
343
  {
    current_.append(_s);
  }

344
345
  void print(const char _c)
  {
346
    if (_c == '\n')
347
348
349
350
351
    {
      line_break();
      return;
    }

352
353
    if (at_line_start_)
    {
354
      indent(true);
355
356
357
358
      at_line_start_ = false;
    }


359
360
361
    if (is_html())
    {
      // translate the esoteric characters used in IGM DEB_out
362

363
      if (_c == -62 )  // -62
364
365
        return;

366
      if (_c == -89) // 167 = -89
367
368
369
370
371
      {
        current_.append("&sect;");
        return;
      }

372
      if (_c == -80) // -80
373
374
375
376
377
378
      {
        current_.append("&deg;");
        return;
      }
    }
    current_.append(&_c, 1);
379
380
  }

381
382
383
384
385
  void print_to_ostream(const char* const _s, std::ostream& os)
  {
    os << _s;
  }

386
  void print(const char* const _s, bool _fork = true)
387
388
389
  {
    if (_s != nullptr)
    {
390
      for (int i = 0; ; ++i)
391
392
      {
        const char c = _s[i];
393
        if (c == '\0') break;
394
395
396
397
        print(c);
      }
      if (_fork)
      {
398
        if (fork_to_cout())
399
          print_to_ostream(_s, std::cout);
400
        if (fork_to_cerr())
401
          print_to_ostream(_s, std::cerr);
402
      }
403
    }
404
405
406
407
408
409
410
411
  }

  void print(int _i)
  {
    char buffer[64];
    sprintf_s(buffer, sizeof(buffer), "%i", _i);
    print(buffer);
  }
412

413

414
  const char* double_format() const
415
416
417
418
419
420
  {
    if (double_format_.empty())
      return "%.17g";
    return double_format_.c_str();
  }

421
  void set_double_format(const char* const str)
422
  {
423
    if (str == nullptr)
424
425
426
427
428
429
      double_format_.clear();
    else
      double_format_ = str;
  }


430
431
432
  void print(double _d)
  {
    char buffer[64];
433
    sprintf_s(buffer, sizeof(buffer), double_format(), _d);
434
435
436
    print(buffer);
  }

437
  void print(const Command& _co)
438
  {
439
    switch (_co.cmd)
440
    {
441
    case Command::END :
442
      if (is_html()) print_direct("</FONT>");
443
      break;
444
    case Command::END_ERR :
445
446
447
    // Powerdown DEB_error font
    // if (is_html()) print_direct("</BLINK>");
    // fall through
448
    case Command::END_LF :
449
      if (is_html()) print_direct("</FONT>");
450
451
452
453
454
455
456
457
458
      line_break(); 
      
      // line_break() does not fork to cout or cerr
      // so do so explicitly.
      if (fork_to_cout())
         std::cout << "\n";
      if (fork_to_cerr())
         std::cerr << "\n";

459
460
461
462
      break;
    }
  }

463
464
  // Append current asctime to given string
  bool add_time(std::string& str)
465
466
  {
    time_t rawtime;
467
    time(&rawtime);
468
    struct tm timeinfo;
469
470
#ifdef WIN32
    int err = localtime_s(&timeinfo, &rawtime);
471
472
473
474
475
476
    if (err == 0)
    {
      char buffer[256];
      err = asctime_s(buffer, sizeof(buffer), &timeinfo);
      if (err == 0)
      {
477
        str.append(buffer);
478
479
480
        return true;
      }
    }
481
482
483
484
485
486
#else//WIN32
    //TODO: Implement a secure version of this code for Linux, OSX
    //timeinfo = *localtime(&rawtime);
    //char* buffer = asctime(&timeinfo);
    str.append("TODO: add_time()");
#endif//WIN32
487
488
489
    return false;
  }

490

491
#if 1
492
  bool hover(std::string& _str, const std::string& _hover, const bool _open)
493
494
495
496
  {
    if (is_html())
    {
      char buffer[1024];
497
498
      if (_open)  sprintf_s(buffer, sizeof(buffer),
                              "<span title=\"%s\">", _hover.c_str());
499
500
501
502
503
504
505
506
      else sprintf_s(buffer, sizeof(buffer), "</span>");
      _str.append(buffer);
      return true;
    }
    return false;
  }
#endif

507
  bool anchor(std::string& _str, const int _id, const char* _tag, const bool _open)
508
509
510
511
512
513
514
515
516
517
518
519
  {
    if (is_html())
    {
      char buffer[1024];
      if (_open)  sprintf_s(buffer, sizeof(buffer), "<A name=\"%08X_%s\">", _id, _tag);
      else sprintf_s(buffer, sizeof(buffer), "</A>");
      _str.append(buffer);
      return true;
    }
    return false;
  }

520
  bool link_to(std::string& _str, const int _id, const char* _tag, const std::string& _hover, const bool _open)
521
522
523
524
525
526
527
528
529
530
  {
    if (is_html())
    {
      char buffer[2048];
      if (_open)
      {
        // HTML title hover text is cropped to 64 char in Firefox but displays
        // OK in Chrome. We could use javascript to avoid this limit but HTML
        // is simpler.
        if (_hover.empty()) sprintf_s(buffer, sizeof(buffer),
531
532
533
                                        "<A href=\"#%08X_%s\">", _id, _tag);
        else sprintf_s(buffer, sizeof(buffer),
                         "<A href=\"#%08X_%s\" title=\"%s\">", _id, _tag, _hover.c_str());
534
535
536
537
538
539
540
541
542
      }
      else sprintf_s(buffer, sizeof(buffer), "</A>");
      _str.append(buffer);
      return true;
    }
    return false;
  }


543
544
545
  void header(std::string& str)
  {
    if (is_html())
546
547
548
549
550
551
552
553
554
    {
      str.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\">");
      str.append("\n<HTML><HEAD>");
      str.append("\n<TITLE>ReForm DEB_out");
      str.append("</TITLE>");
      // javascript lib loads go here
      // stylesheet loads go here
      // within HEAD javascript goes here
      str.append("\n</HEAD>");
555
      if (is_white_on_black())
556
      {
557
        str.append("\n<BODY BGCOLOR=\"#000000\" TEXT=\"#FFFFFF\" LINK=\"#%00FFFF\" VLINK=\"#FFFF00\" >");
558
        //str.append( "\n<BODY BGCOLOR=\"#000000\" TEXT=\"#FFFFFF\"  >");
559
560
561
562
      }
      else
      {
        str.append("\n<BODY BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\" LINK=\"#%FF0000\" VLINK=\"#0000FF\" >");
563
        //str.append( "\n<BODY BGCOLOR=\"#000000\" TEXT=\"#FFFFFF\" >");
564
565
566
567
568
569
      }
      str.append("\n");
    } // endif is_html
    bool date_header = true;
    if (date_header)
    {
570
      if (!flnm_.empty())
571
      {
572
        str.append(flnm_);
573
574
        str.append(" opened ");
      }
575
      add_time(str);
576
      str.append("[ Build: " __TIME__  " " __DATE__  "] ");
577
578
      if (is_html()) str.append("<BR>");
      str.append("\n");
579
    }
580
   }
581
582
583
584
585
586

  void footer()
  {
    bool date_footer = true;
    if (date_footer)
    {
587
      std::string str("\n");
588
      if (!flnm_.empty()) str.append(flnm_);
589
      str.append(" Closed: ");
590
      add_time(str);
591
592
      str.append("\n");
      print(str.c_str());
593
594
595
    }

    if (is_html())
596
      print("\n</BODY></HTML>", false);
597
  }
598

599
600
601
602
  bool is_first_flush()
  {
    return num_flush_ == 0 ;
  }
603

604
605
606
  int flush()
  {
    int res = 0;
607
    if (!current_.empty())
608
    {
609
      const char* fname = filename();
610
      if ((fname != nullptr) || file_is_open())
611
      {
612
        if (!file_is_open())
613
        {
614
615
616
          file_stream_.open(fname,
                            std::fstream::out | ((is_appended() && !is_first_flush()) ?
                                                 std::fstream::app  :  std::fstream::trunc));
617
618
619
620
621
        }

        if (file_stream_.is_open())
        {
          std::string hdr;
622
623
624
          if (!is_appended())
          {
            // Reoutput entire file
625
626
627
628
629
630
            header(hdr);
            output_.append(hdr);
            file_stream_ << output_;
          }
          else
          {
631
            if (is_first_flush())
632
633
            {
              header(hdr);
634
635
              if (is_retained())
                output_.append(hdr);
636
637
638
639
640
641
              file_stream_ << hdr;
            }
            ++num_flush_;
          }
          file_stream_ << current_;

642
          if (is_retained())
643
644
            output_.append(current_);

645
646
          current_.clear();
          if (!is_kept_open())
647
648
649
650
651
652
            file_stream_.close();
        } // endif fs.is_open
        else
          res = -1;
      }
    } // endif current empty
653
    return res;
654
655
  }  // endfunc flush

656
  // Use with extreme caution.
657
  const std::string& string() const { return current_; }
658

659
660
661
662
663
664
  void close()
  {
    footer();
    flush();
  }

665
  void set_filename(const char* _flnm)
666
  {
667
668
669
    flnm_ = _flnm != nullptr ? _flnm : "";
    if (is_html_filename(_flnm))
      flags_ = flags_ | Stream::HTML;
670
  }
671

672
  int permission(const char* const _flnm)
673
  {
674
675
    int lev = lev_;
    for (const auto& fltrs : level_selc_map_)
676
    {
677
678
      if (fltrs.second.select_file(_flnm) ||
          fltrs.second.select_function(call_stack_.call()->name()))
679
680
681
      {// continue this iteration until the maximum allowed level if found
        if (lev < fltrs.first) 
          lev = fltrs.first;
682
      }
683
    }
684
685
    return lev;
  }
686
687

  bool is_at_line_start()
688
  {
689
    return at_line_start_;
690
691
  }

692
693
  void read_debug_config()
  {
694
695
696
    const auto flnm = 
      System::Environment::variable("REFORM_DEB_CONFIG", "reform_deb.cfg");

697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
    std::ifstream deb_stream(flnm.c_str());
    std::string line;
    while(std::getline(deb_stream, line))
    {
      std::stringstream line_stream(line);
      std::string type;
      line_stream >> type;
      void (FilterLevelSelector::*add_string)(const std::string&) = nullptr;
      if (type == "all") {}
      else if (type == "file")
        add_string = &FilterLevelSelector::add_file_string;
      else if (type == "func")
        add_string = &FilterLevelSelector::add_func_string;
      else
        continue;
      int lev;
      line_stream >> lev;
714
715
      //if (lev < 0 || lev > 15)
      //  continue;
716
717
718
719
720
721
722
723
724
725
726
727
728
      if (add_string == nullptr)
      {
        lev_ = lev; // We have red the default level.
        continue;
      }
      char colon;
      line_stream >> colon;
      if (colon != ':')
        continue;
      std::string select_str;
      while(line_stream >> select_str)
        (level_selc_map_[lev].*add_string)(select_str);
    }
729
  }
730

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
private:
  // We use this data to decide the debug level of a function in a file.
  class FilterLevelSelector
  {
  public:
    void add_file_string(const std::string& _str) { file_selct_strngs_.push_back(_str); }
    void add_func_string(const std::string& _str) { func_selct_strngs_.push_back(_str); }

    bool select_file(const char* _flnm) const
    {
      std::string flnm(_flnm);
      const std::string root_dir("ReForm");
      auto pos = flnm.rfind(root_dir);
      if (pos != std::string::npos)
        flnm = flnm.substr(pos + root_dir.size());
      return search(flnm, file_selct_strngs_);
    }

    bool select_function(const char* _func) const
    {
      return search(_func, func_selct_strngs_);
    }

  private:
    static bool search(const std::string& _flnm,
      const std::list<std::string>& _sel_strings)
    {
      for (const auto& sel : _sel_strings)
      {
        if (_flnm.find(sel) != std::string::npos)
          return true;
      }
      return false;
    }

  private:
    std::list<std::string> file_selct_strngs_; // list of strings to be found inside the file name.
    std::list<std::string> func_selct_strngs_; // list of strings to be found inside the function name.
  };

  uint flags_;
  int lev_;
  int num_flush_;
  int priority_; // Last permission granted
  int indent_size_;

  // A map filter_level ==> filter_selector
  std::map<int, FilterLevelSelector> level_selc_map_; 

  bool at_line_start_;

  std::string current_;
  std::string output_;
  std::string flnm_;
  std::fstream file_stream_;

  std::string double_format_;
  //std::string indent_string_;
  CallStack call_stack_;
790
}; // endclass File
791

792
793
//////////////////////////////////////////////////////////////////////////

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
namespace {

Stream& global_stream()
{
  // TODO: Replace with a Singleton?? ThreadArray??
  static Stream glbl_strm(Debug::Default::LOG_FILENAME);
  return glbl_strm;
}

}//namespace


// put test checksum tag and separators in the required format

void warning(const std::string& _wrng, const char* const _fnct, 
  const char* const _file, const int _line)
{  
811
812
813
  Base::CodeLink code_link(_fnct, _file, _line);
  TEST_only(Test::Checksum::Debug::warning.record(_wrng, code_link));
  global_stream() << WARNING << ": " << _wrng << code_link << Command::END_LF;
814
815
816
817
818
}

void error(const std::string& _err, const char* const _fnct, 
  const char* const _file, const int _line)
{
819
820
821
  Base::CodeLink code_link(_fnct, _file, _line);
  TEST_only(Test::Checksum::Debug::error.record(_err, code_link));
  global_stream() << ERROR << ": " << _err << code_link << Command::END_ERR;
822
823
}

824
#undef TRIGGER_POINT
825
826
827

//////////////////////////////////////////////////////////////////////////

828
829
830
831
Enter::Enter(const char* const _flnm, const char* const _fnct, 
  int& _nmbr, int& _lvl)
  : flnm_(_flnm), outs_(0), lns_(0)
{// TODO: for thread-safety we will need to make the constructor body atomic!
832
  global_stream().dfile()->call_stack().add(_fnct, this);
833

834
835
836
  nmbr_ = _nmbr++; 

  if (_lvl == INVALID_LEVEL)
837
    _lvl = global_stream().dfile()->permission(flnm_);
838
  lvl_ = _lvl;
839

840
  static int id_cnt = 0;
841
  id_ = ++id_cnt;
842
843
}

844
Enter::~Enter()
845
{
846
  File* impl = global_stream().dfile();
847
  impl->call_stack().pop();
848

849
  std::string str;
850
  if (((outs_ > 0) || (lns_ > 0)) && impl->anchor(str, id_, "exit", true))
851
  {
852
    impl->anchor(str, id_, "exit", false);
853
854
    impl->print_direct(str);
  }
855
856
}

857
Stream& Enter::stream()
858
{
859
  Stream& ds = global_stream();
860
  File* impl = ds.dfile();
861

862
863
864
865
866
867
868
869
870
871
872
873
874
  if (impl->is_html())
  {
    // bool is_deb_error = (_warn == 2);
    // DEB_error font powerup goes here. BLINK is deprecated sadly.
    // if (is_deb_error)  impl->print_direct("<BLINK>");
    const int col = 0xFF0000; // RED
    char buffer[256];
    sprintf_s(buffer, sizeof(buffer), "<FONT COLOR=\"#%06X\" SIZE=%i>",
              col, impl->priority() + 1);
    impl->print_direct(buffer);
  }

  if (outs_ < 1)
875
  {
876
877
878
879
880
    // First DEB_out in this function so output callstack, and flush.
    impl->indent(true);
    std::string str;
    bool is_html = impl->is_html();
    if (is_html)
881
    {
882
883
      str.append("<FONT SIZE=2><u>");
      impl->anchor(str, id_, "enter", true);
884
    }
885
    else
886
    {
887
888
      // .txt call stack lead in
      str.append("****>");
889
    }
890
891
892
893
894
895

    impl->call_stack().get(str);
    if (is_html) str.append("</u></FONT>");
    impl->print_direct(str.c_str()); // Don't fork callstack to cerr etc.
    impl->line_break();
    ds.dfile()->flush();
896
  }
897
  ++outs_;
898
899
900
901

  return ds;
}

902
void FunctionCallSequence::get_indent(std::string& _str, File* _dfile, bool _is_html)
903
904
{
  int num = number_calls();
905
906
  for (int i = 0; i < num; ++i)
  {
907
    Enter* deb = debs_[i];
908
909
910
911
912
913
914
    if (_is_html)
    {
      /* HTML indent element is <L> with span title the name and count
      of the function and with < linking to the entry anchor (if present) and
      > linking to the exit anchor (will be present).
      L is the first letter of the module name */
      char hovert[1024];
915
      sprintf_s(hovert, sizeof(hovert), "%s[%i]", func_name_.c_str(), deb->nmbr_);
916
      int col = 0xFFFFFF;
917
918
919
920
921
922
923
924
      char buffer[1024];
      sprintf_s(buffer, sizeof(buffer), "<FONT COLOR=\"#%06X\">.", col);

      _dfile->hover(_str, std::string(hovert), true);

      _str.append(buffer);
      std::string cstk;
      //impl->call_stack().get(cstk);
925
      if ((deb->outs_ > 0) && _dfile->link_to(_str, deb->id_, "enter", cstk, true))
926
927
928
929
930
931
      {
        _str.append("&lt;");
        _dfile->link_to(_str, deb->id_, "enter", cstk,  false);
      }
      else _str.append("&lt;");

932
      _str.append(deb->flnm_, 1);
933
934
935
936
937

      if (_dfile->link_to(_str, deb->id_, "exit", cstk, true))
      {
        _str.append("&gt;");
        _dfile->link_to(_str, deb->id_, "exit", cstk,  false);
938
        ++deb->lns_;
939
940
941
942
943
944
945
946
947
948
      }

      _dfile->hover(_str, std::string(hovert), false);

      _str.append("</FONT>");
    } // endif html
    else _str.append("  ");
  }
}

949
bool CallStack::get_indent(std::string& _str, File* _dfile, const bool is_html)
950
951
952
953
954
955
956
957
958
959
{

  if (_dfile->indent_size() == 0) return false;
  if (is_html)
  {
    char buffer[64];
    sprintf_s(buffer, sizeof(buffer), "<FONT SIZE=%i>", _dfile->indent_size());
    _str.append(buffer);
  }
  int num = (int)calls_.size();
960
961
  int i0 = 0;
  if (!is_html) ++i0; // Don't waste whitespace on first level indent if .txt
962
  for (int i = i0; i < num; ++i)
963
    calls_[i].get_indent(_str, _dfile,  is_html);
964
965
966
967
968
  if (is_html) _str.append(":&nbsp;</FONT>\n");
  return true;
}


969
// =====================================
970
//        Stream member funcs
971
972
973
// =====================================


974
975
976
Stream::Stream(const char* _flnm, const uint _flgs)
: dfile_(new File(_flnm, _flgs))
{}
977

978
Stream::~Stream()
979
{
980
  if (dfile_ != nullptr)
981
  {
982
983
    dfile()->close();
    dfile()->clear();
984
    // NB. disable DEB_out over this delete if delete contains DEB_out
985
    delete dfile_;
986
987
988
  }
}

989
const std::string& Stream::string() const { return dfile()->string(); }
990

991
Stream& Stream::print(const int _i)
992
{
993
  dfile_->print(_i);
994
995
996
  return *this;
};

997
Stream& Stream::print(const double _d)
998
{
999
  dfile_->print(_d);
1000
1001
1002
  return *this;
};

1003
Stream& Stream::print(const char* _s, bool _fork)
1004
{
1005
  dfile_->print(_s, _fork);
1006
1007
1008
  return *this;
};

1009
Stream& Stream::print(const char _c)
1010
{
1011
1012
1013
1014
  dfile_->print(_c);
  return *this;
};

1015
Stream& Stream::print(const Command& _co)
1016
1017
{
  dfile_->print(_co);
1018
1019
1020
  return *this;
};

1021
Stream& operator<<(Stream& _ds, const int _i)
1022
1023
1024
{
  return _ds.print(_i);
}
1025
Stream& operator<<(Stream& _ds, const double _d)
1026
1027
1028
{
  return _ds.print(_d);
}
1029
Stream& operator<<(Stream& _ds, const char* const _s)
1030
1031
1032
{
  return _ds.print(_s);
}
1033
Stream& operator<<(Stream& _ds, const char _c)
1034
1035
1036
1037
{
  return _ds.print(_c);
}

1038
Stream& operator<<(Stream& _ds, const size_t _i)
1039
{
1040
  return _ds.print((int)_i);
1041
}
1042
Stream& operator<<(Stream& _ds, const unsigned int _i)
1043
{
1044
  return _ds.print((int)_i);