views:

66

answers:

3

Suppose you have the code of a Cocoa app which logs its own messages through NSlogs and printfs to the Console output. My goal is to redirect all this output into a separate NSWindow in a NSView.

How can I achieve this in a way that

  • minimizes the amount of code to rewrite
  • makes it possible to revert back
  • maximizes the reuse of written code

(the usual software engineering guide lines)?

+1  A: 

How about writing your own log method that informs some controller of the log message (so it can place it into your (NSTextView?) view, then calls NSLog() in turn?

Joshua Nozzi
I should rewrite much of the code to substitute all the logging, well a macro could ease this work I guess
rano
+1  A: 
  1. Soon after your program starts use the dup(2) call to make duplicate File Descriptors for fd 1 (stdout) and 2 (stderr). Save the returned values for later.

  2. Call close(2) on FD 1 and FD 2.

  3. Call openpty(2) twice. The first master returned should be FD 1 (because it is the first avaiable FD), and the second master should be 2. Save the two slave FDs for later. Don't worry about saving the name parameter. Now whenever your program printf(2) to stdout, or NSLogs to stderr the data will be written to your slave FDs.

  4. Now you must choose wether you want to poll the slave FDs or setup a signal when there is data to be read.

For polling use an NSTimer. In your timer use select(2) on your two slave FDs to see if they have data. If they do read(2) it and then output it to your window. You can also get the two slave FDs to use non blocking IO (use fcntl(2) to F_SETFL the slave FDs to O_NONBLOCK). Then you don't need select(2), you just read(2) and it will return zero if there is nothing to read.

For signaling, use fcntl(2) to F_SETFL the slave FDs to O_ASYNC. Then use signal(3) to install a signal handler for SIGIO. When your signal handler is called use one of the two methods I describe in the polling section.

If at run time you want to discard all these changes and set everything back to normal do this:

  1. Call close(2) on FD 1, and FD 2.

  2. Call dup(2) on the two FDs saved from step 1 in the first section, above. Do the dup(2) in the correct order so stdout uses FD 1 and stderr uses FD 2.

Now anything written to stdout and stderr will go to the original FDs.

tlindner
+1 for a very detailed answer
rano
What about `freopen(3)` ?
rano
freopen(3) is useless with regard to your question.
tlindner
I though about this kind of solution, use freopen(3) to write on another filestream and read from that, I'll write it into an answer later
rano
A: 

Here is the solution I came up with:

  1. Create an NSWindowController that has a NSTextView as an outlet (let's call this A)
  2. Create a Singleton class (let's call this B) that encapsulates an A object and provides some methods for sending strings to A (which appends it to its NSTextView) by reading a file (which contains all the loggings) using readInBackgroundAndNotify from NSFileHandle. When notified it calls the appending method. It has a method for starting the logging on file as well, which uses freopen(3) to redirect some stream (stderr and stdout atm) to a file in append mode.
  3. In the project just call the starting logging method of B (no needs of instantiation, but I guess it really does not matter) after importing it.

This solution was created considering both Joshua Nozzi's answer and tlindner's one, and combines them. I have and encapsulated solution that respects the three requests in the question (I have to add only a line of code, I can revert back easily and I can use this solution in other apps too). I noticed that maybe sometimes it can be wrong to have an NSWindowController encapsulated this way (whereas all the other ones are managed by some super-controller).

I finally opted for the file solution since it is very easy to implement and more Cocoa-like than tlindner's one. Also it gives the opportunity to have a logging file that persists on the disk. But of course I may have missed something, point that to me in the comments please ^^

rano
FILE *myStdoutFile = freopen("myLogFile","w",stdout); That should work. But the only way to revert back is to dup(2) the orginal file descriptor.
tlindner
Actually, you'll have to do FILE *myStdoutFile = freopen("myLogFile","rw",stdout); You'll have to be careful about the file position. When something is written to the file the position will be marked at then end, so you have to set it back prior to reading.
tlindner
I am doing freopen("myLogFile","a",stdout)
rano