ios 如何将 NSLog 登录到文件中

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7271528/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:21:28  来源:igfitidea点击:

How to NSLog into a file

iosiphoneobjective-c

提问by Vov4yk

Is it possible to write every NSLognot only into console, but into a file too? I want to prepare this without replacing NSLoginto someExternalFunctionForLogging.

是否可以NSLog不仅将每个写入控制台,还可以写入文件?我想准备这个而不替换NSLogsomeExternalFunctionForLogging.

It will be real problem to replace all NSLog. Maybe there is possibility for parsing data from console or catching messages?

全部更换将是真正的问题NSLog。也许有可能从控制台解析数据或捕获消息?

回答by Jano

Option 1: Use ASL

选项 1:使用 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.

NSLog 将日志输出到 ASL(Apple 的 syslog 版本)和控制台,这意味着当您使用 iPhone 模拟器时,它已经写入 Mac 中的文件。如果您想阅读它,请打开应用程序 Console.app,然后在过滤器字段中输入您的应用程序名称。要在 iPhone 设备上执行相同操作,您需要使用 ASL API 并进行一些编码。

Option 2: write to a file

选项 2:写入文件

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 projectfor details.

假设您在模拟器上运行并且不想使用 Console.app。您可以使用 freopen 将错误流重定向到您喜欢的文件:
freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
有关详细信息,请参阅此说明和示例项目

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

或者您可以使用宏使用自定义函数覆盖 NSLog。例如,将此类添加到您的项目中:

// 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:

并将其导入项目范围,将以下内容添加到您的<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:

现在,对 NSLog 的每次调用都将替换为您的自定义函数,而无需接触您现有的代码。但是,上面的函数只是打印到控制台。要添加文件输出,请在 _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:

并在 _Log 函数中的 fprintf 下面添加这一行:

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.

文件写入也适用于您的 iPhone 设备,但该文件将在其中的目录中创建,除非您添加代码以将其发送回您的 Mac,或将其显示在您的内部视图中,否则您将无法访问应用程序,或使用 iTunes 添加文档目录。

回答by RaffAl

There is a far easierapproach. Here is the method that redirects NSLogoutput into a file in application's Documentsfolder. This can be useful when you want to test your app outside your development studio, unplugged from your mac.

有一个更简单的方法。这是将NSLog输出重定向到应用程序Documents文件夹中的文件的方法。当你想在你的开发工作室之外测试你的应用程序时,这会很有用,从你的 mac 上拔下。

ObjC:

对象:

- (void)redirectLogToDocuments 
{
     NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     NSString *documentsDirectory = [allPaths objectAtIndex:0];
     NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:@"yourFile.txt"];

     freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}

Swift:

迅速:

// 1. Window > Devices and Simulators
// 2. Select the device
// 3. Select your app and click gear icon
// 4. Download container
// 5. Right click and "view contents"
// 6. Find "yourfile.log" under Downloads
//
// redirectLogToDocuments()

func redirectLogToDocuments() {
  let allPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
  let documentsDirectory = allPaths.first!
  let pathForLog = "\(documentsDirectory)/yourfile.log"
  freopen(pathForLog.cString(using: String.Encoding.ascii)!, "a+", stdout)
}

After executing this method all output generated by NSLog(ObjC) or print(Swift) will be forwarded to specified file. To get your saved file open Organizer, browse application's files and save Application Datasomewhere in your file system, than simply browse to Documentsfolder.

执行此方法后,NSLog(ObjC) 或print(Swift)生成的所有输出都将转发到指定文件。要打开保存的文件Organizer,请浏览应用程序的文件并将其保存Application Data在文件系统中的某个位置,而不是简单地浏览到Documents文件夹。

回答by JaakL

I found the simplest solution to the problem: Logging to a file on the iPhone. No need to change any NSLog code or change logger itself, just add these 4 lines to your didFinishLaunchingWithOptions and make sure in your build settings that live release will not have this activated (I added LOG2FILE flag for this).

我找到了解决这个问题的最简单的方法:Logging to a file on the iPhone。无需更改任何 NSLog 代码或更改记录器本身,只需将这 4 行添加到您的 didFinishLaunchingWithOptions 并确保在您的构建设置中实时发布不会激活它(我为此添加了 LOG2FILE 标志)。

