scclib
Stable Cloud Computing C++ Library
logger.cc
Go to the documentation of this file.
1 /*
2 BSD 3-Clause License
3 
4 Copyright (c) 2022, Stable Cloud Computing, Inc.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8 
9 1. Redistributions of source code must retain the above copyright notice, this
10  list of conditions and the following disclaimer.
11 
12 2. Redistributions in binary form must reproduce the above copyright notice,
13  this list of conditions and the following disclaimer in the documentation
14  and/or other materials provided with the distribution.
15 
16 3. Neither the name of the copyright holder nor the names of its
17  contributors may be used to endorse or promote products derived from
18  this software without specific prior written permission.
19 
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31 #include <util/logger.h>
32 #include <string>
33 #include <functional>
34 #include <ctime>
35 #include <iomanip>
36 #include <iostream>
37 #include <map>
38 #include <memory>
39 #include <unordered_set>
40 #include <cassert>
41 #include <mutex>
42 #include <system_error>
43 
49 using namespace scc::util;
50 
51 std::mutex s_mx; // some stream objects (like fstream) are not thread safe
52 
53 class LogStreambuf : public std::streambuf
54 {
55  bool m_on;
56  std::string m_id;
57  std::string m_ts;
58  bool m_utc;
59  unsigned int m_multmax;
60  unsigned int m_multcur;
61  unsigned int m_maxline;
62 
63  std::string m_line;
64 
65  bool m_cout;
66  bool m_clog;
67  bool m_cerr;
68 
69  std::unordered_set<std::shared_ptr<std::ostream>> m_strms;
70 
71  LogStreambuf(const LogStreambuf&) = delete;
72  void operator=(const LogStreambuf&) = delete;
73 
74  // add a prefix and emit the line to all streams
75  void emit() noexcept
76  {
77  if (!m_on || m_line.empty()) return;
78 
79  // output: [ID] TIMESTAMP <line>
80 
81  std::stringstream out;
82 
83  bool emit_pre;
84 
85  if (m_id.empty() && m_ts.empty())
86  {
87  emit_pre = false;
88  }
89  else
90  {
91  emit_pre = true;
92 
93  if (m_multmax > 0 && (m_line[0] == '\t' || m_line[0] == ' '))
94  {
95  if (++m_multcur < m_multmax)
96  {
97  emit_pre = false; // inside a multiline log, don't emit prefix
98  }
99  else
100  {
101  m_multcur = 0;
102  }
103  }
104  }
105 
106  if (emit_pre)
107  {
108  if (m_id.size())
109  {
110  out << "[" << m_id << "] ";
111  }
112 
113  if (m_ts.size())
114  {
115  auto t = std::time(nullptr);
116  struct tm tbuf;
117  if (m_utc)
118  {
119  out << std::put_time(gmtime_r(&t, &tbuf), m_ts.c_str());
120  }
121  else
122  {
123  out << std::put_time(localtime_r(&t, &tbuf), m_ts.c_str());
124  }
125  out << " ";
126  }
127  }
128 
129  out << m_line << '\n';
130 
131  if (m_cout)
132  {
133  std::cout << out.str();
134  std::cout.flush();
135  }
136 
137  if (m_cerr)
138  {
139  std::cerr << out.str();
140  std::cerr.flush();
141  }
142 
143  if (m_clog)
144  {
145  std::clog << out.str();
146  std::clog.flush();
147  }
148 
149  std::lock_guard<std::mutex> lk(s_mx);
150  for (auto os : m_strms)
151  {
152  *os << out.str();
153  os->flush();
154  }
155  }
156 
157 protected:
158 
159  int_type overflow(int_type c)
160  {
161  if (traits_type::eq_int_type(traits_type::eof(), c))
162  {
163  if (m_line.size())
164  {
165  emit();
166  m_line.clear();
167  }
168 
169  return c;
170  }
171 
172  if (c == '\n')
173  {
174  emit();
175  m_line.clear();
176  return c;
177  }
178 
179  m_line.push_back(c);
180 
181  if (m_line.size() == m_maxline)
182  {
183  emit();
184  m_line.clear();
185  }
186  return c;
187  }
188 
189  int sync()
190  {
191  if (m_line.size())
192  {
193  emit();
194  m_line.clear();
195  }
196  return 0;
197  }
198 
199 public:
200 
201  LogStreambuf()
202  {
203  clear();
204  }
205 
206  LogStreambuf(unsigned int max)
207  {
208  clear(max);
209  }
210 
211  virtual ~LogStreambuf() {}
212 
213  void add(const std::shared_ptr<std::ostream>& os)
214  {
215  std::lock_guard<std::mutex> lk(s_mx);
216  if (m_strms.find(os) == m_strms.end())
217  {
218  m_strms.insert(os);
219  }
220  }
221 
222  void remove(const std::shared_ptr<std::ostream>& os)
223  {
224  std::lock_guard<std::mutex> lk(s_mx);
225  auto it = m_strms.find(os);
226  if (it != m_strms.end())
227  {
228  m_strms.erase(it);
229  }
230  }
231 
232  void add_cout()
233  {
234  m_cout = true;
235  }
236 
237  void dup(void* rdbufp)
238  {
239  LogStreambuf& b = *static_cast<LogStreambuf*>(rdbufp);
240 
241  m_on = b.m_on;
242  m_id = b.m_id;
243  m_ts = b.m_ts;
244  m_utc = b.m_utc;
245  m_multmax = b.m_multmax;
246  m_multcur = b.m_multcur;
247 
248  m_maxline = b.m_maxline;
249  m_line.reserve(m_maxline);
250  m_line = b.m_line;
251 
252  m_cout = b.m_cout;
253  m_cerr = b.m_cerr;
254  m_clog = b.m_clog;
255 
256  std::lock_guard<std::mutex> lk(s_mx);
257  m_strms = b.m_strms;
258  }
259 
260  void clear(int max = 256)
261  {
262 
263  m_on = true;
264  m_id = "";
265  m_ts = "";
266  m_utc = false;
267  m_multmax = 0;
268  m_multcur = 0;
269 
270  m_maxline = max;
271  m_line.reserve(m_maxline);
272  m_line.clear();
273 
274  m_cout = false;
275  m_cerr = false;
276  m_clog = false;
277 
278  std::lock_guard<std::mutex> lk(s_mx);
279  m_strms.clear();
280  }
281 
282  void id(const std::string& i)
283  {
284  m_id = i;
285  }
286  std::string id() const
287  {
288  return m_id;
289  }
290 
291  void timestamp(const std::string& ts)
292  {
293  m_ts = ts;
294  }
295  std::string timestamp() const
296  {
297  return m_ts;
298  }
299 
300  void utc(bool on)
301  {
302  m_utc = on;
303  }
304  bool utc() const
305  {
306  return m_utc;
307  }
308 
309  void enable(bool on)
310  {
311  m_on = on;
312  }
313  bool enable() const
314  {
315  return m_on;
316  }
317 
318  void multiline(unsigned int max)
319  {
320  m_multmax = max;
321  m_multcur = 0;
322  }
323  unsigned multiline() const
324  {
325  return m_multmax;
326  }
327 
328  void max_line(unsigned v)
329  {
330  m_maxline = v;
331  }
332  unsigned max_line() const
333  {
334  return m_maxline;
335  }
336 };
337 
338 Logger::Logger(unsigned int max)
339 {
340  if (!max)
341  {
342  throw std::runtime_error("logger max line length must be non-zero");
343  }
344 
345  rdbuf(new LogStreambuf(max)); // each logger contains a stream buffer (which must be thread safe)
346 }
347 
348 Logger::~Logger()
349 {
350  delete rdbuf();
351 }
352 
353 Logger::Logger(const Logger& b) // copy operations must create a streambuf and duplicate
354 {
355  rdbuf(new LogStreambuf());
356  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
357  rb->dup(b.rdbuf());
358 }
359 
361 {
362  rdbuf(new LogStreambuf());
363  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
364  rb->dup(b.rdbuf());
365  return *this;
366 }
367 
368 Logger::Logger(Logger&& b) // move operations must create a streambuf, duplicate, and clear the original logger
369 {
370  rdbuf(new LogStreambuf());
371  LogStreambuf* rba = static_cast<LogStreambuf*>(rdbuf());
372  LogStreambuf* rbb = static_cast<LogStreambuf*>(b.rdbuf());
373  assert(rba);
374  assert(rbb);
375  rba->dup(rbb);
376  rbb->clear();
377 }
378 
380 {
381  LogStreambuf* rba = static_cast<LogStreambuf*>(rdbuf());
382  LogStreambuf* rbb = static_cast<LogStreambuf*>(b.rdbuf());
383  assert(rba);
384  assert(rbb);
385  rba->dup(rbb);
386  rbb->clear();
387  return *this;
388 }
389 
390 void Logger::dup(const Logger& b)
391 {
392  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
393  assert(rb);
394  rb->dup(b.rdbuf());
395 }
396 
398 {
399  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
400  assert(rb);
401  rb->clear();
402 }
403 
404 void Logger::add(const std::shared_ptr<std::ostream>& os)
405 {
406  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
407  assert(rb);
408  rb->add(os);
409 }
410 
411 void Logger::remove(const std::shared_ptr<std::ostream>& os)
412 {
413  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
414  assert(rb);
415  rb->remove(os);
416 }
417 
419 {
420  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
421  assert(rb);
422  rb->add_cout();
423 }
424 
425 void Logger::id(const std::string& i)
426 {
427  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
428  assert(rb);
429  rb->id(i);
430 }
431 
432 std::string Logger::id() const
433 {
434  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
435  assert(rb);
436  return rb->id();
437 }
438 
439 void Logger::timestamp(const std::string& ts)
440 {
441  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
442  assert(rb);
443  rb->timestamp(ts);
444 }
445 
446 std::string Logger::timestamp() const
447 {
448  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
449  assert(rb);
450  return rb->timestamp();
451 }
452 
453 void Logger::utc(bool on)
454 {
455  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
456  assert(rb);
457  rb->utc(on);
458 }
459 
460 bool Logger::utc() const
461 {
462  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
463  assert(rb);
464  return rb->utc();
465 }
466 
467 void Logger::enable(bool on)
468 {
469  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
470  assert(rb);
471  rb->enable(on);
472 }
473 
474 bool Logger::enable() const
475 {
476  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
477  assert(rb);
478  return rb->enable();
479 }
480 
481 void Logger::multiline(unsigned v)
482 {
483  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
484  assert(rb);
485  rb->multiline(v);
486 }
487 
488 unsigned Logger::multiline() const
489 {
490  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
491  assert(rb);
492  return rb->multiline();
493 }
494 
495 void Logger::max_line(unsigned v)
496 {
497  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
498  assert(rb);
499  rb->max_line(v);
500 }
501 
502 unsigned Logger::max_line() const
503 {
504  LogStreambuf* rb = static_cast<LogStreambuf*>(rdbuf());
505  assert(rb);
506  return rb->max_line();
507 }
Thread-safe stream logger.
Definition: logger.h:86
Logger(unsigned int=256)
Create a logger with maximum line length (default 256).
Definition: logger.cc:338
void remove(const std::shared_ptr< std::ostream > &)
Remove shared stream.
Definition: logger.cc:411
Logger & operator=(const Logger &)
Copy assign logger, using original streams and settings.
Definition: logger.cc:360
void add(const std::shared_ptr< std::ostream > &)
Add a shared stream.
Definition: logger.cc:404
std::string id() const
Get the current id string.
Definition: logger.cc:432
unsigned int max_line() const
Get the maximum line length.
Definition: logger.cc:502
bool utc() const
Get utc mode.
Definition: logger.cc:460
unsigned int multiline() const
Get maximum number of multiline lines.
Definition: logger.cc:488
bool enable() const
Is the logger enabled?
Definition: logger.cc:474
void clear()
Clear the logger and reset to defaults.
Definition: logger.cc:397
std::string timestamp() const
Get the current timestamp format.
Definition: logger.cc:446
void add_cout()
Add std::cout console stream.
Definition: logger.cc:418
Thread safe logging.