Logging to multiple LogTargets?

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Das Gurke
Earned a small fee
Earned a small fee
Posts: 17
Joined: Fri Aug 08, 2008 2:47 pm

Logging to multiple LogTargets?

Post by Das Gurke » Sat Sep 20, 2008 6:39 pm

I am currently a bit puzzled to find out if there is a "wxWidgets way" to log to multiple targets or if I would have to derive from wxLog myself to implement the desired behaviour.

The basic thing I want to do is to log everything at (potentially) three places: a file, a window and a C++ stream. In fact that stream and window probably wont co-exist, but if people would really like to open the (wxWidgets wrapped) console of the application they should not need to "switch" windows if they want to see the results of the commands they just executed.

It seemed to me that wxLogPassThrough or wxLogChain is the solution of my problem, but I cant quite make head nor tales out of the documentation.

If everything failes I will simply derive my own class form wxLog and add two methods like addTarget(wxLog*) and removeTarget(wxLog*). But I am not keen in reinventing the wheel =)

mc2r
wxWorld Domination!
wxWorld Domination!
Posts: 1195
Joined: Thu Feb 22, 2007 4:47 pm
Location: Denver, Co
Contact:

Post by mc2r » Sat Sep 20, 2008 7:00 pm

take a look at wxLogChain

-Max

Das Gurke
Earned a small fee
Earned a small fee
Posts: 17
Joined: Fri Aug 08, 2008 2:47 pm

Post by Das Gurke » Sat Sep 20, 2008 8:34 pm

I now tried a little and came up with somethin like this:

Code: Select all

// Initialising a persistant logging to file
mLogfile = fopen("some.log", "w");
wxLog* mStandardLog = new wxLogStderr(mLogfile);
wxLog::SetActiveTarget(mStandardLog);

// A "1" appears in the logfile
wxLogMessage(_T("1"));

// A window with a TextControl is added and should also receive log messages
mLogInstance = new wxLogChain(new wxLogTextCtrl(mLogControl));

// A "2" appears in both places
wxLogMessage(_T("2"));

// That windows is closed again
delete wxLog::SetActiveTarget(mStandardLog);
wxLogMessage(_T("3"));
The logging of the "3" causes the app to crash. If I pass NULL instead of the Standard Log instead the whole program keeps running, but shows those annoying logwindow popups.

I also tried "only" deleting the mLogInstance (as I thought SetActiveTarget() was called by anybody then me, but the way above is the way the documentation hints) and the program still crashes.

So how would I set up another Logging Target without "losing" the old one and without crashing?

mc2r
wxWorld Domination!
wxWorld Domination!
Posts: 1195
Joined: Thu Feb 22, 2007 4:47 pm
Location: Denver, Co
Contact:

Post by mc2r » Sat Sep 20, 2008 8:45 pm

Maybe either wxLogChain::DetachOldLog or wxLogChain::GetOldLog will help.


Or

Code: Select all

mLogInstance->SetLog(NULL); // This sets the active traget to NULL but keeps logging to the previous target.
wxLogMessage(_T("3"));
delete wxLog::SetActiveTarget(mStandardLog); // Do this when logging is done.
-Max

Das Gurke
Earned a small fee
Earned a small fee
Posts: 17
Joined: Fri Aug 08, 2008 2:47 pm

Post by Das Gurke » Sat Sep 20, 2008 9:00 pm

If I only call

Code: Select all

mLogInstance->SetActiveTarget(NULL);
the program will close correctly but as far as I see will also suffer from a memory leak AND stops logging to the file :( And this does also not quite help:

Code: Select all

delete wxLog::SetActiveTarget(mLogInstance->GetOldLog());
To me this looks like as if the old log is destroyed somehow, because wxLog::SetActiveTarget is called somewhere.

Das Gurke
Earned a small fee
Earned a small fee
Posts: 17
Joined: Fri Aug 08, 2008 2:47 pm

Post by Das Gurke » Mon Sep 22, 2008 8:30 am

I just recognized that even my "last chance" approach won't work, as a solution like:

Code: Select all

	void wxMultipleTargetLog::DoLog(wxLogLevel level, const wxChar *szString, time_t t)
	{
		for (LogTargets::iterator it = mLogTargets.begin(); it != mLogTargets.end(); ++it)
		{
			(*it)->DoLog(level, szString, t);
		}
	}

	void wxMultipleTargetLog::DoLogString(const wxChar *szString, time_t t)
	{
		for (LogTargets::iterator it = mLogTargets.begin(); it != mLogTargets.end(); ++it)
		{
			(*it)->DoLog(0, szString, t);
		}
	}
Would need to call the DoLog Methods, which are marked as "protected". So I will have to continue fiddling around with the LogChain :(

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Tue Sep 23, 2008 8:59 pm

LogChain is your answer. I have two logs that I continually monitor... one is a file and the other is a "log window" that I made.

1) My log window is called LogFrame (just a derived wxFrame with a big multi-line text ctrl.)

2) I create a LogFrame, then assign the active target to the logtarget like this: (EDIT: The textctrl in my LogFrame is called Log)

Code: Select all

	LogFrame *log = new LogFrame(NULL);
	wxLogTextCtrl *logTarget = new wxLogTextCtrl(log->Log);
	wxLog::SetActiveTarget(logTarget);