#ifdef LOG2FILE
 #if TARGET_IPHONE_SIMULATOR == 0
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *logPath = [documentsDirectory stringByAppendingPathComponent:@"console.log"];
    freopen([logPath cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
 #endif
#endif

回答by AnitaD

Translated the answer of JaakL to Swift, posting it here in any case someone else needs it as well

将 JaakL 的答案翻译成Swift,在任何其他人也需要它的情况下张贴在这里

Run this code somewhere in your app, from that moment it stores all NSLog() output to a file, in the documents directory.

在您的应用程序中的某处运行此代码,从那一刻起,它将所有 NSLog() 输出存储到文档目录中的文件中。

let docDirectory: NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] as NSString
let logpath = docDirectory.stringByAppendingPathComponent("YourFileName.txt")
freopen(logpath.cStringUsingEncoding(NSASCIIStringEncoding)!, "a+", stderr)

Extra: How to find the log-file with Xcode:
You can simply acces the log from Xcode: Windows > Devices > Choose your app > InfoWheelButton > download container. View the file with finder: click right mouse button on file > show package content > appdata > documents > And there the files are

额外:如何使用 Xcode 查找日志文件:
您可以简单地从 Xcode 访问日志:Windows > 设备 > 选择您的应用程序 > InfoWheelButton > 下载容器。使用查找器查看文件:在文件上单击鼠标右键 > 显示包内容 > appdata > 文档 > 文件在那里

回答by Vov4yk

Ok! firstly, I want to thank Evan-Mulawski. Here is my solution, maybe it will be helpful for someone:

好的!首先,我要感谢 Evan-Mulawski。这是我的解决方案,也许对某人有帮助:

In AppDelegate I add Function:

在 AppDelegate 中,我添加了函数:

void logThis(NSString* Msg, ...)
{   
    NSArray* findingMachine = [Msg componentsSeparatedByString:@"%"];
    NSString* outputString = [NSString stringWithString:[findingMachine objectAtIndex:0]];
    va_list argptr;
    va_start(argptr, Msg);

    for(int i = 1; i < [findingMachine count]; i++) {
        if ([[findingMachine objectAtIndex:i] hasPrefix:@"i"]||[[findingMachine objectAtIndex:i] hasPrefix:@"d"]) {
            int argument = va_arg(argptr, int); /* next Arg */
            outputString = [outputString stringByAppendingFormat:@"%i", argument];      
            NSRange range;
            range.location = 0;
            range.length = 1;
            NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:@""];
            outputString = [outputString stringByAppendingString:tmpStr];
        }
        else if ([[findingMachine objectAtIndex:i] hasPrefix:@"@"]) {
            id argument = va_arg(argptr, id);
            // add argument and next patr of message    
            outputString = [outputString stringByAppendingFormat:@"%@", argument];
            NSRange range;
            range.location = 0;
            range.length = 1;
            NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:@""];
            outputString = [outputString stringByAppendingString:tmpStr];
        }
        else if ([[findingMachine objectAtIndex:i] hasPrefix:@"."]) {
            double argument = va_arg(argptr, double);       
            // add argument and next patr of message    
            outputString = [outputString stringByAppendingFormat:@"%f", argument];
            NSRange range;
            range.location = 0;
            range.length = 3;
            NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:@""];
            outputString = [outputString stringByAppendingString:tmpStr];
        }
        else if ([[findingMachine objectAtIndex:i] hasPrefix:@"f"]) {
            double argument = va_arg(argptr, double);       
            // add argument and next patr of message    
            outputString = [outputString stringByAppendingFormat:@"%f", argument];
            NSRange range;
            range.location = 0;
            range.length = 1;
            NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:@""];
            outputString = [outputString stringByAppendingString:tmpStr];
        }
        else {
            outputString = [outputString stringByAppendingString:@"%"];
            outputString = [outputString stringByAppendingString:[findingMachine objectAtIndex:i]];
        }
    }
    va_end(argptr);
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
    NSString *  filePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:@"logFile.txt"];
    NSError* theError = nil;
    NSString * fileString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&theError];
    if (theError != nil||[fileString length]==0) {
        fileString = [NSString stringWithString:@""];
    }
    fileString = [fileString stringByAppendingFormat:@"\n%@",outputString];
    if(![fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&theError])
    {
            NSLog(@"Loging problem");
    }

    NSLog(@"%@",outputString);
}

