Creating an Application Logfile

I'm an inexperienced C++ novice, coding for a hobby.

Class A and Class B encapsulate my data, and they contain all the public getter and setter functions for the data members. There will eventually be a vector of Bs through which I will need to iterate.

Class C is an encapsulation of a log file for the entire application. The class opens a file of fixed name and location in its constructor, and provides a public member function to write to the log. Again, all fine when I create and write to it in main() (but what a trial to understand how to output a time from Windows in the right format - yeesh).

However, I want to be able to write to that log file from within all instances of Class A and Class B. There must be a way to write to the log file from, for example, multiple Class B objects (non-concurrently), without having to pass an fstream object into every instance on construction or using a global.

By my reasoning, this is not a case for inheritance, Class B : public Class C, because I don't want multiple logs. Is this an occasion for an STATIC fstream variable in Class B or <gasp> a global variable?
Last edited on
You could use the singleton pattern.
Another option would be to pass a pointer of class C to the classes that are supposed to log.
> However, I want to be able to write to that log file from within all instances of Class A and Class B.
> There must be a way to write to the log file from, for example, multiple Class B objects (non-concurrently),
> without having to pass an fstream object into every instance on construction or using a global.

std::clog is a ready made global object which can be used for logging.
To log to a file, set it to use a filebuf as its stream buffer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <fstream>

int main()
{
    // redirect std::clog to write to the log file
    std::filebuf fbuf ;
    fbuf.open( "/tmp/my_logfile.txt", std::ios::out ) ;
    const auto old_buf = std::clog.rdbuf( std::addressof(fbuf) ) ;

    // rest of the program writes log messages using std::clog for example,
    std::clog << "this is a test.\n" << 123456 << '\n' ;

    // before we quit, restore the original std::clog buffer
    std::clog.rdbuf(old_buf) ;
}
Thanks, both of you. Wonderful ideas to consider.

You could use the singleton pattern.
Another option would be to pass a pointer of class C to the classes that are supposed to log.

I'll have to research the singleton pattern. I've seen it before, not sure I completely understand it, but never used it, so I'd have to consider how to implement it for a log file. The question remains how do I pass the singleton to every Class B instance?

I understand the pass-by-pointer approach but wanted to avoid it if I could since this would require changing the signatures of the Class B constructors. However, I can get my head around that one.

std::clog is a ready made global object which can be used for logging.
To log to a file, set it to use a filebuf as its stream buffer.

I understand the code you wrote with a very little digging (http://www.cplusplus.com/reference/iostream/clog/?kw=clog). Do I have this right in that it should work if I encapsulate this in Class C's constructor & destructor, and instantiate a Class C instance from main() to reconfigure the steam. No Class C public write function is required since all log output goes to std::clog directly from within Classes A and B.
> Do I have this right in that it should work if I encapsulate this in Class C's constructor & destructor,
> and instantiate a Class C instance from main() to reconfigure the steam. No Class C public write function
> is required since all log output goes to std::clog directly from within Classes A and B.

Yes.

Here is a somewhat simplified example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include <iomanip>

namespace log
{
    std::string time_stamp()
    {
        const std::time_t t = std::time(nullptr) ;
        std::string str = std::ctime( std::addressof(t) ) ;
        str.pop_back() ;
        return str ;
    }

    struct init_helper // this is the class C that was mentioned
    {
        explicit init_helper( std::string log_file_name )
        {
            // note: std::ios::out will overwrite the log file
            //       use std::ios::app to keep appending to the logfile
            fbuf.open( log_file_name, std::ios::out ) ;
            old_buf = std::clog.rdbuf( std::addressof(fbuf) ) ;
        }

        ~init_helper() { std::clog.rdbuf(old_buf) ; }

        // non copyable, non assignable
        init_helper( const init_helper& ) = delete ;
        init_helper( init_helper&& ) = delete ;
        init_helper& operator= ( const init_helper& ) = delete ;
        init_helper& operator= ( init_helper&& ) = delete ;

        private:
            std::filebuf fbuf ;
            std::streambuf* old_buf ;
    };
}

struct A
{
    void foo( int arg ) const
    {
        // log output from everywhere is sent to std::clog
        std::clog << log::time_stamp() << " void A::foo(" << arg << ")\n" ;
    }
};

struct B
{
    void bar( const char* arg ) const
    {
        // log output from everywhere is sent to std::clog
        std::clog << log::time_stamp() << " void B::bar(" << std::quoted(arg) << ")\n" ;
    }
};

int main()
{
    const char log_file_name[] = "my_log_file.txt" ;

    // instantiate the object that manages the redirection of std::clog output to file
    log::init_helper log_to_file(log_file_name) ;

    A a ;
    B b ;

    a.foo(12345) ;
    b.bar( "hello world!" ) ;

    // destructor of log_to_file will restore the original stream buffer of std::clog
}
Many thanks...you had me at 'yes' :-)

I like what you did to delete the copy and assignment functions. I hadn't thought of that. I also like the time_stamp() abstraction. Nice touch.
Last edited on
I had to make some changes because VS2017 generated a security-related, compile-time error on the use of ctime in the time_stamp helper function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
string time_stamp()
{
	// Get the current time in seconds
	const time_t t = time(nullptr);
	
	// Convert current time to a time structure
	struct tm tmDest;
	int error = localtime_s(&tmDest, &t);

	string time_stamp;
	if (!error)
	{
		// Format the time and save it to a C-style buffer
		// Couldn't figure how to use std::stringstream as an alternative to strftime()
		char buf[26] = { 0 };
		strftime(buf, sizeof(buf), "%T", &tmDest);
		time_stamp = buf;
	}
	else
		time_stamp = "Time Error";	// Consider throwing an exception here instead.

	return time_stamp;
}
Topic archived. No new replies allowed.