Logging

Files CCore/inc/Log.h CCore/src/Log.cpp

CCore provides a log implementation, it can be used as a system-wide log in embedded applications or as a local log of an application or a system component. The log is efficient, providing you use efficient types for log message arguments. To store log messages a fixed memory space is used.

A log message has an associated category and a time-stamp. You can use filters to control the message logging and the message selection from a log. Log also counts messages. Log stores a message as a compact data structure, which includes message category, time-stamp, format string and format arguments. Format string must be a persistent string. Format arguments are copied, so they must have a value semantic. It is recommended, that format arguments should be Small Data Types.

LogCategory_enum

The class LogCategory_enum implements a log message category type, based on two enums.


template <class Src,class Type> // Src and Type are uint5 enums
class LogCategory_enum
 {
   ....
   
  public:
   
   // constructors
  
   LogCategory_enum();
  
   LogCategory_enum(Src src,Type type);
   
   // methods
   
   Src getSource() const;
  
   Type getType() const;
   
   // print object
 
   template <class P>
   void print(P &out) const;
 };

It stores internally two enum values: Src and Type. It is assumed that the Src describes the message source, and the Type describes the message type. Src and Type must be "uint5" enums, i.e. the usable value range should be representable by a 5-bits unsigned.

LogFilter_enum

The class LogFilter_enum implements a log message filter for the category type of the LogCategory_enum.


template <class Src,class Type> // Src and Type are uint5 enums
class LogFilter_enum
 {
   ....

  public:
   
   // constructors
  
   LogFilter_enum(); // enable all
   
   LogFilter_enum(LogCategory_enum<Src,Type> cat); // filter all other cats
   
   // enable/disable
   
   void enable(Src src);
    
   void enable(Type type);
    
   void disable(Src src);
    
   void disable(Type type);
   
   // filter all cats with disabled src OR disabled type
   
   bool operator () (LogCategory_enum<Src,Type> cat) const;
 };
 

Default constructor creates a filter, which passes all categories.

The second constructor creates a filter, which passes only the given category.

enable() enables the given type or source.

disable() disables the given type or source.

The operator () is a filter function. It is called to filter out the given category. If the return value is true, the category must be filtered out. This filter filters categories, whose source is disabled OR whose type is disabled.

LogStamp

The class LogStamp is a default log time-stamp class implementation. It is a Small Data Type. It holds 3 values: the message number, the message second time-stamp and the message differential clock time-stamp (i.e. the clock time since the previous message). The inner class Host is responsible for the time-stamp generation. An instance of this class is created as the part of a log object.


class LogStamp
 {
   ulen num;
   SecTimer::ValueType time_sec;
   ClockDiffTimer::ValueType time_clock;
   
  public: 
  
   // constructors
   
   LogStamp() : num(0),time_sec(0),time_clock(0) {}
   
   // print object
   
   template <class P>
   void print(P &out) const
    {
     Printf(out,"#;) [#;,#6r;]",num,PrintTime(time_sec),time_clock);
    }
   
   // Host
    
   class Host : NoCopy
    {
      ulen num;
      SecTimer sec_timer;
      ClockDiffTimer clock_timer;
      
     public:
     
      Host() : num(0) {}
      
      void operator () (LogStamp &stamp)
       {
        stamp.num=num++;
        stamp.time_sec=sec_timer.get();
        stamp.time_clock=clock_timer.get();
       }
    };
 };

Any other log time-stamp classes must follow this pattern:


class SomeLogTimeStamp // Small Data Type
 {
   ....

  public:

   // default constructor

   SomeLogTimeStamp();

   // print object
   
   template <class P>
   void print(P &out) const;

   // Host -- time-stamp generator
    
   class Host : NoCopy
    {
      ....

     public:
     
      Host();
      
      void operator () (SomeLogTimeStamp &stamp)
    };
 };

LogCounter

The class LogCounter is to count log messages. It has two counters: the total message count and the committed message count. A message can be skipped by different reasons.