and, then use "replace for all" NSLog -> logThis. This code is adapted for my app. It can be expand for different needs.

然后使用“全部替换”NSLog -> logThis。此代码适用于我的应用程序。它可以根据不同的需求进行扩展。



Thnks for help.

谢谢你的帮助。

回答by Fran Sevillano

This is what I use and works well:

这是我使用的并且效果很好:

http://parmantheitroad.com/Redirecting_NSLog_to_a_file

http://parmantheitroad.com/Redirecting_NSLog_to_a_file

Hope it helps.

希望能帮助到你。

I'll just post it here for the sake of the content

我只是为了内容而把它贴在这里

- (BOOL)redirectNSLog { 
     // Create log file 
     [@"" writeToFile:@"/NSLog.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil]; 
     id fileHandle = [NSFileHandle fileHandleForWritingAtPath:@"/NSLog.txt"]; 
     if (!fileHandle) return NSLog(@"Opening log failed"), NO; 
     [fileHandle retain];  

     // Redirect stderr 
     int err = dup2([fileHandle fileDescriptor], STDERR_FILENO); 
     if (!err) return NSLog(@"Couldn't redirect stderr"), NO;  return YES; 
}

回答by A.G

Swift 2.0 :

斯威夫特 2.0:

Add these to Appdelegate didFinishLaunchWithOptions.

将这些添加到 Appdelegate didFinishLaunchWithOptions。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    var paths: Array = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    let documentsDirectory: String = paths[0]
    let logPath: String = documentsDirectory.stringByAppendingString("/console.log")

    if (isatty(STDERR_FILENO) == 0)
    {
        freopen(logPath, "a+", stderr)
        freopen(logPath, "a+", stdin)
        freopen(logPath, "a+", stdout)
    }
    print(logPath)

    return true
}

Accessing console.log :

访问 console.log :

When the log path is printed on Xcode Log Area, select the path, right click, choose Services- Reaveal in Finder and open the file console.log

当Xcode Log Area打印日志路径时,选择路径,右击,在Finder中选择Services-Reaveal,打开文件console.log

回答by Martin S

Swift 4 version

斯威夫特 4 版本

let docDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let logpathe = docDirectory.appendingPathComponent("Logerr.txt")
freopen(logpathe.path.cString(using: .ascii)!, "a+", stderr)
let logpatho = docDirectory.appendingPathComponent("Logout.txt")
freopen(logpatho.path.cString(using: .ascii)!, "a+", stdout)

Output from Swift print()will be in stdout

Swift 的输出print()将在stdout

回答by Hardy_Germany

I worked a little bit with the answer of Alvin George.

我对 Alvin George 的回答做了一些工作。

To keep the log file sizes under control I implemented (quick and dirty) a "10 generations of log files" solution and add a func to delete them later on

为了控制日志文件的大小,我实施了(快速而肮脏的)“10 代日志文件”解决方案,并添加了一个 func 以便稍后删除它们

Every time the app starts, it will generate a new log file with an index "0". The exiting file(s) will be renamed with an index higher than before. Index "10" will be deleted.

每次应用程序启动时,它都会生成一个索引为“0”的新日志文件。现有文件将使用比以前更高的索引重命名。索引“10”将被删除。

So, each start gives you a new log file, maximum 10 generations

所以,每次启动都会给你一个新的日志文件,最多 10 代

Might not be the most elegant way to do it, but works for me during the last weeks very good, as I need some longtime logging "off the mac"

