Pages

Wednesday, May 17, 2017

Logging - Basics #2

After my introduction to logging it became clear to me that I needed to improve the initial ideas. As a process, the enhancements couldn't be achieved all at once, but gradually, step by step, according to the Natural laws :-) More specifically, I tried to address points 1, 2 and 4 from my initial attempt.

A new abstract type is to provide a general interface for all kinds of logging devices, not only file-system as firstly attempted. With this in mind, a log just need to be opened, written-to and finally closed when done. I tried to reflect such a model as shown next.

The general abstract interface:

 1 #ifndef FRAMEWORK_HXX
 2 #define FRAMEWORK_HXX
 3 
 4 namespace framework
 5 {
 6 namespace log
 7 {
 8 
 9 struct file
10 {
11   virtual ~file() {}
12 
13   virtual bool open() = 0;
14   virtual bool close() = 0;
15 
16   virtual bool write( const char *, ... ) const = 0;
17 };
18 
19 }
20 }
21 
22 #endif /* FRAMEWORK_HXX */
23 

One partial-implementation by a sub-library or application:

  1 /*
       ...
 10  *
 11  * Created on May 14, 2017, 8:56 PM
 12  */
 13 
 14 #ifndef LOG_HXX
 15 #define LOG_HXX
 16 
 17 #include <sys/types.h>
 18 #include <sys/stat.h>
 19 #include <fcntl.h>
 20 
 21 #include <unistd.h>
 22 
 23 #include <ctime>
 24 #include <cstdarg>
 25 #include <cstring>
 26 #include <cstdlib>
 27 #include <cstdio>
 28 
 29 #include <functional>
 30 
 31 #include "framework.hxx"
 32 
 33 namespace application
 34 {
 35 namespace log
 36 {
 37 
 38 namespace implementation
 39 {
 40 
 41 struct file : public framework::log::file
 42 {
 43 
 44   file() : fd { -1 }
 45   {
 46   }
 47 
 48   ~file()
 49   {
 50     close();
 51   }
 52 
 53   bool close() override
 54   {
 55     return fd >= 0
 56       ? ::close( fd ) == 0
 57         ? ( fd = -1, true )
 58         : false
 59       : true;
 60   }
 61 
 62   bool write( const char * format, ... ) const override
 63   {
 64     ::ssize_t rv = -1;
 65 
 66     if ( ! timestamp() )
 67       return false;
 68 
 69     va_list ap;
 70     va_start( ap, format );
 71 
 72     // MT-safe if setlocale(3C) not called
 73     // Hence, possibly a serious limitation!
 74     // More advanced C++ to the rescue!
 75     char * line = 0;
 76     ::vasprintf( &line, format, ap );
 77     rv = ::write( fd, line, ::strlen(line) );
 78     ::free( line );
 79 
 80     va_end( ap );
 81 
 82     return rv != -1;
 83   }
 84 
 85 private:
 86 
 87   bool timestamp() const
 88   {
 89     ::time_t utc;
 90     if ( ::time( &utc ) < 0 )
 91       return false;
 92 
 93     ::tm local;
 94     if ( ::localtime_r( &utc, &local ) == 0 )
 95       return false;
 96 
 97     const int size = 32;
 98     char ts[ size ];
 99     if ( ::strftime( ts, size, "[%F %T] ", &local ) == 0 )
100       return false;
101 
102     return ::write( fd, ts, ::strlen(ts) ) != -1;
103   }
104 
105 protected:
106 
107   const ::mode_t mode { S_IRUSR | S_IWUSR | S_IRGRP };
108   const int flags { O_WRONLY | O_APPEND | O_CREAT };
109 
110   int fd;
111 };
112 
113 }
114 
115 struct file : implementation::file
116 {
117   using get_fd_t = int ( const int /* flags */, const ::mode_t );
118   using callback_open = std::function< get_fd_t >;
119 
120   file( callback_open get_fd ) : get_fd( get_fd )
121   {
122   }
123 
124   bool open() override
125   {
126     return fd >= 0
          ? true
          : ( fd = get_fd( flags, mode ) ) >= 0;
127   }
128 
129 private:
130 
131   callback_open get_fd;
132 };
133 
134 }
135 }
136 
137 #endif /* LOG_HXX */
138 

An application could use or extend the above partial-library as follows:

 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 
 5 #include <cstdlib>
 6 #include <string>
 7 
 8 #include "framework.hxx"
 9 #include "log.hxx"
10 
11 void use( framework::log::file & log, const char * message )
12 {
13   if ( ! log.open() )
14     exit( EXIT_FAILURE );
15 
16   log.write( message );
17 }
18 
19 namespace application
20 {
21 namespace log
22 {
23 
24 int open_1( const int flags, const ::mode_t mode )
25 {
26   return ::open( "trace_1.log", flags, mode );
27 }
28 
29 struct open_2
30 {
31   open_2( const std::string & file_name ) : 
       file_name { file_name }
     {
     }
32 
33   int operator () ( const int flags, const ::mode_t mode ) const
34   {
35     return ::open( file_name.c_str(), flags, mode );
36   }
37 
38   const std::string file_name;
39 };
40 
41 struct log3 : public implementation::file
42 {
43   bool open() override
44   {
45     return fd >= 0
46        ? true
47        : ( fd = ::open( "trace_3.log", flags, mode ) ) >= 0;
48   }
49 };
50 
51 }
52 }
53 
54 int main()
55 {
56   namespace log = application::log;
57 
58   log::open_2 open_2( "trace_2.log" );
59 
60   log::file log1( log::open_1 );
61   log::file log2( open_2 );
62   log::log3 log3;
63 
64   log::file log4( log::open_2( "trace_4.log" ) );
65 
66   use( log1, "Test message 1!\n" );
67   use( log2, "Test message 2!\n" );
68   use( log3, "Test message 3!\n" );
69 
70   use( log4, "Test message 4!\n" );
71 
72   return EXIT_SUCCESS;
73 }
74 

As seen above, now there's a general interface which usage is demonstrated in the use() global function and this addresses the previous version issue #1. The new call-back or derivation mechanisms for open() addresses the previous version issues #2 and #4. Both strategies are very powerful as global-functions, function-objects or derived-classes can be easily integrated into the framework / libraries adding great versatility without sacrificing the general abstract interface.

The pending issue #3 about  write() is more delicate. The problem could be mitigated by making the function accept just std::string message to be printed, which could be previously formatted using standard stream formatting techniques. But this work-around adds a lot of verbosity to the program which programmers are not fond of. The versatility of printf()-like functions is tremendously appealing to programmers, but at the price of being error-prone to say the least. The best solution would be if the C++ STL officially supported up-to-date variadic printf()-like function templates that could approach the good behavior and flexibility of the legacy printf()-like functions, allied to the natural safety of improved type-checking of C++. But it seems that this will be left for third-parties or individual / isolated efforts.

The pending issue #5 would require extensive redesign and rewrite and could completely abolish pending issue #3. Nevertheless, as logging activity is timestamp-line-record-based, extra care would be necessary to be devised (if possible or viable at all) for providing line-record delimiters within series of chained put-to stream operators. This extra-care trade-off would favor not pursuing a solution to pending issue #5 in favor of #3. If I get some time off I plan to experiment with some coding artifacts for better assessing these issues on each of the strategies.