class LogCounter
 {
   ulen total;
   ulen committed;
 
  public:
  
   // constructors
   
   LogCounter()
    {
     total=0;
     committed=0;
    }
   
   // count
   
   void alloc() { total++; }
   
   void commit() { committed++; }
   
   // print object
   
   template <class P>
   void print(P &out) const
    {
     Printf(out,"total = #;",total);
     
     if( ulen skipped=total-committed ) Printf(out," skipped = #;",skipped);
    }
 };

LogMsgBase

This class serves as the base class for log message object classes.


template <class Cat,class Stamp>
class LogMsgBase : NoCopy
 {
   ....

  private: 
   
   virtual void printBody(PrintBase &) const {}
   
  public:
  
   // constructors
   
   explicit LogMsgBase(Cat cat_) : cat(cat_) {}
   
   virtual ~LogMsgBase() {}
   
   // methods
   
   Cat getCategory() const { return cat; }
   
   // print object
   
   void print(PrintBase &out) const;
 };

LogMsg

This class is an actual log message object type, as it is stored in a log. It is derived from the LogMsgBase.


template <class Cat,class Stamp,class ... TT>
class LogMsg<Cat,Stamp,TT...> : public LogMsgBase<Cat,Stamp>
 {
   const char *format;
   Tuple<TT...> data;
   
  private: 
   
   virtual void printBody(PrintBase &out) const
    {
     Printf(out,format,data);
    }
   
  public: 
  
   LogMsg(Cat cat,const char *format_,const TT & ... tt)
    : LogMsgBase<Cat,Stamp>(cat),format(format_),data(tt...) {}
 };

LogMem

The class LogMem is a memory distributor for log messages. This class is used by the LogStorage to manage the log message memory blocks. You don't need to use it directly.


class LogMem : NoCopy
 {
   ....

  public:
  
   // constructors
   
   explicit LogMem(ulen mem_len); 
   
   explicit LogMem(Space space); // space.mem aligned
   
   ~LogMem();
   
   // methods
   
   void clear();
   
   void * alloc(ulen len);
   
   void free(void *ret);
   
   void del(void *beg);
 };

LogStorage

This class is a storage for log messages. It is a core class for the log implementation.


template <class Cat,class Stamp>
class LogStorage : NoCopy
 {
   ....

  public:
  
   // constructors
   
   explicit LogStorage(ulen mem_len,bool overrun=true);
   
   explicit LogStorage(Space space,bool overrun=true); // space.mem aligned
   
   ~LogStorage();
   
   // methods
   
   ulen getCount() const;
   
   void getCounter(LogCounter &ret) const;
   
   void setOverrun(bool overrun);
   
   void delAll();
   
   // message
   
   void * alloc(ulen msg_len);
   
   void free(void *mem);
   
   void commit(void *mem,LogMsgBase<Cat,Stamp> *msg);
   
   // cursor
   
   const LogMsgBase<Cat,Stamp> * startCursor(); 
   
   const LogMsgBase<Cat,Stamp> * nextCursor(); 
    
   void stopCursor();
 };

The first constructor allocates the memory dynamically. The second uses the provided memory space. The base address of this space must be aligned.

getCount() returns the number of messages in the storage.

getCounter() returns the current message counter.

setOverrun() sets the overrun flag. If this flag is set and there is no enough memory for a new message, the oldest messages are deleted to acquire the memory.

delAll() deletes all messages.

To add a new message to the storage, you must allocate the memory for the message using the method alloc(). The argument is the required memory length. If there is no enough memory, alloc() returns null (but the current message counter is updated). Otherwise, you get the address of the memory block. You must create a message at this address, or return this block, using the method free(). If you are succeeded in the message creation, you must commit the message into the storage using the method commit(). The first argument is the address of the memory block, the second is the pointer to the message base class (the message type is derived from the LogMsgBase<Cat,Stamp>).

A storage is responsible for the message counting and the message stamping.

You can iterate over the storage content using the storage cursor. There can be only one active storage cursor at any moment.

startCursor() starts the cursor. If the cursor is already active, the null is returned. Otherwise, the pointer to the first message is returned, or null if there is no one, in the last case the cursor is not activated.

nextCursor() moves to the next message. If the return value is null, the cursor is not active anymore.

stopCursor() deactivates the cursor. You must use this method when you have done with an iteration, unless the last call of the nextCursor() has returned null.

Be advised, while the storage cursor is active, the current cursor message and all following cannot be deleted. The method delAll(), in particular, leaves these messages alive. It means, you have to finish with the cursor before the storage object destructor call.

UserLog

The class UserLog is a "user" any-purpose log. It is parameterized by four template parameter.

The first parameter is Cat, it is a log message category type.

The second is Stamp, it is a log time-stamp class.

The third is Filter, it is a log message filter class.

The last is Mutex, it is a mutex class. The object of this type is used to protect log operations. It is defaulted to NoMutex. If you need a thread-safe log, use a real Mutex class here.


template <class Cat,class Stamp,class Filter,class Mutex>
class UserLog : NoCopy
 {
   ....

  public:
   
   // constructors
  
   UserLog(TextLabel name,ulen mem_len,bool overrun=true);
   
   UserLog(TextLabel name,Space space,bool overrun=true) // space.mem aligned
   
   ~UserLog();
   
   // methods
   
   TextLabel getName() const;
   
   void getCounter(LogCounter &ret) const; 
   
   void setOverrun(bool overrun); 
    
   void setFilter(Filter filter);
   
   void getFilter(Filter &ret) const;
   
   // message
   
   template <class ... TT>
   void operator () (Cat cat,const char *format,const TT & ... tt);
   
   // cursor
    
   class Cursor;
   
   // print
   
   class PrintFunc;
   
   PrintFunc getPrint(ulen count,Filter filter=Filter());
 };

Two constructors are differ only in the way of the memory allocation: the first takes memory from the heap. The second uses the provided memory space, the space base address must be aligned. The name is used to name the internal mutex object and the log itself. The overrun is the overrun flag.

getName() returns the log name.

getCounter() returns the log message counter.

setOverrun() sets the overrun flag. If this flag is set, then new messages will overrun oldest messages in the log. Otherwise, log doesn't delete old messages and therefor has a limited capacity.

setFilter() sets the given message filter to drop incoming messages.

getFilter() returns the current log filter.

To add a message to the log the operator () is used. The first argument is the message category. The message can be filtered out based on this value. The second argument is the message format string. It must be a persistent string (at least for the log life-time duration), not a temporary life-time value. Usually, it is a string literal. The last arguments are used to format the message pretty the same way as the Printf() function is working.

getPrint() builds a temporary object to be used for printing. It prints up to the given count of log messages from the beginning of the log using the given filter to filter out messages for printing. Printing doesn't lock the log: logging may continue during the print operation. That is why we need some message count limit. If you are sure the log is not appending anymore, you may use the MaxULen value for the count. Be advised, a log can be printed only to printer objects, whose type is derived from the PrintBase class.

The global function PrintLog() can also be used to build a print object:


template <class Cat,class Stamp,class Filter,class Mutex>
auto PrintLog(UserLog<Cat,Stamp,Filter,Mutex> &log,ulen count,Filter filter);
 
template <class Cat,class Stamp,class Filter,class Mutex>
auto PrintLog(UserLog<Cat,Stamp,Filter,Mutex> &log,ulen count);

The inner class Cursor can be used to iterate over the log messages. There can be only one active cursor object at the given time.


class Cursor : NoCopy
 {
   ....

  public:
   
   // constructors
  
   Cursor(UserLog &log,ulen count);
   
   ~Cursor();
   
   // object ptr
    
   bool operator + () const;
   
   bool operator ! () const;
   
   const LogMsgBase * getPtr() const;
   
   const LogMsgBase & operator * () const;
    
   const LogMsgBase * operator -> () const;
   
   // cursor
   
   void operator ++ ();
 };