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_;