Adding a header to the head of a log file

102 Views Asked by At

I would like to add a header to a log file which would describe the format of the data in the log file. The purpose of this is to perform post analysis in python pandas. I am looking to add a header something like this (the first line is the header, the rest is data).

The header needs to be added when the file is initialized and only be at the head of the file.

YYYYMMDDHHMMSS.MS,TYPE,SIMULATION_TIME,PLATFORM_ID,LATITUDE,LONGITUDE,ELEVATION
20231008091747.442690,[info],0,drone[0],21.281,-157.796,41.7117
20231008091747.448856,[info],0,drone[2],21.2576,-157.799,6.23664
20231008091747.448907,[info],0,drone[3],21.2576,-157.799,6.23664
20231008091747.453667,[info],0,server,21.3,-157.8,196.413
20231008091747.454533,[info],0.033,drone[0],21.281,-157.796,41.6859
20231008091747.454550,[info],0.033,drone[2],21.2576,-157.799,6.24225

Update to this post...
adding my source file.
I use the log elsewhere in my code using the following

LOG_POSITION << simTime() << "," << getFullName() << "," << latitude << "," << longitude << "," << test;

Here is my log header file. logger.h

#pragma once

#define BOOST_LOG_DYN_LINK 1
#include <boost/log/expressions.hpp>
#include <boost/log/sources/global_logger_storage.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>

// This header needs to be implemented and called only once
// during initialization
#define LOG_POSITION_HEADER BOOST_LOG_SEV(my_logger::get(), boost::log::trivial::info)
#define LOG_POSITION        BOOST_LOG_SEV(my_logger::get(), boost::log::trivial::info)
#define WARN1               BOOST_LOG_SEV(my_logger::get(), boost::log::trivial::warning)
#define ERROR1              BOOST_LOG_SEV(my_logger::get(), boost::log::trivial::error)

#define SYS_LOGFILE             "../resources/logs/droneMovements.log"

typedef boost::log::sources::severity_logger_mt<boost::log::trivial::severity_level> logger_t;

BOOST_LOG_GLOBAL_LOGGER(my_logger, logger_t)

The cc file logger.cc

#include "logger.h"

namespace attrs   = boost::log::attributes;
namespace expr    = boost::log::expressions;
namespace logging = boost::log;

BOOST_LOG_GLOBAL_LOGGER_INIT(my_logger, logger_t)
{
    logger_t lg;

    logging::add_common_attributes();

    logging::add_file_log(
            boost::log::keywords::file_name = SYS_LOGFILE,
            boost::log::keywords::format = (expr::stream
                                            << expr::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y%m%d%H%M%S.%f,")
                                            << expr::smessage)
    );

    logging::core::get()->set_filter(logging::trivial::severity >= logging::trivial::info);

    return lg;
}
2

There are 2 best solutions below

0
sehe On

You don't show any code, so you're having us guess/make assumptions.

Let me assume that you use any of the setup helpers to arrive at a text_file_backend, e.g.:

Live On Coliru

#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <fstream>
#include <iomanip>
namespace l = boost::log;
using namespace l::trivial;

void init() {
    l::add_file_log("sample.log");

    l::core::get()->set_filter(severity >= l::trivial::info);
    l::add_common_attributes();
}

int main() {
    init();

    l::sources::severity_logger<severity_level> lg;

    BOOST_LOG_SEV(lg, trace)   << "A trace severity message";
    BOOST_LOG_SEV(lg, debug)   << "A debug severity message";
    BOOST_LOG_SEV(lg, info)    << "An informational severity message";
    BOOST_LOG_SEV(lg, warning) << "A warning severity message";
    BOOST_LOG_SEV(lg, error)   << "An error severity message";
    BOOST_LOG_SEV(lg, fatal)   << "A fatal severity message";
}

Creating a log file containing:

An informational severity message
A warning severity message
An error severity message
A fatal severity message

The Problem

The textfile backend likes to govern the actual file name (having capabilities of rotating log files and naming according to certain pattern and limits).

Also, the initialization is lazy, so even if it seemed you can get the current filename from the backend like:

auto             f    = l::add_file_log("sample.log");
auto             be   = f->locked_backend();
filesystem::path name = be->get_current_file_name();

It will not give a name until after the sink actually gets activated, which happens at the first log. By then it will be too late to insert a header.

A solution?

Depending on your actual context, you may not need all the features, and you could perhaps get away with opening your own stream and logging to that:

void init() {
    static std::ofstream ofs("sample.log");
    ofs << "THIS IS A HEADER LINE" << std::endl;

    l::add_console_log(ofs);

    l::core::get()->set_filter(severity >= l::trivial::info);
    l::add_common_attributes();
}

Which does give the expected outcome:

THIS IS A HEADER LINE
An informational severity message
A warning severity message
An error severity message
A fatal severity message

Elegance?

This is not very elegant in that it uses a static variable. This limitation is due to add_console_log only (which as the name implies, expects existing globals like std::clog).

So you can be more verbose in the initialization to avoid the static:

auto ofs = boost::make_shared<std::ofstream>("sample.log");
*ofs << "THIS IS A HEADER LINE" << std::endl;

{
    using BE = l::sinks::basic_text_ostream_backend<char>;
    using S  = l::sinks::synchronous_sink<BE>;

    auto be  = boost::make_shared<BE>();
    be->add_stream(ofs);

    l::core::get()->add_sink(boost::make_shared<S>(be));
}

l::core::get()->set_filter(severity >= l::trivial::info);
l::add_common_attributes();

Whether or not this is actual elegant or worth it, is a decision I'll happily leave to your discretion.

0
Andrey Semashev On

Assuming you are using text_file_backend to write the log file, you can use file open handler to write the header to the file. For example:

sink->locked_backend()->set_open_handler([](sinks::text_file_backend::stream_type& file)
{
    file << "YYYYMMDDHHMMSS.MS,TYPE,SIMULATION_TIME,PLATFORM_ID,LATITUDE,LONGITUDE,ELEVATION\n";
});

Note that the handler will be called whenever a new log file is opened, which means any new files after log file rotation will also have the header.