Howto limit the size of the log files

Note: RTI Connext 5.3.0 and above has new options in the LoggingQosPolicy, which allow limiting the size of RTI Connext Log Files in an easy way. Prior to RTI Connext 5.3.0, a custom device had to be set for this purpose. This article has been updated to cover both methods of limiting the size of log files, both post-5.3.0 and pre-5.3.0.

The size of log files produced by RTI Connext can be quite large. Large log files may be an issue if your environment has limited resources, or if you are just interested in the last few log messages. This solution shows two methods to set a maximum size to RTI Connext log files.

Limiting the size of Log Files (in RTI Connext 5.3.0 and above)

Creating RTI Connext Log Files

The simplest way to redirect RTI Connext logging information into a file is to configure the LoggingQosPolicy in the ParticipantFactoryQoS. With minimum setup, this will tell RTI Connext to create a single file that grows unbounded. The following XML snippet illustrates this case, where the errors will be logged to a file called rti.log:

<participant_factory_qos>
   <logging>
      <output_file>rti.log</output_file>
   </logging>
</participant_factory_qos>

Limiting the size of RTI Connext Log Files

In 5.3.0 and above, the LoggingQosPolicy allows limiting the size of RTI Connext log files. For example, in the following XML snippet we create a single log file and we limit its size to 10 kB. We also set the highest verbosity level and configure a timestamped format:

<participant_factory_qos>
   <logging>
      <output_file>rti</output_file>
      <output_file_suffix>.log</output_file_suffix>
      <verbosity>ALL</verbosity>
      <print_format>TIMESTAMPED</print_format>
      <max_bytes_per_file>10000</max_bytes_per_file>
      <max_files>1</max_files>
   </logging>
</participant_factory_qos>

As the  max_bytes_per_file and  max_files properties suggest, the logger can be set up to write multiple files as the log grows. When the  max_files limit is reached, the log files will be overwritten, starting with the first one. If max_files = 1, when the size limit is reached, the file will be reopened for writing, resulting in the loss of immediate log history. That’s why it’s best practice to set  max_files > 1. This will guarantee that the immediate log history will be in a different file.

By default,  max_bytes_per_file and  max_files are set to UNLIMITED and 1, respectively, resulting in a single log file growing indefinitely. An additional implication of having  max_bytes_per_file = UNLIMITED is that the log from different executions of your application will be concatenated into the same file. Hence, the full history from every execution will be present in the same log file. A comprehensive list of QoS properties and their description can be found at LOGGING QosPolicy (DDS Extension) in the DDS Core Libraries User’s Manual.

The following XML snippet shows an example best practice: up to 5 log files will be created, each with a maximum size of 10 kB:

<participant_factory_qos>
   <logging>
      <output_file>logs/rti_</output_file>
      <output_file_suffix>.log</output_file_suffix>
      <verbosity>ALL</verbosity>
      <print_format>TIMESTAMPED</print_format>
      <max_bytes_per_file>10000</max_bytes_per_file>
      <max_files>5</max_files>
   </logging>
</participant_factory_qos>

The files generated by this example will be stored in a logs directory and will present the following  names.

  • rti_1.log
  • rti_2.log
  • rti_3.log
  • rti_4.log
  • rti_5.log

These file names are achieved by separating the file name from its extension with the  output_file_suffix property. Setting this property is required when max_bytes_per_file != UNLIMITED.

You can set the same logging configuration programmatically using NDDSConfigLogger::set_output_file_set(). For more information about this method, refer to the API documentation.

Limiting the size of Log Files (before RTI Connext 5.3.0)

Another way of limiting the maximum size of the produced log files is to set up a custom logger. Although this option requires more work to implement, it allows for fine-grained control of the logging output. Setting up a custom logger is the only way of achieving the log file size limitation in versions of RTI Connext earlier than 5.3.0.

Creating RTI Connext Log Files

To redirect RTI Connext logging information into a file you can configure the LoggingQosPolicy in the ParticipantFactoryQoS. This will tell RTI Connext to create a single file that grows unbounded. For example, in the following XML snippet we create a log file under the logs directory with the highest verbosity level using the timestamped format:

