SatCat5
log.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/ethernet.h>
7 #include <satcat5/io_core.h>
8 #include <satcat5/ip_core.h>
9 #include <satcat5/log.h>
10 #include <satcat5/utils.h>
11 
12 namespace io = satcat5::io;
13 namespace log = satcat5::log;
14 using log::Log;
15 using log::LogBuffer;
16 
17 // Enable emoji for log priority indicators?
18 #ifndef SATCAT5_LOG_EMOJI
19 #define SATCAT5_LOG_EMOJI 1
20 #endif
21 
22 // Global pointer to a linked list of active destination objects, if any.
23 log::EventHandler* g_log_dst = 0;
24 
25 // Forcibly unregister any EventHandler objects.
27  bool ok = true;
28  if (g_log_dst) {g_log_dst = 0; ok = false;}
29  return ok;
30 }
31 
32 // Helper function for looking up hex values.
33 inline char hex_lookup(unsigned val) {
34  switch (val & 0xF) {
35  case 0x0: return '0';
36  case 0x1: return '1';
37  case 0x2: return '2';
38  case 0x3: return '3';
39  case 0x4: return '4';
40  case 0x5: return '5';
41  case 0x6: return '6';
42  case 0x7: return '7';
43  case 0x8: return '8';
44  case 0x9: return '9';
45  case 0xA: return 'A';
46  case 0xB: return 'B';
47  case 0xC: return 'C';
48  case 0xD: return 'D';
49  case 0xE: return 'E';
50  default: return 'F';
51  }
52 }
53 
54 // Helper function for writing a single decimal digit.
55 template <typename T>
56 inline void next_digit(char* out, unsigned& wridx, T& val, T zpad, T place) {
57  // Find value of leading digit (i.e., '0' through '9').
58  // Use while loop in case CPU doesn't have a divide instruction.
59  char digit = '0';
60  while (val >= place) {++digit; val -= place;}
61 
62  // Write the next digit if it is nonzero or trails an earlier digit.
63  if ((digit > '0') || (wridx > 0) || (place <= zpad))
64  out[wridx++] = digit;
65 }
66 
67 // Helper function writes a decimal number to buffer, returns sting length.
68 // Working buffer MUST contain the designated minimum size:
69 // u32 max = ~4 billion = 10 digits + terminator = 11 bytes
70 // u64 max = ~18 pentillion = 20 digits + terminator = 21 bytes
71 static constexpr unsigned LOG_ITOA_BUFF32 = 11;
72 static unsigned log_itoa32(char* out, u32 val, u32 zpad) {
73  unsigned wridx = 0;
74  next_digit<u32>(out, wridx, val, zpad, 1000000000u);
75  next_digit<u32>(out, wridx, val, zpad, 100000000u);
76  next_digit<u32>(out, wridx, val, zpad, 10000000u);
77  next_digit<u32>(out, wridx, val, zpad, 1000000u);
78  next_digit<u32>(out, wridx, val, zpad, 100000u);
79  next_digit<u32>(out, wridx, val, zpad, 10000u);
80  next_digit<u32>(out, wridx, val, zpad, 1000u);
81  next_digit<u32>(out, wridx, val, zpad, 100u);
82  next_digit<u32>(out, wridx, val, zpad, 10u);
83  out[wridx++] = val + '0'; // Always write final digit
84  out[wridx] = 0; // Null termination
85  return wridx; // String length excludes terminator
86 }
87 
88 static constexpr unsigned LOG_ITOA_BUFF64 = 21;
89 static unsigned log_itoa64(char* out, u64 val, u64 zpad) {
90  unsigned wridx = 0;
91  next_digit<u64>(out, wridx, val, zpad, 10000000000000000000ull);
92  next_digit<u64>(out, wridx, val, zpad, 1000000000000000000ull);
93  next_digit<u64>(out, wridx, val, zpad, 100000000000000000ull);
94  next_digit<u64>(out, wridx, val, zpad, 10000000000000000ull);
95  next_digit<u64>(out, wridx, val, zpad, 1000000000000000ull);
96  next_digit<u64>(out, wridx, val, zpad, 100000000000000ull);
97  next_digit<u64>(out, wridx, val, zpad, 10000000000000ull);
98  next_digit<u64>(out, wridx, val, zpad, 1000000000000ull);
99  next_digit<u64>(out, wridx, val, zpad, 100000000000ull);
100  next_digit<u64>(out, wridx, val, zpad, 10000000000ull);
101  next_digit<u64>(out, wridx, val, zpad, 1000000000ull);
102  next_digit<u64>(out, wridx, val, zpad, 100000000ull);
103  next_digit<u64>(out, wridx, val, zpad, 10000000ull);
104  next_digit<u64>(out, wridx, val, zpad, 1000000ull);
105  next_digit<u64>(out, wridx, val, zpad, 100000ull);
106  next_digit<u64>(out, wridx, val, zpad, 10000ull);
107  next_digit<u64>(out, wridx, val, zpad, 1000ull);
108  next_digit<u64>(out, wridx, val, zpad, 100ull);
109  next_digit<u64>(out, wridx, val, zpad, 10ull);
110  out[wridx++] = val + '0'; // Always write final digit
111  out[wridx] = 0; // Null termination
112  return wridx; // String length excludes terminator
113 }
114 
115 // Translate priority code (+/-20) to a suitable UTF8 emoji.
116 const char* log::priority_label(s8 val) {
117  if (val >= log::CRITICAL) // Critical = Skull and crossbones
118  return SATCAT5_LOG_EMOJI ? "\xE2\x98\xA0\xEF\xB8\x8F" : "Crit";
119  else if (val >= log::ERROR) // Error = Red 'X'
120  return SATCAT5_LOG_EMOJI ? "\xE2\x9D\x8C" : "Error";
121  else if (val >= log::WARNING) // Warning = Caution sign
122  return SATCAT5_LOG_EMOJI ? "\xE2\x9A\xA0\xEF\xB8\x8F" : "Warn";
123  else if (val >= log::INFO) // Info = Speech bubble
124  return SATCAT5_LOG_EMOJI ? "\xF0\x9F\x92\xAC" : "Info";
125  else // Debug = Gear
126  return SATCAT5_LOG_EMOJI ? "\xE2\x9A\x99\xEF\xB8\x8F" : "Debug";
127 }
128 
130  : m_next(0)
131 {
132  satcat5::util::ListCore::add(g_log_dst, this);
133 }
134 
135 #if SATCAT5_ALLOW_DELETION
136 log::EventHandler::~EventHandler() {
137  satcat5::util::ListCore::remove(g_log_dst, this);
138 }
139 #endif
140 
142  : m_dst(dst)
143 {
144  // Write a few newlines to flush Tx buffer.
145  dst->write_str("\r\n\n");
146  dst->write_finalize();
147 }
148 
150  s8 priority, unsigned nbytes, const char* msg)
151 {
152  // Prefix message with a priority emoji.
153  m_dst->write_str(log::priority_label(priority));
154  m_dst->write_str("\t");
155  m_dst->write_bytes(nbytes, msg);
156  m_dst->write_str("\r\n");
157  m_dst->write_finalize();
158 }
159 
160 Log::Log(s8 priority)
161  : m_priority(priority)
162 {
163  // Nothing else to do at this time.
164 }
165 
166 Log::Log(s8 priority, const char* str)
167  : m_priority(priority)
168 {
169  m_buff.wr_str(str);
170 }
171 
172 Log::Log(s8 priority, const char* str1, const char* str2)
173  : m_priority(priority)
174 {
175  m_buff.wr_str(str1);
176  m_buff.wr_str(": ");
177  m_buff.wr_str(str2);
178 }
179 
180 Log::Log(s8 priority, const void* str, unsigned nbytes)
181  : m_priority(priority)
182 {
183  m_buff.wr_fix((const char*)str, nbytes);
184 }
185 
187  // Null-terminate the final message string.
188  m_buff.terminate();
189 
190  // Deliver it to each handler on the global list.
191  log::EventHandler* dst = g_log_dst;
192  while (dst) {
193  dst->log_event(m_priority, m_buff.len(), m_buff.m_buff);
195  }
196 }
197 
198 Log& Log::write(const char* str) {
199  m_buff.wr_str(str);
200  return *this;
201 }
202 
203 Log& Log::write(bool val) {
204  m_buff.wr_str(" = ");
205  m_buff.wr_h32(val ? 1:0, 1);
206  return *this;
207 }
208 
209 Log& Log::write(u8 val) {
210  m_buff.wr_str(" = 0x");
211  m_buff.wr_h32(val, 2);
212  return *this;
213 }
214 
215 Log& Log::write(u16 val) {
216  m_buff.wr_str(" = 0x");
217  m_buff.wr_h32(val, 4);
218  return *this;
219 }
220 
221 Log& Log::write(u32 val) {
222  m_buff.wr_str(" = 0x");
223  m_buff.wr_h32(val, 8);
224  return *this;
225 }
226 
227 Log& Log::write(u64 val) {
228  m_buff.wr_str(" = 0x");
229  m_buff.wr_h64(val, 16);
230  return *this;
231 }
232 
234  m_buff.wr_str(" = 0x");
235  while (rd->get_read_ready())
236  m_buff.wr_h32(rd->read_u8(), 2);
237  return *this;
238 }
239 
240 Log& Log::write(const u8* val, unsigned nbytes) {
241  m_buff.wr_str(" = 0x");
242  for (unsigned a = 0 ; a < nbytes ; ++a)
243  m_buff.wr_h32(val[a], 2);
244  return *this;
245 }
246 
248  // Convention is six hex bytes with ":" delimeter.
249  // e.g., "DE:AD:BE:EF:CA:FE"
250  m_buff.wr_str(" = ");
251  mac.log_to(m_buff);
252  return *this;
253 }
254 
256  m_buff.wr_str(" = ");
257  ip.log_to(m_buff);
258  return *this;
259 }
260 
261 Log& Log::write10(s32 val) {
262  m_buff.wr_str(" = ");
263  m_buff.wr_s32(val);
264  return *this;
265 }
266 
267 Log& Log::write10(s64 val) {
268  m_buff.wr_str(" = ");
269  m_buff.wr_s64(val);
270  return *this;
271 }
272 
273 Log& Log::write10(u32 val) {
274  m_buff.wr_str(" = ");
275  m_buff.wr_dec(val);
276  return *this;
277 }
278 
279 Log& Log::write10(u64 val) {
280  m_buff.wr_str(" = ");
281  m_buff.wr_d64(val);
282  return *this;
283 }
284 
285 const char* LogBuffer::c_str() {
286  terminate();
287  return m_buff;
288 }
289 
290 void LogBuffer::wr_fix(const char* str, unsigned len) {
291  if (!str) return; // Ignore null pointers
292  const char* end = str + len;
293  while (str != end && m_wridx < SATCAT5_LOG_MAXLEN)
294  m_buff[m_wridx++] = *(str++);
295 }
296 
297 void LogBuffer::wr_str(const char* str) {
298  if (!str) return; // Ignore null pointers
299  while (*str && m_wridx < SATCAT5_LOG_MAXLEN)
300  m_buff[m_wridx++] = *(str++);
301 }
302 
303 void LogBuffer::wr_h32(u32 val, unsigned nhex) {
304  for (unsigned a = 0 ; m_wridx < SATCAT5_LOG_MAXLEN && a < nhex ; ++a) {
305  unsigned shift = 4 * (nhex-a-1); // Most significant nybble first
306  m_buff[m_wridx++] = hex_lookup(val >> shift);
307  }
308 }
309 
310 void LogBuffer::wr_h64(u64 val, unsigned nhex) {
311  for (unsigned a = 0 ; m_wridx < SATCAT5_LOG_MAXLEN && a < nhex ; ++a) {
312  unsigned shift = 4 * (nhex-a-1); // Most significant nybble first
313  m_buff[m_wridx++] = hex_lookup(val >> shift);
314  }
315 }
316 
317 void LogBuffer::wr_d32(u32 val, unsigned zpad) {
318  char temp[LOG_ITOA_BUFF32];
319  log_itoa32(temp, val, zpad);
320  wr_str(temp);
321 }
322 
323 void LogBuffer::wr_d64(u64 val, unsigned zpad) {
324  char temp[LOG_ITOA_BUFF64];
325  log_itoa64(temp, val, zpad);
326  wr_str(temp);
327 }
328 
329 void LogBuffer::wr_s32(s32 val, unsigned zpad) {
330  wr_str(val < 0 ? "-" : "+");
331  wr_d32(satcat5::util::abs_s32(val), zpad);
332 }
333 
334 void LogBuffer::wr_s64(s64 val, unsigned zpad) {
335  wr_str(val < 0 ? "-" : "+");
336  wr_d64(satcat5::util::abs_s64(val), zpad);
337 }
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
virtual unsigned get_read_ready() const =0
How many bytes can be read without blocking?
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
void write_str(const char *str)
Write the contents of a null-terminated string.
virtual bool write_finalize()
Mark end of frame and release temporary working data.
Defines the interface for accepting Log messages.
Definition: log.h:72
virtual void log_event(s8 priority, unsigned nbytes, const char *msg)=0
Callback for each formatted Log message.
EventHandler()
Constructor automatically manages the list of active handler objects.
Definition: log.cc:129
Internal buffer used by the Log class.
Definition: log.h:134
unsigned len() const
Number of characters written to this buffer.
Definition: log.h:171
void wr_s32(s32 val, unsigned zpad=0)
Write a signed integer (s32) in decimal format.
Definition: log.cc:329
void wr_d64(u64 val, unsigned zpad=0)
Write an unsigned integer (u64) in decimal format.
Definition: log.cc:323
void wr_s64(s64 val, unsigned zpad=0)
Write a signed integer (s64) in decimal format.
Definition: log.cc:334
void wr_h32(u32 val, unsigned nhex=8)
Write an integer (u32) in hexadecimal format.
Definition: log.cc:303
void wr_fix(const char *str, unsigned len)
Write a fixed-length UTF-8 string.
Definition: log.cc:290
void wr_str(const char *str)
Write a null-terminated UTF-8 string.
Definition: log.cc:297
void wr_h64(u64 val, unsigned nhex=16)
Write an integer (u64) in hexadecimal format.
Definition: log.cc:310
const char * c_str()
Buffer contents, in the form of a null-terminated string.
Definition: log.cc:285
void wr_dec(u32 val)
Legacy alias for wr_d32.
Definition: log.h:166
void wr_d32(u32 val, unsigned zpad=0)
Write an unsigned integer (u32) in decimal format.
Definition: log.cc:317
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log(s8 priority)
Constructor sets priority and optionally the first string.
Definition: log.cc:160
~Log()
Destructor sends the message.
Definition: log.cc:186
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
ToWriteable(satcat5::io::Writeable *dst)
Bind this object to the designated output interface.
Definition: log.cc:141
void log_event(s8 priority, unsigned nbytes, const char *msg) override
Implement the EventHandler API.
Definition: log.cc:149
static void remove(T *&list, T *item)
Remove the designated item from the list.
Definition: list.h:182
static void add(T *&list, T *item)
Add new item to front or back, whichever is simpler.
Definition: list.h:56
static T * next(const T *item)
Fetch pointer to the next item.
Definition: list.h:151
I/O interface core definitions.
Diagnostic logging to UART and/or Ethernet ports.
constexpr s8 CRITICAL
Define basic priority codes for log messages.
Definition: log.h:113
const char * priority_label(s8 priority)
Convert priority code to a human-readable UTF-8 string.
Definition: log.cc:116
constexpr s8 WARNING
Define basic priority codes for log messages.
Definition: log.h:111
constexpr s8 ERROR
Define basic priority codes for log messages.
Definition: log.h:112
bool pre_test_reset()
Hard-reset of global variables at the start of each unit test.
Definition: log.cc:26
constexpr s8 INFO
Define basic priority codes for log messages.
Definition: log.h:110
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
void log_to(satcat5::log::LogBuffer &wr) const
Format this field as a human-readable string.
Definition: eth_header.cc:86
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
void log_to(satcat5::log::LogBuffer &wr) const
Format this IP address in standard form, e.g., "192.168.1.2".
Definition: ip_core.cc:12
Miscellaneous mathematical utility functions.