mirror of
https://github.com/livingcomputermuseum/UniBone.git
synced 2026-01-29 13:11:11 +00:00
490 lines
14 KiB
C++
490 lines
14 KiB
C++
/* logger.cpp: global error & info handling
|
|
|
|
Copyright (c) 2018, Joerg Hoppe
|
|
j_hoppe@t-online.de, www.retrocmp.com
|
|
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
- Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
- Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
- Neither the name of the copyright holder nor the names of its
|
|
contributors may be used to endorse or promote products derived from
|
|
this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
12-nov-2018 JH entered beta phase
|
|
09-Jul-2018 JH created
|
|
|
|
- Can route messages to console and into a file
|
|
- loglevels Error, Warning, Info and Debug
|
|
- user sees Info and Debug if "verbose"
|
|
"Debug" messages are marked with "log channel" bitmask, to
|
|
enable only selected channels
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
|
|
#include <unistd.h> // mark DEBUG with thread ID
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/syscall.h>
|
|
#include <pthread.h>
|
|
|
|
#include "logger.hpp" // own
|
|
|
|
using namespace std;
|
|
|
|
/**** singleton ***/
|
|
logger_c *logger;
|
|
|
|
#define RENDER_STYLE_NONE 0
|
|
#define RENDER_STYLE_CONSOLE 1
|
|
#define RENDER_CSV_TITLES 2
|
|
#define RENDER_CSV_DATA 3
|
|
|
|
// constructor
|
|
logger_c::logger_c() {
|
|
// mutex = PTHREAD_MUTEX_INITIALIZER ;
|
|
fifo = NULL;
|
|
fifo_init(LOG_FIFO_DEFAULT_SIZE);
|
|
messagecount = 0;
|
|
life_level = default_level;
|
|
logsources.clear();
|
|
// pthread_mutex_destroy(&mutex);
|
|
}
|
|
|
|
logger_c::~logger_c() {
|
|
fifo_init(0); // free buffer
|
|
}
|
|
|
|
// register a source, set its id
|
|
void logger_c::add_source(logsource_c *logsource) {
|
|
unsigned id;
|
|
// initialize each object log level with global default
|
|
*(logsource->log_level_ptr) = default_level;
|
|
|
|
for (id = 0; id < logsources.size(); id++)
|
|
if (logsources[id] == NULL) { // found unused place
|
|
logsources[id] = logsource;
|
|
logsource->log_id = id;
|
|
return;
|
|
}
|
|
|
|
// no free entry found:
|
|
logsources.push_back(logsource);
|
|
logsource->log_id = logsources.size() - 1;
|
|
}
|
|
|
|
// just set the pointer to NULL
|
|
void logger_c::remove_source(logsource_c *logsource) {
|
|
unsigned id = logsource->log_id;
|
|
logsources[id] = NULL;
|
|
}
|
|
|
|
// set log levels of all soruces back to "default"
|
|
void logger_c::reset_log_levels() {
|
|
unsigned id;
|
|
for (id = 0; id < logsources.size(); id++) {
|
|
logsource_c *logsource = logsources[id];
|
|
if (logsource != NULL) // found unused place
|
|
*(logsource->log_level_ptr) = default_level;
|
|
}
|
|
}
|
|
|
|
// set new size
|
|
// 0 <=> fifo NULL
|
|
void logger_c::fifo_init(unsigned size) {
|
|
unsigned idx;
|
|
if (fifo) {
|
|
free(fifo);
|
|
fifo = NULL;
|
|
fifo_capacity = 0;
|
|
}
|
|
fifo_capacity = size;
|
|
if (fifo_capacity > 0) {
|
|
fifo = (logmessage_t *) malloc(fifo_capacity * sizeof(logmessage_t));
|
|
assert(fifo);
|
|
}
|
|
for (idx = 0; idx < fifo_capacity; idx++)
|
|
fifo[idx].valid = false;
|
|
|
|
fifo_clear();
|
|
}
|
|
|
|
void logger_c::fifo_clear(void) {
|
|
messagecount = 0;
|
|
fifo_fill = fifo_readidx = fifo_writeidx = 0;
|
|
}
|
|
|
|
// get an entry. 0 = oldest. NULL if idx >= fill
|
|
logmessage_t *logger_c::fifo_get(unsigned idx_rel) {
|
|
logmessage_t *result;
|
|
unsigned idx_abs;
|
|
|
|
if (idx_rel >= fifo_fill)
|
|
return NULL;
|
|
// calc absolute idx in ring buffer
|
|
idx_abs = (fifo_readidx + idx_rel) % fifo_capacity;
|
|
result = &(fifo[idx_abs]);
|
|
// printf("get(): msg %u = %p\n", idx_abs, result) ;
|
|
return result;
|
|
}
|
|
|
|
// new msg into fifo. oldest may be deleted
|
|
void logger_c::fifo_push(logmessage_t * msg) {
|
|
if (fifo_fill >= (fifo_capacity - 1))
|
|
// fifo full: delete oldest
|
|
fifo_pop();
|
|
fifo[fifo_writeidx] = *msg; // copy into
|
|
//printf("psh(): msg %u = %p", fifo_writeidx, &(fifo[fifo_writeidx])) ;
|
|
// inc write pointer, roll around
|
|
fifo_writeidx = (fifo_writeidx + 1) % fifo_capacity;
|
|
fifo_fill++;
|
|
//printf(" => idx=%u, fill=%u\n", fifo_writeidx, fifo_fill) ;
|
|
}
|
|
|
|
// erase oldest message
|
|
void logger_c::fifo_pop(void) {
|
|
if (fifo_fill == 0)
|
|
return;
|
|
// inc read pointer, roll around
|
|
fifo_readidx = (fifo_readidx + 1) % fifo_capacity;
|
|
fifo_fill--;
|
|
// emtpy: readptr == writeptr
|
|
assert(fifo_fill != 0 || fifo_readidx == fifo_writeidx);
|
|
}
|
|
|
|
const char * logger_c::level_text(unsigned level) {
|
|
switch (level) {
|
|
case LL_FATAL:
|
|
return "FATAL";
|
|
case LL_ERROR:
|
|
return "ERR";
|
|
case LL_WARNING:
|
|
return "WRN";
|
|
case LL_INFO:
|
|
return "Inf";
|
|
case LL_DEBUG:
|
|
return "Dbg";
|
|
default:
|
|
return "ILLEGAL_LEVEL";
|
|
}
|
|
}
|
|
/*
|
|
string logger_c::channelmask_text(unsigned logchannel) {
|
|
static string buffer;
|
|
buffer = "";
|
|
if (logchannel == LC_ANY)
|
|
buffer = "ANY";
|
|
else {
|
|
if (logchannel & LC_UIO)
|
|
buffer = "UIO ";
|
|
if (logchannel & LC_UNIBUS)
|
|
buffer = "UNIBUS ";
|
|
if (logchannel & LC_RL)
|
|
buffer = "RL ";
|
|
}
|
|
return buffer;
|
|
}
|
|
*/
|
|
|
|
// is output with verbosity "level" active for logsource?
|
|
bool logger_c::ignored(logsource_c *logsource, unsigned msglevel) {
|
|
if (msglevel == LL_FATAL)
|
|
return false; // never ignored
|
|
if (msglevel > *(logsource->log_level_ptr))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
const char *logger_c::timestamp_text(timeval *tv) {
|
|
static char result[80], millibuff[10];
|
|
// int millis = tv->tv_usec / 1000;
|
|
strftime(result, 26, "%H:%M:%S", localtime(&tv->tv_sec));
|
|
sprintf(millibuff, ".%06ld", tv->tv_usec);
|
|
// sprintf(millibuff, ".%03d", millis);
|
|
strcat(result, millibuff);
|
|
return result;
|
|
}
|
|
|
|
/* convert to text
|
|
possible enhancements:
|
|
- select which fields to show
|
|
-select between "nice" format and CSV
|
|
Format is:
|
|
CONSOLE: " [timestamp level logsource thread@srcfilename:line] <printf()>"
|
|
CSV_TITLE
|
|
CSV_LINE
|
|
*/
|
|
|
|
void logger_c::message_render(char *buffer, unsigned buffer_size, logmessage_t *msg,
|
|
unsigned style) {
|
|
|
|
char fmtbuffer[LOGMESSAGE_TEXT_SIZE];
|
|
|
|
assert(buffer_size >= LOGMESSAGE_TEXT_SIZE);
|
|
|
|
if (style == RENDER_CSV_TITLES) {
|
|
strcpy(buffer, "id;timestamp;level;source;thread;file;line;message");
|
|
} else {
|
|
// data!
|
|
char *wp = buffer; // write pointer
|
|
int chars_written = 0;
|
|
|
|
// very long text? 10000 = reserve for % place holder expansion
|
|
assert(buffer_size > (strlen(msg->printf_format) + 1000));
|
|
switch (style) {
|
|
case RENDER_STYLE_CONSOLE:
|
|
|
|
if (msg->level >= LL_DEBUG && msg->source_filename != NULL) {
|
|
// full format with source file: assemble format
|
|
strcpy(fmtbuffer, "[%s %s %6s %05u@%s:%04u] ");
|
|
chars_written = sprintf(wp, fmtbuffer, timestamp_text(&msg->timestamp),
|
|
level_text(msg->level), msg->logsource->log_label.c_str(),
|
|
(unsigned) msg->thread_id, msg->source_filename, msg->source_line);
|
|
} else {
|
|
// without source_file and line
|
|
strcpy(fmtbuffer, "[%s %s %6s] ");
|
|
chars_written = sprintf(wp, fmtbuffer, timestamp_text(&msg->timestamp),
|
|
level_text(msg->level), msg->logsource->log_label.c_str());
|
|
}
|
|
break;
|
|
case RENDER_CSV_DATA:
|
|
// full format with source file: assemble format
|
|
strcpy(fmtbuffer, "%u;%s;%s;%s;%u;%s;%u;");
|
|
chars_written = sprintf(wp, fmtbuffer, msg->id, timestamp_text(&msg->timestamp),
|
|
level_text(msg->level), msg->logsource->log_label.c_str(),
|
|
(unsigned) msg->thread_id, msg->source_filename, msg->source_line);
|
|
break;
|
|
}
|
|
// print actual message behind header, at wp
|
|
wp += chars_written;
|
|
chars_written = snprintf(wp, buffer_size - chars_written, msg->printf_format,
|
|
msg->printf_args[0], msg->printf_args[1], msg->printf_args[2],
|
|
msg->printf_args[3], msg->printf_args[4], msg->printf_args[5],
|
|
msg->printf_args[6], msg->printf_args[7], msg->printf_args[8],
|
|
msg->printf_args[9]);
|
|
wp += chars_written;
|
|
*wp = 0; // necessary for snprintf() ?
|
|
}
|
|
}
|
|
|
|
void logger_c::set_fifo_size(unsigned size) {
|
|
fifo_init(size);
|
|
}
|
|
|
|
// single portal for all messages
|
|
volatile int m1 = 0;
|
|
void logger_c::log(logsource_c *logsource, unsigned msglevel, const char *srcfilename,
|
|
unsigned srcline, const char *fmt, ...) {
|
|
va_list args;
|
|
logmessage_t msg;
|
|
if (ignored(logsource, msglevel))
|
|
return; // don't output
|
|
|
|
fifo_mutex.lock();
|
|
// pthread_mutex_lock (&mutex);
|
|
assert(!m1);
|
|
m1++;
|
|
|
|
gettimeofday(&msg.timestamp, NULL);
|
|
msg.id = messagecount++;
|
|
msg.logsource = logsource;
|
|
msg.level = msglevel;
|
|
msg.thread_id = syscall(SYS_gettid);
|
|
msg.source_filename = basename(srcfilename); // may be full path
|
|
msg.source_line = srcline;
|
|
|
|
assert(sizeof(msg.printf_format) > strlen(fmt) + 1);
|
|
strcpy(msg.printf_format, fmt);
|
|
|
|
assert(LOGMESSAGE_ARGCOUNT >= 10);
|
|
va_start(args, fmt);
|
|
msg.printf_args[0] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[1] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[2] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[3] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[4] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[5] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[6] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[7] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[8] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.printf_args[9] = va_arg(args, LOGMESSAGE_ARGTYPE);
|
|
msg.valid = true;
|
|
|
|
fifo_push(&msg); // always into ring buffer
|
|
|
|
// print message immediately
|
|
if (msglevel <= life_level) {
|
|
char msgtext[LOGMESSAGE_TEXT_SIZE];
|
|
message_render(msgtext, sizeof(msgtext), &msg, RENDER_STYLE_CONSOLE);
|
|
cout << msgtext << "\n";
|
|
// cout << string(msgtext) << "\n"; // not thread safe???
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
m1--;
|
|
fifo_mutex.unlock();
|
|
// pthread_mutex_unlock (&mutex);
|
|
|
|
// stop program
|
|
if (msglevel == LL_FATAL) {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* dump buffer as hexdump at DEBUG level
|
|
* "markptr" = position im buffer, where a >x< should be placed
|
|
*/
|
|
void logger_c::debug_hexdump(logsource_c *logsource, const char *info, uint8_t *databuff,
|
|
unsigned databuffsize, void *markptr) {
|
|
// whole dump is one big string
|
|
char message_buffer[5000]; // 16 lines need about 1K
|
|
char phrase_buffer[80];
|
|
unsigned max_linelen = 80;
|
|
char *wp; // write pointer in outbuff
|
|
char sep = ' '; // separator char between bytes
|
|
unsigned zero_count = 0; // stop output after too many 0x00's
|
|
bool early_end = false; // terminate dump?
|
|
uint8_t *curaddr;
|
|
unsigned i;
|
|
|
|
wp = message_buffer;
|
|
assert(info); // must give an info
|
|
strcpy(wp, info);
|
|
wp += strlen(info);
|
|
sep = '\0'; // no
|
|
|
|
for (i = 0; !early_end && i < databuffsize; i++) {
|
|
if (i % 16 == 0) {
|
|
// new line
|
|
if (sep)
|
|
*wp++ = sep; // pending < marker?
|
|
// line with address prefix: +0x123
|
|
sprintf(phrase_buffer, "\n+0x%03x: ", i);
|
|
*wp = 0;
|
|
strcat(wp, phrase_buffer);
|
|
wp += strlen(phrase_buffer);
|
|
sep = ' ';
|
|
} else if (i % 8 == 0) {
|
|
*wp++ = sep; // marker?
|
|
sep = ' ';
|
|
*wp++ = '-';
|
|
}
|
|
curaddr = databuff + i;
|
|
|
|
if (curaddr == markptr) {
|
|
// before uint8_t a ">", after that a "<"
|
|
*wp++ = '>';
|
|
sep = '<';
|
|
} else {
|
|
*wp++ = sep; // Space vor jeder Zahl
|
|
sep = ' ';
|
|
}
|
|
// *curaddr = current uint8_t from buffer
|
|
#define HEXSYM(n) ( (n) < 10 ? '0' + (n) : 'a' + (n) - 10 )
|
|
*wp++ = HEXSYM(*curaddr / 16);
|
|
*wp++ = HEXSYM(*curaddr % 16);
|
|
if (*curaddr == 0x00)
|
|
zero_count++;
|
|
else
|
|
zero_count = 0;
|
|
// buffer full ??
|
|
if (((unsigned)(wp - message_buffer) + max_linelen) > sizeof(message_buffer))
|
|
// buffer almost filled
|
|
early_end = true;
|
|
|
|
// do not suppress multiple zeros: would set early_end
|
|
}
|
|
|
|
if (early_end) {
|
|
*wp++ = ' ';
|
|
*wp++ = '.';
|
|
*wp++ = '.';
|
|
*wp++ = '.';
|
|
}
|
|
*wp = '\0';
|
|
// do not output "%" chars!
|
|
log(logsource, LL_DEBUG, NULL, 0, message_buffer);
|
|
// DEBUG(channelmask, message_buffer);
|
|
}
|
|
|
|
// buffer interface
|
|
|
|
void logger_c::dump(ostream *stream, unsigned style_title, unsigned style_data) {
|
|
logmessage_t *msg;
|
|
unsigned idx = 0;
|
|
|
|
//pthread_mutex_lock(&mutex);
|
|
fifo_mutex.lock();
|
|
|
|
// optional title row
|
|
if (style_title) {
|
|
char msgtext[LOGMESSAGE_TEXT_SIZE];
|
|
message_render(msgtext, sizeof(msgtext), NULL, style_title);
|
|
*stream << string(msgtext) << "\n";
|
|
}
|
|
|
|
// dump all message in fifo in sequential order
|
|
while ((msg = fifo_get(idx++))) {
|
|
assert(msg->valid);
|
|
char msgtext[LOGMESSAGE_TEXT_SIZE];
|
|
message_render(msgtext, sizeof(msgtext), msg, style_data);
|
|
*stream << string(msgtext) << "\n";
|
|
}
|
|
fifo_mutex.unlock();
|
|
// pthread_mutex_unlock(&mutex);
|
|
}
|
|
|
|
// dump all messages in fifo to console
|
|
void logger_c::dump(void) {
|
|
dump(&cout, RENDER_STYLE_NONE, RENDER_STYLE_CONSOLE);
|
|
}
|
|
|
|
// dump all messages into a file
|
|
void logger_c::dump(string filepath) {
|
|
ofstream file_stream ;
|
|
file_stream.open(filepath, ofstream::out | ofstream::trunc);
|
|
if (!file_stream.is_open()) {
|
|
cout << "Can not open log file \"" << filepath << "\"! Aborting!\n";
|
|
exit(2);
|
|
}
|
|
dump(&file_stream, RENDER_CSV_TITLES, RENDER_CSV_DATA);
|
|
|
|
file_stream.close();
|
|
cout << "Dumped " << fifo_fill << " log messages to file \"" << filepath << "\".\n";
|
|
}
|
|
|
|
// clear fifo
|
|
void logger_c::clear(void) {
|
|
fifo_clear();
|
|
}
|
|
|