可能不是最优雅的方式,但在过去几周对我来说非常好,因为我需要长时间“关闭 mac”

  // -----------------------------------------------------------------------------------------------------------
  // redirectConsoleToFile()
  //
  // does two things  
  // 1) redirects "stderr", "stdin" and "stdout" to a logfile
  // 2) deals with old/existing files to keep up to 10 generations of the logfiles
  // tested with IOS 9.4 and Swift 2.2
  func redirectConsoleToFile() {

    // Instance of a private filemanager
    let myFileManger = NSFileManager.defaultManager()

    // the path of the documnts directory of the app
    let documentDirectory: String = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!

    // maximum number of logfiles
    let maxNumberOfLogFiles: Int = 10

    // look if the max number of files already exist
    var logFilePath : String = documentDirectory.stringByAppendingString("/Console\(maxNumberOfLogFiles).log")
    var FlagOldFileNoProblem: Bool = true
    if myFileManger.fileExistsAtPath(logFilePath) == true {

        // yes, max number of files reached, so delete the oldest one
        do {
            try myFileManger.removeItemAtPath(logFilePath)

        } catch let error as NSError {

            // something went wrong
            print("ERROR deleting old logFile \(maxNumberOfLogFiles): \(error.description)")
            FlagOldFileNoProblem = false
        }
    }

    // test, if there was a problem with the old file
    if FlagOldFileNoProblem == true {

        // loop over all possible filenames
        for i in 0 ..< maxNumberOfLogFiles {

            // look, if an old file exists, if so, rename it with an index higher than before
            logFilePath = documentDirectory.stringByAppendingString("/Console\((maxNumberOfLogFiles - 1) - i).log")
            if myFileManger.fileExistsAtPath(logFilePath) == true {

                // there is an old file
                let logFilePathNew = documentDirectory.stringByAppendingString("/WayAndSeeConsole\(maxNumberOfLogFiles - i).log")
                do {

                    // rename it
                    try myFileManger.moveItemAtPath(logFilePath, toPath: logFilePathNew)

                } catch let error as NSError {

                    // something went wrong
                    print("ERROR renaming logFile: (i = \(i)), \(error.description)")
                    FlagOldFileNoProblem = false
                }
            }
        }
    }

    // test, if there was a problem with the old files
    if FlagOldFileNoProblem == true {

        // No problem so far, so try to delete the old file
        logFilePath = documentDirectory.stringByAppendingString("/Console0.log")
        if myFileManger.fileExistsAtPath(logFilePath) == true {

            // yes, it exists, so delete it
            do {
                try myFileManger.removeItemAtPath(logFilePath)

            } catch let error as NSError {

                // something went wrong
                print("ERROR deleting old logFile 0: \(error.description)")
            }
        }
    }

    // even if there was a problem with the files so far, we redirect
    logFilePath = documentDirectory.stringByAppendingString("/Console0.log")

    if (isatty(STDIN_FILENO) == 0) {
        freopen(logFilePath, "a+", stderr)
        freopen(logFilePath, "a+", stdin)
        freopen(logFilePath, "a+", stdout)
        displayDebugString(DEBUG_Others, StringToAdd: "stderr, stdin, stdout redirected to \"\(logFilePath)\"")
    } else {
        displayDebugString(DEBUG_Others, StringToAdd: "stderr, stdin, stdout NOT redirected, STDIN_FILENO = \(STDIN_FILENO)")
    }
}

// -----------------------------------------------------------------------------------------------------------
// cleanupOldConsoleFiles()
//
// delete all old consolfiles
func cleanupOldConsoleFiles() {

    // Instance of a private filemanager
    let myFileManger = NSFileManager.defaultManager()

    // the path of the documnts directory of the app
    let documentDirectory: String = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!

    // maximum number of logfiles
    let maxNumberOfLogFiles: Int = 10

    // working string
    var logFilePath: String = ""

    // loop over all possible filenames
    for i in 0 ... maxNumberOfLogFiles {

        // look, if an old file exists, if so, rename it with an index higher than before
        logFilePath = documentDirectory.stringByAppendingString("/Console\(i).log")
        if myFileManger.fileExistsAtPath(logFilePath) == true {

            // Yes, file exist, so delete it
            do {
                try myFileManger.removeItemAtPath(logFilePath)
            } catch let error as NSError {

                // something went wrong
                print("ERROR deleting old logFile \"\(i)\": \(error.description)")
            }
        }
    }
}