<participant_factory_qos>
    <logging>
        <output_file>logs/rti.log</output_file>
        <verbosity>ALL</verbosity>
        <print_format>TIMESTAMPED</print_format>
    </logging>
</participant_factory_qos>

Limiting the size of RTI Connext Log Files

The purpose of this solution is to describe a method to control the size of the RTI Connext log files and keep just the most recent information (i.e., limit the log history depth). To do this, you need to create a custom class extending NDDSConfigLoggerDevice in your application. This custom logger device needs to overwrite the write() method to manage the size of the file.

Let's start with the class definition:

class MyLoggerDevice : public NDDSConfigLoggerDevice {
private:
    fpos_t _lastMessagePosition;
    unsigned long _maxSizeInKb;
    const char* _endingMessage;
    RTIOsapiSemaphore* _mutex;
    FILE* _file;
public:
    MyLoggerDevice(const char *fileName, unsigned long maxSizeInKb);
    ~MyLoggerDevice();
    virtual void write(const NDDS_Config_LogMessage *message);
};

The constructor opens the log file and creates a mutex. Note that we use a mutex to avoid concurrency problems as the write() method can be called by different threads at the same time.

MyLoggerDevice::MyLoggerDevice(const char *fileName, unsigned long maxSizeInKb) {
    _lastMessagePosition = 0;
    _maxSizeInKb = maxSizeInKb;
    _endingMessage = "\n\nEnd of current log\n\n\n";
    _mutex = RTIOsapiSemaphore_new(RTI_OSAPI_SEMAPHORE_KIND_MUTEX, NULL);
    _file = fopen(fileName, "r+");
    if (_file == NULL) { 
        _file = fopen(fileName, "w+");
        if (_file == NULL) {
            throw std::runtime_error("Could not create file");
        } 
    }   
    else {
        fseek(_file,0,SEEK_END);
    } 
}

The destructor closes the file and deletes the mutex.

MyLoggerDevice::~MyLoggerDevice() {
    fclose(_file);
    RTIOSapiSemaphore_delete(_mutex); 
}

The write() method is implemented as follows:

void MyLoggerDevice::write(const NDDS_Config_LogMessage *message){
    /* Using mutex because this method can be called multiple times*/
    RTIOsapiSemaphore_take(_mutex,NULL);
    /* Going back to the position before writing the "ending message" */
    fseek(_file,_lastMessagePosition,SEEK_SET);
    int spaceToEndOfFile = (1024*_maxSizeInKb/sizeof(char))-ftell(_file);
    /* Checking if the following message fits in our sized file */
    if (spaceToEndOfFile <= strlen(message->text))
    { 
        char buffer[100] = "\n \n";
        for (int i = 0; i <= spaceToEndOfFile/100; i++)
            fputs(buffer,_file); /* Cleaning garbage at end of file */
        fseek(_file,0,SEEK_SET); /* Back to the beginning */
    }
    fputs(message->text, _file);
    /* Remember position before writing the "ending message" */
    this->lastMessagePosition = ftell(_file);
    fputs(_endingMessage, _file);
    RTIOsapiSemaphore_give(_mutex);
}

When the log file reaches _maxSizeInKb, the write() method goes back to the beginning of the file and overwrites the oldest messages. This way, we only keep the newest messages that fit in _maxSizeInKb. We use an "ending message" to indicate the actual end of the log file.

Finally, all you have to do is to set MyLoggerDevice as an output device of NDDSConfigLogger in the main() function. Note that in the example, we set a maximum size of 10 kB for the logs/rti.log log file.

MyLoggerDevice* customDevice;
long maxSizeInKb = 10;
try {
    customDevice = new MyLoggerDevice("logs/rti.log",maxSizeInKb);     
} catch (exception &e){
    printf("Error creating MyLoggerDevice\n");
    return -1;
}
NDDSConfigLogger* logger = NDDSConfigLogger::get_instance();
if (!logger->set_output_device(customDevice)) {
    printf("Error setting output device");
} 

You can find the example code attached to this solution. 

Keywords: