LCOV - code coverage report
Current view: top level - libMagAOX/logger - logFileRaw.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 75.9 % 87 66
Test Date: 2026-01-03 21:03:39 Functions: 100.0 % 14 14

            Line data    Source code
       1              : /** \file logFileRaw.hpp
       2              :  * \brief Manage a raw log file.
       3              :  * \ingroup logger_files
       4              :  */
       5              : 
       6              : #ifndef logger_logFileRaw_hpp
       7              : #define logger_logFileRaw_hpp
       8              : 
       9              : #include <iostream>
      10              : 
      11              : #include <mx/ioutils/fileUtils.hpp>
      12              : #include <mx/ioutils/stringUtils.hpp>
      13              : 
      14              : #include <flatlogs/flatlogs.hpp>
      15              : 
      16              : #include "../file/fileTimes.hpp"
      17              : 
      18              : namespace MagAOX
      19              : {
      20              : namespace logger
      21              : {
      22              : 
      23              : /// A class to manage raw binary log files
      24              : /** Manages a binary file containing MagAO-X logs.
      25              :  *
      26              :  * The log entries are written as a binary stream of a configurable
      27              :  * maximum size.  If this size will be exceed by the next entry, then a new file is created.
      28              :  *
      29              :  * Filenames have a standard form of: `[path]/[name]/[name]_YYYYMMDDHHMMSSNNNNNNNNN.[ext]` where fields in [] are
      30              :  * configurable.
      31              :  *
      32              :  * The timestamp in the file name is from the first entry of the file.
      33              :  *
      34              :  */
      35              : template <class verboseT = XWC_DEFAULT_VERBOSITY>
      36              : class logFileRaw
      37              : {
      38              : 
      39              :   protected:
      40              :     /** \name Configurable Parameters
      41              :      *@{
      42              :      */
      43              :     std::string m_logPath{ "." };                  ///< The base path for the log files.
      44              :     std::string m_logName{ "xlog" };               ///< The base name for the log files.
      45              :     std::string m_logExt{ MAGAOX_default_logExt }; ///< The extension for the log files.
      46              : 
      47              :     size_t m_maxLogSize{ MAGAOX_default_max_logSize }; ///< The maximum file size in bytes. Default is 10 MB.
      48              :     ///@}
      49              : 
      50              :     /** \name Internal State
      51              :      *@{
      52              :      */
      53              : 
      54              :     FILE *m_fout{ 0 }; ///< The file pointer
      55              : 
      56              :     size_t m_currFileSize{ 0 }; ///< The current file size.
      57              : 
      58              :     ///@}
      59              : 
      60              :   public:
      61              :     /// Default constructor
      62              :     /** Currently does nothing.
      63              :      */
      64              :     logFileRaw();
      65              : 
      66              :     /// Destructor
      67              :     /** Closes the file if open
      68              :      */
      69              :     ~logFileRaw();
      70              : 
      71              :     /// Set the path.
      72              :     /**
      73              :      *
      74              :      * \returns 0 on success
      75              :      * \returns -1 on error
      76              :      */
      77              :     mx::error_t logPath( const std::string &newPath /**< [in] the new value of _path */ );
      78              : 
      79              :     /// Get the path.
      80              :     /**
      81              :      * \returns the current value of m_logPath.
      82              :      */
      83              :     std::string logPath();
      84              : 
      85              :     /// Set the log name
      86              :     /**
      87              :      *
      88              :      * \returns 0 on success
      89              :      * \returns -1 on error
      90              :      */
      91              :     mx::error_t logName( const std::string &newName /**< [in] the new value of m_logName */ );
      92              : 
      93              :     /// Get the name
      94              :     /**
      95              :      * \returns the current value of _name.
      96              :      */
      97              :     std::string logName();
      98              : 
      99              :     /// Set the log extension
     100              :     /**
     101              :      *
     102              :      * \returns 0 on success
     103              :      * \returns -1 on error
     104              :      */
     105              :     mx::error_t logExt( const std::string &newExt /**< [in] the new value of m_logExt */ );
     106              : 
     107              :     /// Get the log extension
     108              :     /**
     109              :      * \returns the current value of m_logExt.
     110              :      */
     111              :     std::string logExt();
     112              : 
     113              :     /// Set the maximum file size
     114              :     /**
     115              :      *
     116              :      * \returns 0 on success
     117              :      * \returns -1 on error
     118              :      */
     119              :     mx::error_t maxLogSize( size_t newMaxFileSize /**< [in] the new value of _maxLogSize */ );
     120              : 
     121              :     /// Get the maximum file size
     122              :     /**
     123              :      * \returns the current value of m_maxLogSize
     124              :      */
     125              :     size_t maxLogSize();
     126              : 
     127              :     /// Write a log entry to the file
     128              :     /** Checks if this write will exceed m_maxLogSize, and if so opens a new file.
     129              :      * The new file will have the timestamp of this log entry.
     130              :      *
     131              :      * \returns 0 on success
     132              :      * \returns -1 on error
     133              :      */
     134              :     mx::error_t writeLog( flatlogs::bufferPtrT &data /**< [in] the log entry to write to disk */ );
     135              : 
     136              :     /// Flush the stream
     137              :     /** Calls `fflush`. See issue #192
     138              :      *
     139              :      * \returns 0 on success
     140              :      * \returns -1 on error
     141              :      */
     142              :     mx::error_t flush();
     143              : 
     144              :     /// Close the file pointer
     145              :     /** Sets \ref m_fout to nullptr after calling fclose regardless of error.
     146              :      *
     147              :      * \returns 0 on success
     148              :      * \returns -1 on error
     149              :      */
     150              :     mx::error_t close();
     151              : 
     152              :   protected:
     153              :     /// Create a new file
     154              :     /** Closes the current file if open.  Then creates a new file with a name of the form
     155              :      * [path]/[name]/YYYY_MM_DD/[name]_YYYYMMDDHHMMSSNNNNNNNNN.[ext]
     156              :      *
     157              :      *
     158              :      * \returns 0 on success
     159              :      * \returns -1 on error
     160              :      */
     161              :     mx::error_t createFile( flatlogs::timespecX &ts /**< [in] A MagAOX timespec, used to set the timestamp */ );
     162              : };
     163              : 
     164              : template <class verboseT>
     165         2442 : logFileRaw<verboseT>::logFileRaw()
     166              : {
     167          407 : }
     168              : 
     169              : template <class verboseT>
     170          407 : logFileRaw<verboseT>::~logFileRaw()
     171              : {
     172          407 :     close();
     173          407 : }
     174              : 
     175              : template <class verboseT>
     176           34 : mx::error_t logFileRaw<verboseT>::logPath( const std::string &newPath )
     177              : {
     178              :     try
     179              :     {
     180           34 :         m_logPath = newPath;
     181              :     }
     182            0 :     catch( const std::bad_alloc &e )
     183              :     {
     184            0 :         std::throw_with_nested( xwcException( "string assignment" ) );
     185              :     }
     186            0 :     catch( const std::exception &e )
     187              :     {
     188            0 :         return mx::error_report( mx::error_t::std_exception, std::string( "string assignment: " ) + e.what() );
     189              :     }
     190              : 
     191           34 :     return mx::error_t::noerror;
     192              : }
     193              : 
     194              : template <class verboseT>
     195           16 : std::string logFileRaw<verboseT>::logPath()
     196              : {
     197           16 :     return m_logPath;
     198              : }
     199              : 
     200              : template <class verboseT>
     201           24 : mx::error_t logFileRaw<verboseT>::logName( const std::string &newName )
     202              : {
     203              :     try
     204              :     {
     205           24 :         m_logName = newName;
     206              :     }
     207            0 :     catch( const std::bad_alloc &e )
     208              :     {
     209            0 :         std::throw_with_nested( xwcException( "string assignment" ) );
     210              :     }
     211            0 :     catch( const std::exception &e )
     212              :     {
     213            0 :         return mx::error_report( mx::error_t::std_exception, std::string( "string assignment: " ) + e.what() );
     214              :     }
     215              : 
     216           24 :     return mx::error_t::noerror;
     217              : }
     218              : 
     219              : template <class verboseT>
     220           12 : std::string logFileRaw<verboseT>::logName()
     221              : {
     222           12 :     return m_logName;
     223              : }
     224              : 
     225              : template <class verboseT>
     226            6 : mx::error_t logFileRaw<verboseT>::logExt( const std::string &newExt )
     227              : {
     228              :     try
     229              :     {
     230            6 :         m_logExt = newExt;
     231              :     }
     232            0 :     catch( const std::bad_alloc &e )
     233              :     {
     234            0 :         std::throw_with_nested( xwcException( "string assignment" ) );
     235              :     }
     236            0 :     catch( const std::exception &e )
     237              :     {
     238            0 :         return mx::error_report( mx::error_t::std_exception, std::string( "string assignment: " ) + e.what() );
     239              :     }
     240              : 
     241            6 :     return mx::error_t::noerror;
     242              : }
     243              : 
     244              : template <class verboseT>
     245           13 : std::string logFileRaw<verboseT>::logExt()
     246              : {
     247           13 :     return m_logExt;
     248              : }
     249              : 
     250              : template <class verboseT>
     251            2 : mx::error_t logFileRaw<verboseT>::maxLogSize( size_t newMaxFileSize )
     252              : {
     253              :     try
     254              :     {
     255            2 :         m_maxLogSize = newMaxFileSize;
     256              :     }
     257              :     catch( const std::bad_alloc &e )
     258              :     {
     259              :         std::throw_with_nested( xwcException( "string assignment" ) );
     260              :     }
     261              :     catch( const std::exception &e )
     262              :     {
     263              :         return mx::error_report( mx::error_t::std_exception, std::string( "string assignment: " ) + e.what() );
     264              :     }
     265              : 
     266            2 :     return mx::error_t::noerror;
     267              : }
     268              : 
     269              : template <class verboseT>
     270            2 : size_t logFileRaw<verboseT>::maxLogSize()
     271              : {
     272            2 :     return m_maxLogSize;
     273              : }
     274              : 
     275              : template <class verboseT>
     276         1433 : mx::error_t logFileRaw<verboseT>::writeLog( flatlogs::bufferPtrT &data )
     277              : {
     278         1433 :     size_t N = flatlogs::logHeader::totalSize( data );
     279              : 
     280              :     // Check if we need a new file
     281         1433 :     if( m_currFileSize + N > m_maxLogSize || m_fout == 0 )
     282              :     {
     283           37 :         flatlogs::timespecX ts = flatlogs::logHeader::timespec( data );
     284              : 
     285           37 :         mx_error_check( createFile( ts ) );
     286              :     }
     287              : 
     288         1433 :     size_t nwr = fwrite( data.get(), sizeof( char ), N, m_fout );
     289              : 
     290         1433 :     if( nwr != N * sizeof( char ) )
     291              :     {
     292            0 :         return mx::error_report<verboseT>( mx::errno2error_t( errno ), "Error from fwrite" );
     293              :     }
     294              : 
     295         1433 :     m_currFileSize += N;
     296              : 
     297         1433 :     return mx::error_t::noerror;
     298              : }
     299              : 
     300              : template <class verboseT>
     301           68 : mx::error_t logFileRaw<verboseT>::flush()
     302              : {
     303              :     ///\todo this probably should be fsync, with appropriate error handling (see fsyncgate) [issue #192]
     304              : 
     305           68 :     if( m_fout )
     306              :     {
     307           66 :         if( fflush( m_fout ) != 0 )
     308              :         {
     309            0 :             return mx::error_report<verboseT>( mx::errno2error_t( errno ), "Error from fflush" );
     310              :         }
     311              :     }
     312           68 :     return mx::error_t::noerror;
     313              : }
     314              : 
     315              : template <class verboseT>
     316          452 : mx::error_t logFileRaw<verboseT>::close()
     317              : {
     318          452 :     if( m_fout )
     319              :     {
     320           41 :         errno = 0;
     321              : 
     322           41 :         if( fclose( m_fout ) != 0 )
     323              :         {
     324            0 :             m_fout = nullptr;
     325              : 
     326            0 :             return mx::error_report<verboseT>( mx::errno2error_t( errno ), "Error from fflush" );
     327              :         }
     328              : 
     329           41 :         m_fout = nullptr;
     330              :     }
     331              : 
     332          452 :     return mx::error_t::noerror;
     333              : }
     334              : 
     335              : template <class verboseT>
     336           44 : mx::error_t logFileRaw<verboseT>::createFile( flatlogs::timespecX &ts )
     337              : {
     338           44 :     std::string fileName;
     339           44 :     std::string relPath;
     340              : 
     341              :     try
     342              :     {
     343           44 :         mx::error_t errc = file::fileTimeRelPath( fileName, relPath, m_logName, m_logExt, ts.time_s, ts.time_ns );
     344              : 
     345           44 :         if( !!errc )
     346              :         {
     347            1 :             return mx::error_report<verboseT>( errc );
     348              :         }
     349              :     }
     350            0 :     catch( ... )
     351              :     {
     352            0 :         std::throw_with_nested( mx::exception<verboseT>(mx::error_t::exception));
     353              :     }
     354              : 
     355           43 :     std::string fullPath = m_logPath + '/' + relPath + '/';
     356              : 
     357              :     // Create directory
     358           43 :     mx::error_t errc = mx::ioutils::createDirectories( fullPath );
     359              : 
     360           43 :     if( !!errc )
     361              :     {
     362            2 :         return mx::error_report<verboseT>( errc, "creating directory" );
     363              :     }
     364              : 
     365           42 :     fullPath += fileName;
     366              : 
     367           42 :     if( mx::ioutils::exists( fullPath, errc ) )
     368              :     {
     369            1 :         return mx::error_report<verboseT>( mx::error_t::eexist, "file " + fullPath + " exists" );
     370              :     }
     371              : 
     372           41 :     if( !!errc )
     373              :     {
     374            0 :         return mx::error_report<verboseT>( errc, "checking directory" );
     375              :     }
     376              : 
     377              :     // Close current file if it's open
     378           41 :     errc = close();
     379           41 :     if( errc != mx::error_t::noerror )
     380              :     {
     381            0 :         mx::error_report<verboseT>( errc, "Error from close, attempting to continue");
     382              :     }
     383              : 
     384           41 :     errno = 0;
     385              : 
     386           41 :     m_fout = fopen( fullPath.c_str(), "wb" );
     387              : 
     388           41 :     if( m_fout == 0 )
     389              :     {
     390            0 :         return mx::error_report<verboseT>( mx::errno2error_t( errno ), "Error from fopen on " + fullPath );
     391              :     }
     392              : 
     393              :     // Reset counters.
     394           41 :     m_currFileSize = 0;
     395              : 
     396           41 :     return mx::error_t::noerror;
     397           44 : }
     398              : 
     399              : extern template class logFileRaw<XWC_DEFAULT_VERBOSITY>;
     400              : 
     401              : } // namespace logger
     402              : } // namespace MagAOX
     403              : 
     404              : #endif // logger_logFileRaw_hpp
        

Generated by: LCOV version 2.0-1