Как NSLog в файл - программирование
Подтвердить что ты не робот

Как NSLog в файл

Можно ли писать каждый NSLog не только в консоль, но и в файл? Я хочу подготовить это без замены NSLog на someExternalFunctionForLogging.

Это будет реальная проблема для замены всех NSLog. Может быть, есть возможность анализировать данные с консоли или ловить сообщения?

4b9b3361

Ответ 1

Вариант 1: используйте ASL

NSLog выводит журнал в ASL (версия syslog для Apple) и консоль, то есть он уже записывает файл на вашем Mac, когда вы используете симулятор iPhone. Если вы хотите прочитать это, откройте приложение Console.app и введите имя своего приложения в поле фильтра. Чтобы сделать то же самое на своем устройстве iPhone, вам нужно будет использовать API ASL и сделать некоторое кодирование.

Вариант 2: запись в файл

Скажем, вы работаете на симуляторе, и вы не хотите использовать Console.app. Вы можете перенаправить поток ошибок в файл по своему вкусу, используя freopen:
freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
Подробнее см. пояснение и образец проекта.

Или вы можете переопределить 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];
}

и добавьте эту строку ниже fprintf в функцию _Log:

append(msg);

Запись файла также работает на вашем устройстве iPhone, но файл будет создан в каталоге внутри него, и вы не сможете получить доступ, если вы не добавите код, чтобы отправить его обратно на ваш Mac, или покажите его на просмотрите внутри своего приложения или используйте iTunes для добавления каталога документов.

Ответ 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);
}

Swift:

func redirectLogToDocuments() {

    let allPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    let documentsDirectory = allPaths.first!
    let pathForLog = documentsDirectory.stringByAppendingString("/yourFile.txt")

    freopen(pathForLog.cStringUsingEncoding(NSASCIIStringEncoding)!, "a+", stderr)
}

После выполнения этого метода весь вывод, сгенерированный NSLog (ObjC) или print (Swift), будет перенаправлен в указанный файл. Чтобы открыть сохраненный файл Organizer, просмотрите файлы приложений и сохраните Application Data где-нибудь в вашей файловой системе, а не просто перейдите в папку Documents.

Ответ 3

Перевела ответ 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 > Devices > Выберите ваше приложение > InfoWheelButton > загрузить контейнер. Просмотр файла с помощью finder: щелкните правой кнопкой мыши в файле > показать содержимое пакетa > appdata > документы > И там файлы

Ответ 4

Я нашел простейшее решение проблемы: Запись в файл на 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

Ответ 5

Ok! во-первых, я хочу поблагодарить Эвана-Мулавского. Вот мое решение, возможно, это будет полезно для кого-то:

В 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);
}

а затем используйте "replace for all" NSLog → logThis. Этот код адаптирован для моего приложения. Он может расширяться для разных нужд.


Спасибо за помощь.

Ответ 6

Это то, что я использую и хорошо работаю:

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; 
}

Ответ 7

Swift 2.0:

Добавьте их в Appdelegate doneFinishLaunchWithOptions.

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, выберите путь, щелкните правой кнопкой мыши, выберите "Службы-Повторное воспроизведение в Finder" и откройте файл console.log

Ответ 8

Я немного поработал с ответом Элвина Джорджа.

Чтобы контролировать размеры файлов журнала, я реализовал (быстро и грязно) решение "10 поколений файлов журналов" и добавил func для их удаления позже

Каждый раз, когда приложение запускается, оно генерирует новый файл журнала с индексом "0". Выходной файл будет переименован с индексом выше, чем раньше. Индекс "10" будет удален.

Итак, каждый старт дает вам новый файл журнала, максимум 10 поколений

Не может быть самым изящным способом сделать это, но работает для меня в течение последних недель очень хорошо, так как мне нужно какое-то давнее вхождение в журнал "с мака"

  // -----------------------------------------------------------------------------------------------------------
  // 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)")
            }
        }
    }
}