pattern_formatter_impl.h
Go to the documentation of this file.
1 //
2 // Copyright(c) 2015 Gabi Melman.
3 // Distributed under the MIT License (http://opensource.org/licenses/MIT)
4 //
5 
6 #pragma once
7 
8 #include "opc/spdlog/formatter.h"
10 #include "opc/spdlog/details/os.h"
11 #include "opc/spdlog/fmt/fmt.h"
12 
13 #include <chrono>
14 #include <ctime>
15 #include <memory>
16 #include <mutex>
17 #include <string>
18 #include <thread>
19 #include <utility>
20 #include <vector>
21 #include <array>
22 
23 namespace spdlog
24 {
25 namespace details
26 {
28 {
29 public:
30  virtual ~flag_formatter()
31  {}
32  virtual void format(details::log_msg& msg, const std::tm& tm_time) = 0;
33 };
34 
36 // name & level pattern appenders
38 namespace
39 {
40 class name_formatter:public flag_formatter
41 {
42  void format(details::log_msg& msg, const std::tm&) override
43  {
44  msg.formatted << *msg.logger_name;
45  }
46 };
47 }
48 
49 // log level appender
51 {
52  void format(details::log_msg& msg, const std::tm&) override
53  {
54  msg.formatted << level::to_str(msg.level);
55  }
56 };
57 
58 // short log level appender
60 {
61  void format(details::log_msg& msg, const std::tm&) override
62  {
64  }
65 };
66 
68 // Date time pattern appenders
70 
71 static const char* ampm(const tm& t)
72 {
73  return t.tm_hour >= 12 ? "PM" : "AM";
74 }
75 
76 static int to12h(const tm& t)
77 {
78  return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour;
79 }
80 
81 //Abbreviated weekday name
82 using days_array = std::array<std::string, 7>;
83 static const days_array& days()
84 {
85  static const days_array arr{ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } };
86  return arr;
87 }
89 {
90  void format(details::log_msg& msg, const std::tm& tm_time) override
91  {
92  msg.formatted << days()[tm_time.tm_wday];
93  }
94 };
95 // message counter formatter
96 class i_formatter SPDLOG_FINAL:public flag_formatter
97 {
98  void format(details::log_msg& msg, const std::tm&) override
99  {
100  msg.formatted << '#' << msg.msg_id;
101  }
102 };
103 //Full weekday name
104 static const days_array& full_days()
105 {
106  static const days_array arr{ { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } };
107  return arr;
108 }
109 class A_formatter SPDLOG_FINAL :public flag_formatter
110 {
111  void format(details::log_msg& msg, const std::tm& tm_time) override
112  {
113  msg.formatted << full_days()[tm_time.tm_wday];
114  }
115 };
116 
117 //Abbreviated month
118 using months_array = std::array<std::string, 12>;
119 static const months_array& months()
120 {
121  static const months_array arr{ { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" } };
122  return arr;
123 }
125 {
126  void format(details::log_msg& msg, const std::tm& tm_time) override
127  {
128  msg.formatted << months()[tm_time.tm_mon];
129  }
130 };
131 
132 //Full month name
133 static const months_array& full_months()
134 {
135  static const months_array arr{ { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } };
136  return arr;
137 }
138 class B_formatter SPDLOG_FINAL :public flag_formatter
139 {
140  void format(details::log_msg& msg, const std::tm& tm_time) override
141  {
142  msg.formatted << full_months()[tm_time.tm_mon];
143  }
144 };
145 
146 
147 //write 2 ints seperated by sep with padding of 2
148 static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, char sep)
149 {
150  w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0');
151  return w;
152 }
153 
154 //write 3 ints seperated by sep with padding of 2
155 static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, int v3, char sep)
156 {
157  w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0') << sep << fmt::pad(v3, 2, '0');
158  return w;
159 }
160 
161 
162 //Date and time representation (Thu Aug 23 15:35:46 2014)
163 class c_formatter SPDLOG_FINAL:public flag_formatter
164 {
165  void format(details::log_msg& msg, const std::tm& tm_time) override
166  {
167  msg.formatted << days()[tm_time.tm_wday] << ' ' << months()[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' ';
168  pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900;
169  }
170 };
171 
172 
173 // year - 2 digit
174 class C_formatter SPDLOG_FINAL:public flag_formatter
175 {
176  void format(details::log_msg& msg, const std::tm& tm_time) override
177  {
178  msg.formatted << fmt::pad(tm_time.tm_year % 100, 2, '0');
179  }
180 };
181 
182 
183 
184 // Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01
185 class D_formatter SPDLOG_FINAL:public flag_formatter
186 {
187  void format(details::log_msg& msg, const std::tm& tm_time) override
188  {
189  pad_n_join(msg.formatted, tm_time.tm_mon + 1, tm_time.tm_mday, tm_time.tm_year % 100, '/');
190  }
191 };
192 
193 
194 // year - 4 digit
195 class Y_formatter SPDLOG_FINAL:public flag_formatter
196 {
197  void format(details::log_msg& msg, const std::tm& tm_time) override
198  {
199  msg.formatted << tm_time.tm_year + 1900;
200  }
201 };
202 
203 // month 1-12
204 class m_formatter SPDLOG_FINAL:public flag_formatter
205 {
206  void format(details::log_msg& msg, const std::tm& tm_time) override
207  {
208  msg.formatted << fmt::pad(tm_time.tm_mon + 1, 2, '0');
209  }
210 };
211 
212 // day of month 1-31
213 class d_formatter SPDLOG_FINAL:public flag_formatter
214 {
215  void format(details::log_msg& msg, const std::tm& tm_time) override
216  {
217  msg.formatted << fmt::pad(tm_time.tm_mday, 2, '0');
218  }
219 };
220 
221 // hours in 24 format 0-23
222 class H_formatter SPDLOG_FINAL:public flag_formatter
223 {
224  void format(details::log_msg& msg, const std::tm& tm_time) override
225  {
226  msg.formatted << fmt::pad(tm_time.tm_hour, 2, '0');
227  }
228 };
229 
230 // hours in 12 format 1-12
231 class I_formatter SPDLOG_FINAL:public flag_formatter
232 {
233  void format(details::log_msg& msg, const std::tm& tm_time) override
234  {
235  msg.formatted << fmt::pad(to12h(tm_time), 2, '0');
236  }
237 };
238 
239 // minutes 0-59
240 class M_formatter SPDLOG_FINAL:public flag_formatter
241 {
242  void format(details::log_msg& msg, const std::tm& tm_time) override
243  {
244  msg.formatted << fmt::pad(tm_time.tm_min, 2, '0');
245  }
246 };
247 
248 // seconds 0-59
249 class S_formatter SPDLOG_FINAL:public flag_formatter
250 {
251  void format(details::log_msg& msg, const std::tm& tm_time) override
252  {
253  msg.formatted << fmt::pad(tm_time.tm_sec, 2, '0');
254  }
255 };
256 
257 // milliseconds
258 class e_formatter SPDLOG_FINAL:public flag_formatter
259 {
260  void format(details::log_msg& msg, const std::tm&) override
261  {
262  auto duration = msg.time.time_since_epoch();
263  auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000;
264  msg.formatted << fmt::pad(static_cast<int>(millis), 3, '0');
265  }
266 };
267 
268 // microseconds
269 class f_formatter SPDLOG_FINAL:public flag_formatter
270 {
271  void format(details::log_msg& msg, const std::tm&) override
272  {
273  auto duration = msg.time.time_since_epoch();
274  auto micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count() % 1000000;
275  msg.formatted << fmt::pad(static_cast<int>(micros), 6, '0');
276  }
277 };
278 
279 // nanoseconds
280 class F_formatter SPDLOG_FINAL:public flag_formatter
281 {
282  void format(details::log_msg& msg, const std::tm&) override
283  {
284  auto duration = msg.time.time_since_epoch();
285  auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000;
286  msg.formatted << fmt::pad(static_cast<int>(ns), 9, '0');
287  }
288 };
289 
290 // AM/PM
291 class p_formatter SPDLOG_FINAL:public flag_formatter
292 {
293  void format(details::log_msg& msg, const std::tm& tm_time) override
294  {
295  msg.formatted << ampm(tm_time);
296  }
297 };
298 
299 
300 // 12 hour clock 02:55:02 pm
301 class r_formatter SPDLOG_FINAL:public flag_formatter
302 {
303  void format(details::log_msg& msg, const std::tm& tm_time) override
304  {
305  pad_n_join(msg.formatted, to12h(tm_time), tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << ampm(tm_time);
306  }
307 };
308 
309 // 24-hour HH:MM time, equivalent to %H:%M
310 class R_formatter SPDLOG_FINAL:public flag_formatter
311 {
312  void format(details::log_msg& msg, const std::tm& tm_time) override
313  {
314  pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, ':');
315  }
316 };
317 
318 // ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S
319 class T_formatter SPDLOG_FINAL:public flag_formatter
320 {
321  void format(details::log_msg& msg, const std::tm& tm_time) override
322  {
323  pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':');
324  }
325 };
326 
327 // ISO 8601 offset from UTC in timezone (+-HH:MM)
328 class z_formatter SPDLOG_FINAL:public flag_formatter
329 {
330 public:
331  const std::chrono::seconds cache_refresh = std::chrono::seconds(5);
332 
333  z_formatter():_last_update(std::chrono::seconds(0)), _offset_minutes(0)
334  {}
335  z_formatter(const z_formatter&) = delete;
336  z_formatter& operator=(const z_formatter&) = delete;
337 
338  void format(details::log_msg& msg, const std::tm& tm_time) override
339  {
340 #ifdef _WIN32
341  int total_minutes = get_cached_offset(msg, tm_time);
342 #else
343  // No need to chache under gcc,
344  // it is very fast (already stored in tm.tm_gmtoff)
345  int total_minutes = os::utc_minutes_offset(tm_time);
346 #endif
347  bool is_negative = total_minutes < 0;
348  char sign;
349  if (is_negative)
350  {
351  total_minutes = -total_minutes;
352  sign = '-';
353  }
354  else
355  {
356  sign = '+';
357  }
358 
359  int h = total_minutes / 60;
360  int m = total_minutes % 60;
361  msg.formatted << sign;
362  pad_n_join(msg.formatted, h, m, ':');
363  }
364 private:
365  log_clock::time_point _last_update;
367  std::mutex _mutex;
368 
369  int get_cached_offset(const log_msg& msg, const std::tm& tm_time)
370  {
371  using namespace std::chrono;
372  std::lock_guard<std::mutex> l(_mutex);
373  if (msg.time - _last_update >= cache_refresh)
374  {
375  _offset_minutes = os::utc_minutes_offset(tm_time);
376  _last_update = msg.time;
377  }
378  return _offset_minutes;
379  }
380 };
381 
382 
383 
384 // Thread id
385 class t_formatter SPDLOG_FINAL:public flag_formatter
386 {
387  void format(details::log_msg& msg, const std::tm&) override
388  {
389  msg.formatted << msg.thread_id;
390  }
391 };
392 
393 // Current pid
394 class pid_formatter SPDLOG_FINAL:public flag_formatter
395 {
396  void format(details::log_msg& msg, const std::tm&) override
397  {
398  msg.formatted << details::os::pid();
399  }
400 };
401 
402 
403 class v_formatter SPDLOG_FINAL:public flag_formatter
404 {
405  void format(details::log_msg& msg, const std::tm&) override
406  {
407  msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size());
408  }
409 };
410 
411 class ch_formatter SPDLOG_FINAL:public flag_formatter
412 {
413 public:
414  explicit ch_formatter(char ch): _ch(ch)
415  {}
416  void format(details::log_msg& msg, const std::tm&) override
417  {
418  msg.formatted << _ch;
419  }
420 private:
421  char _ch;
422 };
423 
424 
425 //aggregate user chars to display as is
426 class aggregate_formatter SPDLOG_FINAL:public flag_formatter
427 {
428 public:
430  {}
431  void add_ch(char ch)
432  {
433  _str += ch;
434  }
435  void format(details::log_msg& msg, const std::tm&) override
436  {
437  msg.formatted << _str;
438  }
439 private:
441 };
442 
443 // Full info formatter
444 // pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v
445 class full_formatter SPDLOG_FINAL:public flag_formatter
446 {
447  void format(details::log_msg& msg, const std::tm& tm_time) override
448  {
449 #ifndef SPDLOG_NO_DATETIME
450  auto duration = msg.time.time_since_epoch();
451  auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000;
452 
453  /* Slower version(while still very fast - about 3.2 million lines/sec under 10 threads),
454  msg.formatted.write("[{:d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}] [{}] [{}] {} ",
455  tm_time.tm_year + 1900,
456  tm_time.tm_mon + 1,
457  tm_time.tm_mday,
458  tm_time.tm_hour,
459  tm_time.tm_min,
460  tm_time.tm_sec,
461  static_cast<int>(millis),
462  msg.logger_name,
463  level::to_str(msg.level),
464  msg.raw.str());*/
465 
466 
467  // Faster (albeit uglier) way to format the line (5.6 million lines/sec under 10 threads)
468  msg.formatted << '[' << static_cast<unsigned int>(tm_time.tm_year + 1900) << '-'
469  << fmt::pad(static_cast<unsigned int>(tm_time.tm_mon + 1), 2, '0') << '-'
470  << fmt::pad(static_cast<unsigned int>(tm_time.tm_mday), 2, '0') << ' '
471  << fmt::pad(static_cast<unsigned int>(tm_time.tm_hour), 2, '0') << ':'
472  << fmt::pad(static_cast<unsigned int>(tm_time.tm_min), 2, '0') << ':'
473  << fmt::pad(static_cast<unsigned int>(tm_time.tm_sec), 2, '0') << '.'
474  << fmt::pad(static_cast<unsigned int>(millis), 3, '0') << "] ";
475 
476  //no datetime needed
477 #else
478  (void)tm_time;
479 #endif
480 
481 #ifndef SPDLOG_NO_NAME
482  msg.formatted << '[' << *msg.logger_name << "] ";
483 #endif
484 
485  msg.formatted << '[' << level::to_str(msg.level) << "] ";
486  msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size());
487  }
488 };
489 
490 
491 
492 }
493 }
495 // pattern_formatter inline impl
497 inline spdlog::pattern_formatter::pattern_formatter(const std::string& pattern, pattern_time_type pattern_time)
498  : _pattern_time(pattern_time)
499 {
500  compile_pattern(pattern);
501 }
502 
503 inline void spdlog::pattern_formatter::compile_pattern(const std::string& pattern)
504 {
505  auto end = pattern.end();
506  std::unique_ptr<details::aggregate_formatter> user_chars;
507  for (auto it = pattern.begin(); it != end; ++it)
508  {
509  if (*it == '%')
510  {
511  if (user_chars) //append user chars found so far
512  _formatters.push_back(std::move(user_chars));
513 
514  if (++it != end)
515  handle_flag(*it);
516  else
517  break;
518  }
519  else // chars not following the % sign should be displayed as is
520  {
521  if (!user_chars)
522  user_chars = std::unique_ptr<details::aggregate_formatter>(new details::aggregate_formatter());
523  user_chars->add_ch(*it);
524  }
525  }
526  if (user_chars) //append raw chars found so far
527  {
528  _formatters.push_back(std::move(user_chars));
529  }
530 
531 }
532 inline void spdlog::pattern_formatter::handle_flag(char flag)
533 {
534  switch (flag)
535  {
536  // logger name
537  case 'n':
538  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::name_formatter()));
539  break;
540 
541  case 'l':
542  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::level_formatter()));
543  break;
544 
545  case 'L':
546  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::short_level_formatter()));
547  break;
548 
549  case('t'):
550  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::t_formatter()));
551  break;
552 
553  case('v'):
554  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::v_formatter()));
555  break;
556 
557  case('a'):
558  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::a_formatter()));
559  break;
560 
561  case('A'):
562  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::A_formatter()));
563  break;
564 
565  case('b'):
566  case('h'):
567  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::b_formatter()));
568  break;
569 
570  case('B'):
571  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::B_formatter()));
572  break;
573  case('c'):
574  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::c_formatter()));
575  break;
576 
577  case('C'):
578  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::C_formatter()));
579  break;
580 
581  case('Y'):
582  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::Y_formatter()));
583  break;
584 
585  case('D'):
586  case('x'):
587 
588  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::D_formatter()));
589  break;
590 
591  case('m'):
592  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::m_formatter()));
593  break;
594 
595  case('d'):
596  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::d_formatter()));
597  break;
598 
599  case('H'):
600  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::H_formatter()));
601  break;
602 
603  case('I'):
604  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::I_formatter()));
605  break;
606 
607  case('M'):
608  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::M_formatter()));
609  break;
610 
611  case('S'):
612  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::S_formatter()));
613  break;
614 
615  case('e'):
616  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::e_formatter()));
617  break;
618 
619  case('f'):
620  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::f_formatter()));
621  break;
622  case('F'):
623  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::F_formatter()));
624  break;
625 
626  case('p'):
627  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::p_formatter()));
628  break;
629 
630  case('r'):
631  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::r_formatter()));
632  break;
633 
634  case('R'):
635  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::R_formatter()));
636  break;
637 
638  case('T'):
639  case('X'):
640  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::T_formatter()));
641  break;
642 
643  case('z'):
644  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::z_formatter()));
645  break;
646 
647  case ('+'):
648  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::full_formatter()));
649  break;
650 
651  case ('P'):
652  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::pid_formatter()));
653  break;
654 
655 #if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)
656  case ('i'):
657  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::i_formatter()));
658  break;
659 #endif
660 
661  default: //Unknown flag appears as is
662  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::ch_formatter('%')));
663  _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::ch_formatter(flag)));
664  break;
665  }
666 }
667 
668 inline std::tm spdlog::pattern_formatter::get_time(details::log_msg& msg)
669 {
670  if (_pattern_time == pattern_time_type::local)
671  return details::os::localtime(log_clock::to_time_t(msg.time));
672  else
673  return details::os::gmtime(log_clock::to_time_t(msg.time));
674 }
675 
677 {
678 
679 #ifndef SPDLOG_NO_DATETIME
680  auto tm_time = get_time(msg);
681 #else
682  std::tm tm_time;
683 #endif
684  for (auto &f : _formatters)
685  {
686  f->format(msg, tm_time);
687  }
688  //write eol
690 }
static const months_array & full_months()
bool is_negative(T value)
Definition: format.h:944
static int to12h(const tm &t)
int * count
const std::string * logger_name
Definition: log_msg.h:41
IntFormatSpec< int, AlignTypeSpec< TYPE_CODE >, Char > pad(int value, unsigned width, Char fill= ' ')
f
static fmt::MemoryWriter & pad_n_join(fmt::MemoryWriter &w, int v1, int v2, char sep)
static SPDLOG_CONSTEXPR int eol_size
Definition: os.h:144
void write(BasicCStringRef< Char > format, ArgList args)
Definition: format.h:2918
std::string format(CStringRef format_str, ArgList args)
Definition: format.h:3686
static const char * ampm(const tm &t)
const Char * data() const FMT_NOEXCEPT
Definition: format.h:2866
int utc_minutes_offset(const std::tm &tm=details::os::localtime())
Definition: os.h:257
void format(details::log_msg &msg, const std::tm &tm_time) override
BasicStringRef< char > StringRef
Definition: format.h:548
fmt::BufferedFile & move(fmt::BufferedFile &f)
Definition: posix.h:432
level::level_enum level
Definition: log_msg.h:42
std::array< std::string, 12 > months_array
std::tm localtime(const std::time_t &time_tt)
Definition: os.h:80
const char * to_short_str(spdlog::level::level_enum l)
fmt::MemoryWriter formatted
Definition: log_msg.h:46
std::tm gmtime(const std::time_t &time_tt)
Definition: os.h:100
std::size_t size() const
Definition: format.h:2857
void format(details::log_msg &msg, const std::tm &tm_time) override
static const months_array & months()
static const days_array & full_days()
static SPDLOG_CONSTEXPR const char * eol
Definition: os.h:143
static const days_array & days()
void format(details::log_msg &msg, const std::tm &tm_time) override
fmt::MemoryWriter raw
Definition: log_msg.h:45
void format(details::log_msg &msg, const std::tm &) override
virtual void format(details::log_msg &msg, const std::tm &tm_time)=0
std::array< std::string, 7 > days_array
void format(details::log_msg &msg, const std::tm &) override
const char * to_str(spdlog::level::level_enum l)
void format(details::log_msg &msg, const std::tm &) override
int get_cached_offset(const log_msg &msg, const std::tm &tm_time)
log_clock::time_point time
Definition: log_msg.h:43


ros_opcua_impl_freeopcua
Author(s): Denis Štogl
autogenerated on Tue Jan 19 2021 03:12:07