Как NSLog в файл


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

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

9 52

9 ответов:

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

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

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

допустим, вы работаете на симулятор и вы не хотите использовать консоль.приложение. Вы можете перенаправить поток ошибок в файл по своему вкусу с помощью 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 для добавления каталога документов.

есть легче подход. Вот метод, который перенаправляет NSLog вывод в файл в приложении . Это может быть полезно, если вы хотите протестировать свое приложение за пределами своей студии разработки, отключенной от вашего 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 где-то в файловой системе, чем просто найдите .

перевел ответ Яакла на Свифт, разместив его здесь в любом случае кто-то нуждается в нем так же

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

Я нашел самое простое решение проблемы: вход в файл на iPhone . Нет необходимости менять какой-либо код NSLog или изменять сам регистратор, просто добавьте эти 4 строки в свои didFinishLaunchingWithOptions и убедитесь, что в ваших настройках сборки live release не будет активирован (я добавил флаг 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

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

в 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. Этот код адаптирован для моего приложения. Его можно расширить для различных нужд.


Thnks для помощи.

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

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

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
}

доступ к консоли.журнал :

когда путь журнал будет распечатан на Лога в Xcode, выберите путь, щелкните правой кнопкой мыши, выбрать сервис - разгадывайте в Finder и откройте файл консоли.журнал

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

чтобы держать размеры файлов журнала под контролем я реализовал (быстрый и грязный) решение "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)")
            }
        }
    }
}

Swift 4 version

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