Search code examples
c++fstreamofstream

How can I determine the current size of the file opened by std::ofstream?


I have a class that has a filestream of type ofstream. The constructor opens the file in append mode and all the messages always get written at the end of the file.

I need to write into outputFile up to some fixed size say 1Mb, then I need to close, rename, and compress it, and then open a new file of the same name.

This needs to be done when a certain size of file is reached.

I tried using tellg() but after reading stuffs (and this) on internet, I understood that this is not the right approach.

As I'm new to C++, I'm trying to find out the most optimized and correct way to get the accurate current size of file opened by ofstream?

class Logger {
    std::ofstream outputFile;
    int curr_size;
    Logger (const std::string logfile) : outputFile(FILENAME,
                                                     std::ios::app)
    {
        curr_size = 0;
    }
};

Somewhere in the program, I'm writing data into it:

    // ??? Determine the size of current file ???

    if (curr_size >= MAX_FILE_SIZE) {
        outputFile.close();
        //Code to rename and compress file
        // ...
        outputFile.open(FILENAME, std::ios::app);
        curr_size = 0;
    }

    outputFile << message << std::endl;
    outputFile.flush();

Solution

  • fstreams can be both input and output streams. tellg() will return the input position and tellp() will tell you of the output position. tellp() will after appending to a file tell you its size.

    Consider initializing your Logger like this (edit: added example for output stream operator):

    #include <iostream>
    #include <fstream>
    
    class Logger {
        std::string m_filename;
        std::ofstream m_os;
        std::ofstream::pos_type m_curr_size;
        std::ofstream::pos_type m_max_size;
    public:
        Logger(const std::string& logfile, std::ofstream::pos_type max_size) :
            m_filename(logfile),
            m_os(m_filename, std::ios::app),
            m_curr_size(m_os.tellp()),
            m_max_size(max_size)
        {}
    
        template<typename T>
        friend Logger& operator<<(Logger&, const T&);
    };
    
    template<typename T>
    Logger& operator<<(Logger& log, const T& msg) {
        log.m_curr_size = (log.m_os << msg << std::flush).tellp();
    
        if(log.m_curr_size>log.m_max_size) {
            log.m_os.close();
            //rename & compress
            log.m_os = std::ofstream(log.m_filename, std::ios::app);
            log.m_curr_size = log.m_os.tellp();
        }
        return log;
    }
    
    int main()
    {
        Logger test("log", 4LL*1024*1024*1024*1024);
        test << "hello " << 10 << "\n";
        return 0;
    }
    

    If you use C++17 or have an experimental version of <filesystem> available, you could also use that to get the absolute file size, like this:

    #include <iostream>
    #include <fstream>
    #include <filesystem>
    
    namespace fs = std::filesystem;
    
    class Logger {
        fs::directory_entry m_logfile;
        std::ofstream m_os;
        std::uintmax_t m_max_size;
    
        void rotate_if_needed() {
            if(max_size_reached()) {
                m_os.close();
                //rename & compress
                m_os = std::ofstream(m_logfile.path(), std::ios::app);
            }
        }
    public:
        Logger(const std::string& logfile, std::uintmax_t max_size) :
            m_logfile(logfile),
            m_os(m_logfile.path(), std::ios::app),
            m_max_size(max_size)
        {
            // make sure the path is absolute in case the process
            // have changed current directory when we need to rotate the log
            if(m_logfile.path().is_relative())
                m_logfile = fs::directory_entry(fs::absolute(m_logfile.path()));
        }
    
        std::uintmax_t size() const { return m_logfile.file_size(); }
        bool max_size_reached() const { return size()>m_max_size; }
    
        template<typename T>
        friend Logger& operator<<(Logger&, const T&);
    };
    
    template<typename T>
    Logger& operator<<(Logger& log, const T& msg) {
        log.m_os << msg << std::flush;
        log.rotate_if_needed();
        return log;
    }
    
    int main()
    {
        Logger test("log", 4LL*1024*1024*1024*1024);
        std::cout << test.size() << "\n";
        test << "hello " << 10 << "\n";
        std::cout << test.size() << "\n";
        test << "some more " << 3.14159 << "\n";
        std::cout << test.size() << "\n";
        return 0;
    }