如何 NSLog 进入文件
Posted
技术标签:
【中文标题】如何 NSLog 进入文件【英文标题】:How to NSLog into a file 【发布时间】:2011-11-08 10:46:41 【问题描述】:是否可以将每个NSLog
不仅写入控制台,还写入文件?我想准备这个而不将NSLog
替换为someExternalFunctionForLogging
。
替换所有NSLog
将是真正的问题。也许有可能从控制台解析数据或捕获消息?
【问题讨论】:
您可以使用 #define 将 NSLog 替换为另一个函数调用。 我试图按照下面的最佳答案进行操作,但这只会弄乱我的项目,并在 NSObjCRuntime.h 和整个 NSobject 等中引发大量解析问题。 【参考方案1】:选项 1:使用 ASL
NSLog 将日志输出到 ASL(Apple 的 syslog 版本)和控制台,这意味着当您使用 iPhone 模拟器时,它已经在写入您 Mac 中的文件。如果您想阅读它,请打开应用程序 Console.app,然后在过滤器字段中输入应用程序的名称。要在您的 iPhone 设备上执行相同操作,您需要使用 ASL API 并进行一些编码。
选项 2:写入文件
假设您在模拟器上运行并且不想使用 Console.app。您可以使用 freopen 将错误流重定向到您喜欢的文件:freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
有关详细信息,请参阅此explanation and sample 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
并在项目范围内导入它,将以下内容添加到您的<application>-Prefix.pch
:
#import "Log.h"
现在对 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];
并在 _Log 函数中的 fprintf 下面添加这一行:
append(msg);
文件写入也适用于您的 iPhone 设备,但该文件将在其中的目录中创建,您将无法访问,除非您添加代码将其发送回您的 Mac,或将其显示在查看您的应用程序内部,或使用 iTunes 添加文档目录。
【讨论】:
您可以使用管理器获取文本文件:选择“设备” - #您的设备# - “应用程序”。选择您的应用程序。您可以在下面的“沙盒中的数据文件”树中看到您的文件(以及其他文件)。点击“下载”。您现在在您的 Mac 上拥有该文件,并且可以右键单击“显示包内容”以浏览到您的文本文件。 我们可以在 NSLog 上创建一个类别吗? 如何在 NSLog 的类上创建一个类别?如果是,您能否建议 NSLog 函数存在的类名是什么?我尝试使用“目标 c 类别文件创建模板”创建 NSObjCRuntime 类别,但它不允许我下一步。请建议我如何覆盖 NSLog ?谢谢。 @PrasadG NSLog 不属于任何类。它是一个 C 函数。要覆盖它,您必须像上面的 #define 一样重新定义它。 当我尝试使用这种方式记录 [对象描述] 时,系统会出错。我不知道为什么。【参考方案2】:有一种更容易的方法。这是将 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);
斯威夫特:
// 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)
执行此方法后,NSLog
(ObjC) 或 print
(Swift) 生成的所有输出都将转发到指定文件。要打开保存的文件Organizer
,请浏览应用程序的文件并将Application Data
保存在文件系统的某个位置,而不是简单地浏览到Documents
文件夹。
【讨论】:
当我在运行应用程序后使用它时,我得到了多个日志文件,为什么会变成这样?我们不能只将它用于单个文件吗? freopen(pathForLog.cStringUsingEncoding(NSASCIIStringEncoding)!, "a+", stderr) 。我们需要 stdout 而不是 stderr 以将所有日志重定向到文件。使用 stderr,只有错误日志会进入文件。 我认为 NSLog 只写入标准错误。 有人可以指导一下,如何清除这个文件,一旦它被复制到剪贴板,以便下次启动应用程序重新开始。 我看到的一件事是,如果您在 Swift 中混合使用 print 和 NSLog,那么它们不会以正确的顺序登录到文件中(打印似乎要晚得多完全)。我的偏好通常是使用 NSLog 来获取时间戳信息。【参考方案3】:我找到了解决问题的最简单方法: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
【讨论】:
奇怪的是,几周后发布与此代码几乎完全相同的代码的答案得到了所有的赞成... 从 Xcode 运行时,更容易登录到 Mac 磁盘上的文件。只需登录"~/console.log"
。然后在终端中输入tail -f ~/console.log
以获得不断更新的输出。【参考方案4】:
将 JaakL 的答案翻译成 Swift,以防万一其他人也需要它
在您的应用程序中的某处运行此代码,从那一刻起,它将所有 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)
补充:如何使用 Xcode 查找日志文件: 您可以简单地从 Xcode 访问日志:Windows > 设备 > 选择您的应用程序 > InfoWheelButton > 下载容器。 使用查找器查看文件:在文件上单击鼠标右键 > 显示包内容 > appdata > 文档 > 文件就在那里
【讨论】:
这不会在 swift 中记录 println() 调用 我们如何在全局范围内捕获 println() 调用? 相同的三行,不同的文件名和标准输出,而不是 freopen 中的标准错误。【参考方案5】:Swift 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)
Swift print()
的输出将在 stdout
中
【讨论】:
【参考方案6】:好的!首先,我要感谢 Evan-Mulawski。 这是我的解决方案,也许对某人有帮助:
在 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);
然后,使用“全部替换”NSLog -> logThis。 此代码适用于我的应用程序。可以根据不同的需求进行扩展。
感谢您的帮助。
【讨论】:
【参考方案7】:这是我使用的并且效果很好:
http://parmanoir.com/Redirecting_NSLog_to_a_file
希望对你有帮助。
为了内容,我就放在这里
- (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;
【讨论】:
【参考方案8】:Swift 2.0:
将这些添加到 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
访问console.log:
在Xcode Log Area打印日志路径后,选择路径,右键,在Finder中选择Services-Reaveal,打开文件console.log
【讨论】:
你为什么要检查isatty(STDERR_FILENO) == 0
?【参考方案9】:
我根据 Alvin George 的答案做了一些工作。
为了控制日志文件的大小,我实现了(快速而肮脏的)“10 代日志文件”解决方案,并添加了一个函数以便稍后删除它们
应用程序每次启动时,都会生成一个索引为“0”的新日志文件。现有文件将使用比以前更高的索引重命名。索引“10”将被删除。
因此,每次启动都会为您提供一个新的日志文件,最多 10 代
可能不是最优雅的方式,但在过去几周对我来说非常好,因为我需要一些长时间的“关闭 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)")
【讨论】:
以上是关于如何 NSLog 进入文件的主要内容,如果未能解决你的问题,请参考以下文章