Redirect Nslog to File in Swift Not Working

Redirect NSLog to File in Swift not working

The absoluteString property of an URL produces an URL string, e.g.


file:///path/to/file.txt

which is not suitable as argument to freopen().
To get the file path as a string, use path instead:

let logPath = dir.appendingPathComponent(file).path

Better, use the dedicated method to pass an URLs path to a system call:

let logFileURL = dir.appendingPathComponent(file)
logFileURL.withUnsafeFileSystemRepresentation {
_ = freopen($0, "a+", stderr)
}

How to NSLog into a file

Option 1: Use ASL

NSLog outputs log to ASL (Apple's version of syslog) and console, meaning it is already writing to a file in your Mac when you use the iPhone simulator. If you want to read it open the application Console.app, and type the name of your application in the filter field. To do the same in your iPhone device, you would need to use the ASL API and do some coding.

Option 2: write to a file

Let's say you are running on the simulator and you don't want to use the Console.app. You can redirect the error stream to a file of your liking using freopen:

freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
See this explanation and sample project for details.

Or you can override NSLog with a custom function using a macro. Example, add this class to your project:

// file Log.h
#define NSLog(args...) _Log(@"DEBUG ", __FILE__,__LINE__,__PRETTY_FUNCTION__,args);
@interface Log : NSObject
void _Log(NSString *prefix, const char *file, int lineNumber, const char *funcName, NSString *format,...);
@end

// file Log.m
#import "Log.h"
@implementation Log
void _Log(NSString *prefix, const char *file, int lineNumber, const char *funcName, NSString *format,...) {
va_list ap;
va_start (ap, format);
format = [format stringByAppendingString:@"\n"];
NSString *msg = [[NSString alloc] initWithFormat:[NSString stringWithFormat:@"%@",format] arguments:ap];
va_end (ap);
fprintf(stderr,"%s%50s:%3d - %s",[prefix UTF8String], funcName, lineNumber, [msg UTF8String]);
[msg release];
}
@end

And import it project wide adding the following to your <application>-Prefix.pch:

#import "Log.h"

Now every call to NSLog will be replaced with your custom function without the need to touch your existing code. However, the function above is only printing to console. To add file output, add this function above _Log:

void append(NSString *msg){
// get path to Documents/somefile.txt
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"logfile.txt"];
// create if needed
if (![[NSFileManager defaultManager] fileExistsAtPath:path]){
fprintf(stderr,"Creating file at %s",[path UTF8String]);
[[NSData data] writeToFile:path atomically:YES];
}
// append
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:path];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:[msg dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
}

and add this line below fprintf in the _Log function:

append(msg);

File writing also works in your iPhone device, but the file will be created in a directory inside it, and you won't be able to access unless you add code to send it back to your mac, or show it on a view inside your app, or use iTunes to add the documents directory.

Redirect print to a File like redirecting NSLog to a file

First, overriding NSLog with a macro is really bad approach. Just use a different function name and use that. Overriding print is even more fragile. Just name it something else. When someone sees NSLog or print in the code, it shouldn't be a trick.

That said, it'll generally work within a given module, as long as you give it the right signature:

public func print(items: Any..., separator: String = "", terminator: String = "\n")

(EDIT: Fixed up the signature to be very explicit on exactly how print does it, rather than just showing the docs.)

You didn't use varargs syntax (...) in your function, which is why it doesn't accept multiple parameters. And if you want it to accept non-Strings (like obj) then you have to accept Any, not just String.

If you just make your own printlog function (or whatever you want to call it), then of course you can use whatever signature is convenient, but if you're going to overload print, you'll need to match its signature and functionality.

Here's the full implementation. This is a terrible way to do it. You should not override print. But this is how Swift allows it to be done (spread across three files just to demonstrate that it's working):

main.swift:

func print(items: Any..., separator: String = "", terminator: String = "\n") {
Swift.print("My print is printing \(items)", separator: separator, terminator: terminator)
}

print("In main!")
file1print()
file2print()

file1.swift:

func file1print() {
print("file1!") // Calls our print
}

file2.swift:

func file2print() {
print("file2!") // Calls our print
}

Again, this is a horrible idea. It is far better, flexible, and clearer to just create a new function. But Swift definitely can do it.

As a note, if your real goal is just to write stdout or stderr to a file, there's no need to override print or NSLog this way. Just reopen your stdout and/or stderr. For example:

let path: NSString = ...;
freopen(path.UTF8String, "a+", stderr)

That would redirect stderr. Use stdout to get stdout. This will work for anything that prints to stdout/err, no matter what module does it.

NSLog() to both console and file

According to the docs, you will need a custom log facility.

You need at a minimum, a function that takes variadic arguments and printd to NSLog and fprintf, something like:

void myLog(NSString* format, ...)
{
va_list argList;
va_start(argList, format);
NSString* formattedMessage = [[NSString alloc] initWithFormat: format arguments: argList];
va_end(argList);
NSLog(@"%@", formattedMessage);
fprintf(myFileDescriptor, "%s\n", [formattedMessage UTF8String]);
[formattedMessage release]; // if not on ARC
}

Or there are Cocoa logging frameworks.

Looking for a Specific Cocoa Logging Framework

Write log content into text file using freopen not working with Swift

 func redirectLogToDocuments() {
let allPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = allPaths.first!
let pathStringComponent = "/" + LoggingConstants.LogFileName + "." + LoggingConstants.LogFileType
let pathForLog = (documentsDirectory as NSString).appending(pathStringComponent)
freopen(pathForLog.cString(using: String.Encoding.ascii)!, "a+", stderr)
}

Use the above method in didFinishLaunching() and after that use NSLog() statements instead of print() and you will get what you want.

Is there a way to capture the output of NSLog on an iPhone when not connected to a debugger?

I'm pretty sure that NSLog() calls will be written to the system console log, so if you connect your iPhone to your computer after being offline, you should be able to look at the console log in XCode Organizer. The only caveat is that the console log is limited in size so older entries may be bumped off if you do a lot of logging.



Related Topics



Leave a reply



Submit