Goby v2
flex_ostreambuf.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include <iomanip>
24 #include <iostream>
25 #include <limits>
26 #include <sstream>
27 
28 #include <boost/assign.hpp>
29 #include <boost/bind.hpp>
30 #include <boost/date_time.hpp>
31 #include <boost/foreach.hpp>
32 
33 #include "goby/common/logger/flex_ostreambuf.h"
34 
35 #ifdef HAS_NCURSES
36 #include "flex_ncurses.h"
37 #endif
38 #include "flex_ostream.h"
39 
40 #include "goby/common/exception.h"
41 #include "goby/common/time.h"
42 #include "goby/util/sci.h"
43 #include "logger_manipulators.h"
44 
46 
47 #ifdef HAS_NCURSES
48 boost::mutex curses_mutex;
49 #endif
50 
51 boost::recursive_mutex goby::common::logger::mutex;
52 
53 goby::common::FlexOStreamBuf::FlexOStreamBuf(FlexOstream* parent)
54  : buffer_(1), name_("no name"), die_flag_(false), current_verbosity_(logger::UNKNOWN),
55 #ifdef HAS_NCURSES
56  curses_(0),
57 #endif
58  start_time_(goby_time()), is_gui_(false), highest_verbosity_(logger::QUIET), parent_(parent)
59 
60 {
61  Group no_group("", "Ungrouped messages");
62  groups_[""] = no_group;
63 }
64 
65 goby::common::FlexOStreamBuf::~FlexOStreamBuf()
66 {
67 #ifdef HAS_NCURSES
68  if (curses_)
69  delete curses_;
70 #endif
71 }
72 
73 void goby::common::FlexOStreamBuf::add_stream(logger::Verbosity verbosity, std::ostream* os)
74 {
75  //check that this stream doesn't exist
76  // if so, update its verbosity and return
77  bool stream_exists = false;
78  BOOST_FOREACH (StreamConfig& sc, streams_)
79  {
80  if (sc.os() == os)
81  {
82  sc.set_verbosity(verbosity);
83  stream_exists = true;
84  }
85  }
86 
87  if (!stream_exists)
88  streams_.push_back(StreamConfig(os, verbosity));
89 
90  highest_verbosity_ = logger::QUIET;
91  for (std::vector<StreamConfig>::const_iterator it = streams_.begin(), end = streams_.end();
92  it != end; ++it)
93  {
94  if (it->verbosity() > highest_verbosity_)
95  highest_verbosity_ = it->verbosity();
96  }
97 }
98 
99 void goby::common::FlexOStreamBuf::enable_gui()
100 {
101 #ifdef HAS_NCURSES
102 
103  is_gui_ = true;
104  curses_ = new FlexNCurses;
105 
106  boost::mutex::scoped_lock lock(curses_mutex);
107 
108  curses_->startup();
109 
110  // add any groups already on the screen as ncurses windows
111  typedef std::pair<std::string, Group> P;
112  BOOST_FOREACH (const P& p, groups_)
113  curses_->add_win(&groups_[p.first]);
114 
115  curses_->recalculate_win();
116 
117  input_thread_ = boost::shared_ptr<boost::thread>(
118  new boost::thread(boost::bind(&FlexNCurses::run_input, curses_)));
119 #else
120  throw(goby::Exception("Tried to enable NCurses GUI without compiling against NCurses. Install "
121  "NCurses and recompile goby or disable GUI functionality"));
122 #endif
123 }
124 
125 void goby::common::FlexOStreamBuf::add_group(const std::string& name, Group g)
126 {
127  // if(groups_.count(name)) return;
128 
129  groups_[name] = g;
130 
131 #ifdef HAS_NCURSES
132  if (is_gui_)
133  {
134  boost::mutex::scoped_lock lock(curses_mutex);
135  curses_->add_win(&groups_[name]);
136  }
137 #endif
138 }
139 
141 {
142  parent_->set_unset_verbosity();
143 
144  if (c == EOF)
145  return c;
146  else if (c == '\n')
147  buffer_.push_back(std::string());
148  else
149  buffer_.back().push_back(c);
150 
151  return c;
152 }
153 
154 // called when flush() or std::endl
156 {
157  if (current_verbosity_ == logger::UNKNOWN && lock_action_ == logger_lock::lock)
158  {
159  std::cerr
160  << "== Misuse of goby::glog in threaded mode: must use 'goby.is(...) && glog' syntax"
161  << std::endl;
162  std::cerr << "== Offending line: " << buffer_.front() << std::endl;
163  assert(!(lock_action_ == logger_lock::lock && current_verbosity_ == logger::UNKNOWN));
164  return 0;
165  }
166 
167  // all but last one
168  while (buffer_.size() > 1)
169  {
170  display(buffer_.front());
171  buffer_.pop_front();
172  }
173 
174  group_name_.erase();
175 
176  current_verbosity_ = logger::UNKNOWN;
177 
178  if (die_flag_)
179  exit(EXIT_FAILURE);
180 
181  if (lock_action_ == logger_lock::lock)
182  {
183  logger::mutex.unlock();
184  }
185 
186  return 0;
187 }
188 
189 void goby::common::FlexOStreamBuf::display(std::string& s)
190 {
191  bool gui_displayed = false;
192  BOOST_FOREACH (const StreamConfig& cfg, streams_)
193  {
194  if ((cfg.os() == &std::cout || cfg.os() == &std::cerr || cfg.os() == &std::clog) &&
195  current_verbosity_ <= cfg.verbosity())
196  {
197 #ifdef HAS_NCURSES
198  if (is_gui_ && current_verbosity_ <= cfg.verbosity() && !gui_displayed)
199  {
200  if (!die_flag_)
201  {
202  boost::mutex::scoped_lock lock(curses_mutex);
203  std::stringstream line;
204  boost::posix_time::time_duration time_of_day = goby_time().time_of_day();
205  line << "\n"
206  << std::setfill('0') << std::setw(2) << time_of_day.hours() << ":"
207  << std::setw(2) << time_of_day.minutes() << ":" << std::setw(2)
208  << time_of_day.seconds()
209  << TermColor::esc_code_from_col(groups_[group_name_].color()) << " | "
210  << esc_nocolor << s;
211 
212  curses_->insert(goby_time(), line.str(), &groups_[group_name_]);
213  }
214  else
215  {
216  curses_->alive(false);
217  input_thread_->join();
218  curses_->cleanup();
219  std::cerr << TermColor::esc_code_from_col(groups_[group_name_].color()) << name_
220  << esc_nocolor << ": " << s << esc_nocolor << std::endl;
221  }
222  gui_displayed = true;
223  continue;
224  }
225 #endif
226 
227  *cfg.os() << TermColor::esc_code_from_col(groups_[group_name_].color()) << name_
228  << esc_nocolor << " [" << goby::common::goby_time_as_string() << "]";
229  if (!group_name_.empty())
230  *cfg.os() << " "
231  << "{" << group_name_ << "}";
232  *cfg.os() << ": " << s << std::endl;
233  }
234  else if (cfg.os() && current_verbosity_ <= cfg.verbosity())
235  {
236  basic_log_header(*cfg.os(), group_name_);
237  strip_escapes(s);
238  *cfg.os() << s << std::endl;
239  }
240  }
241 }
242 
244 {
245 #ifdef HAS_NCURSES
246  if (is_gui_)
247  {
248  boost::mutex::scoped_lock lock(curses_mutex);
249  curses_->recalculate_win();
250  }
251 #endif
252 }
253 
254 // clean out any escape codes for non terminal streams
255 void goby::common::FlexOStreamBuf::strip_escapes(std::string& s)
256 {
257  static const std::string esc = "\33[";
258  static const std::string m = "m";
259 
260  size_t esc_pos, m_pos;
261  while ((esc_pos = s.find(esc)) != std::string::npos &&
262  (m_pos = s.find(m, esc_pos)) != std::string::npos)
263  s.erase(esc_pos, m_pos - esc_pos + 1);
264 }
void refresh()
refresh the display (does nothing if !is_gui())
void add_group(const std::string &name, Group g)
add a new group
int sync()
virtual inherited from std::streambuf. Called when std::endl or std::flush is inserted into the strea...
void startup()
start the display
Definition: chat_curses.cpp:30
std::string goby_time_as_string(const boost::posix_time::ptime &t=goby_time())
Simple string representation of goby_time()
Definition: time.h:150
void add_stream(logger::Verbosity verbosity, std::ostream *os)
add a stream to the logger
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
void run_input()
run in its own thread to take input from the user
int overflow(int c=EOF)
virtual inherited from std::streambuf. Called when something is inserted into the stream ...
Enables the Verbosity == gui mode of the Goby logger and displays an NCurses gui for the logger conte...
Definition: flex_ncurses.h:42
void cleanup()
end the display
Definition: chat_curses.cpp:96
simple exception class for goby applications
Definition: exception.h:32
static std::string esc_code_from_col(const Colors::Color &c)
Escape code from color enumeration (e.g. red -> "\33[31m")
Definition: term_color.h:151
Defines a group of messages to be sent to the Goby logger. For Verbosity == verbose streams...