3) Now I want to add the "stderr" to the log chain (I redirect stderr to file since I'm only programming for windows.... ANY valid log type will work here.)

Code: Select all

    cerrLog = new wxLogChain(new wxLogStderr);
    wxLog::SetTimestamp("%m/%d/%Y %H:%M:%S");
4) When I want to stop writing to the file, I use

Code: Select all

wxLog *tmpLogger = cerrLog->GetOldLog();
cerrLog->DetachOldLog();
delete cerrLog;
wxLog::SetActiveTarget(tmpLogger);
What this does is first get a pointer to the old log target (if you notice it is a local pointer... so it is already out of scope). Next, I detach the old log target so that when I delete the LogChain it won't go away. And lastly, reset the active target to the old target.

Hopefully this will help you. And hopefully I didn't skip anything as I did kinda strip away my application so I could just show this.
Last edited by jmason1182 on Wed Sep 24, 2008 2:02 pm, edited 1 time in total.
John A. Mason
Midland, TX

Das Gurke
Earned a small fee
Earned a small fee
Posts: 17
Joined: Fri Aug 08, 2008 2:47 pm

Post by Das Gurke » Wed Sep 24, 2008 1:27 pm

Thanks a lot for your detailed explanation! I will have a look if this works for me =)

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Thu Apr 23, 2009 10:06 pm

I hate when I don't finish publishing something so if I myself go through and search for it later on I can't find it. SO, I'm posting this for myself (but other people might benefit from it too.) It is just a complete example of what I talked about earlier.

Again, my wxCLogFrame class is just a wxFrame with a big text control. That's all. Nothing fancy. I don't show it because I want the end user to be able to see the session log by going Help->Show Log and then my application calls wxCLogFrame::Show.

And lastly, the file redirect is only good for windows. If you are in linux then you have a stderr redirection in whatever command shell you wish. I wish my project could be a linux project but my office won't let me get a linux machine! (darn) Anyway... To the code.


myapp.h

Code: Select all

...
//Include the custom log window (this is a single wxFrame with a sizer that makes a
//multiline text control called "log".) The "log" wxTextCtrl is a public member for easy access to the log functionality. That's it.
//We just don't show the custom log window except if called from a menu in the main frame of the application.
#include "logframe.h"

class MyApp : public wxApp {
		private:
			wxLogChain *cerrLog;
			wxCLogFrame *log;

		public:
			bool OnInit();
			int OnExit();
	};
myapp.cpp

Code: Select all

...
#include "myapp.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit() {

	//Setup our logging
	//Create a custom log window...
	wxCLogFrame*log = new wxCLogFrame(NULL);
	wxLogTextCtrl *logTarget = new wxLogTextCtrl(log->Log);
	wxLog::SetActiveTarget(logTarget);

	//And now we use a log chain to chain into it a stderr log
	//this will also mean that cerrLog will take care of deleting log and logTarget
    cerrLog = new wxLogChain(new wxLogStderr);
    wxLog::SetTimestamp("%m/%d/%Y %H:%M:%S");

	//Redirect output for logging/errors from stderr to error log file
	#ifdef __WXMSW__
		//Check the file's size to keep it below max size
		if(wxFileExists("errors.log")) {
			wxFile *checkFile = new wxFile();
			if(checkFile->Open("errors.log",wxFile::read)){
				if(checkFile->Length()>1024){
					//file is too big... so move it and start over
					checkFile->Close();
					wxRenameFile("errors.log","errors.oldlog",true);
				}
			} else {
				//an error with the file... so remove it and start over
				wxRemoveFile("errors.log");
			}
			delete checkFile;
		}
        std::freopen("errors.log", "a", stderr);
    #endif

    //...(create main frame, rest of application, etc)...

    return true;
}

int MyApp::OnExit() {

    //The beauty of wxWidgets is it will close it all down if we just get rid of the 
    //custom frame we made to hold the text control.
    log->Destroy();
    //The cerrlog chain will be taken care of automatically.

    //But we do need to stop redirecting stderr! 
    #ifdef __WXMSW__
        std::fclose(stderr);
    #endif

    return 0;
}



Note: This is just an example. I took out all the details of my application because they don't apply. However, I may have made a typo as I "snipped" this. So if you notice a mistake, just let me know so I can make sure this is a true "resource" post. Enjoy.
John A. Mason
Midland, TX

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Fri Apr 24, 2009 9:35 pm

One correction to above code:

My wxCLogFrame (I plan to post it itself as a library inside of the Code Dump forum later) has to be stored inside of your main frame. AND when your main frame closes (in the OnClose method) you have to Destroy the log frame. If you wait until after the main frame closes, it is too late and it will hang. Doh!

So create a static member (you can play with that) that is a pointer to a wxCLogFrame (say we call it log for simplicity). Then in your main frame, call log->Destroy() and omit the log->Destroy() in the OnExit of the main application. Also, after you call log-Destroy(), you need reset your wxLog active target or else any other wxLog calls will crash (since you just destroyed part of the chain!).

Oh, and that means you don't have to have a class member of the application called log... it can be a local variable in the OnInit method. Just be sure to pass that local variable or assign it to your main frame so that the main frame window gets it.

If this is as clear as mud, I'll post a full example, the library of wxCLogFrame, etc. in the code dump forum here. And yes, I'll link from here to it as well.
John A. Mason
Midland, TX

Post Reply