web-dev-qa-db-fra.com

Comment NSLog dans un fichier

Est-il possible d'écrire chaque NSLog non seulement dans la console, mais également dans un fichier? Je veux préparer ceci sans remplacer NSLog par someExternalFunctionForLogging.

Ce sera un réel problème de remplacer tous les NSLog. Peut-être qu’il est possible d’analyser les données de la console ou d’attraper des messages? 

54
Vov4yk

Option 1: Utiliser ASL

NSLog affiche le journal dans ASL (version de syslog d’Apple) et dans la console, ce qui signifie qu’il écrit déjà dans un fichier de votre Mac lorsque vous utilisez le simulateur iPhone. Si vous voulez le lire, ouvrez l'application Console.app et tapez le nom de votre application dans le champ du filtre. Pour faire la même chose sur votre appareil iPhone, vous devez utiliser l’API ASL et procéder à des opérations de codage.

Option 2: écrire dans un fichier

Supposons que vous utilisez le simulateur et que vous ne voulez pas utiliser Console.app. Vous pouvez rediriger le flux d’erreurs vers un fichier de votre choix à l’aide de freopen:
freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
Voir ceci explication et exemple de projet pour plus de détails.

Vous pouvez également remplacer NSLog par une fonction personnalisée à l'aide d'une macro. Exemple, ajoutez cette classe à votre projet:

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

Et importez-le dans tout le projet en ajoutant ce qui suit à votre <application>-Prefix.pch:

#import "Log.h"

Désormais, chaque appel à NSLog sera remplacé par votre fonction personnalisée sans qu'il soit nécessaire de toucher votre code existant. Cependant, la fonction ci-dessus n’imprime que sur la console. Pour ajouter une sortie de fichier, ajoutez cette fonction au-dessus de _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];
}

et ajoutez cette ligne sous fprintf dans la fonction _Log:

append(msg);

L’écriture de fichier fonctionne également sur votre iPhone, mais le fichier est créé dans un répertoire et vous ne pourrez pas y accéder à moins d’ajouter du code pour le renvoyer sur votre Mac ou de l’afficher dans une vue de votre ordinateur. app, ou utilisez iTunes pour ajouter le répertoire de documents.

86
Jano

Il y a une approche beaucoup plus facile. Voici la méthode qui redirige NSLog la sortie dans un fichier du dossier Documents de l'application. Cela peut être utile lorsque vous souhaitez tester votre application en dehors de votre studio de développement, débranchée de votre 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);
}

Rapide:

func redirectLogToDocuments() {

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

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

Après avoir exécuté cette méthode, toutes les sorties générées par NSLog (ObjC) ou print (Swift) seront transmises au fichier spécifié. Pour ouvrir votre fichier enregistré Organizer, parcourez les fichiers de l’application et enregistrez Application Data quelque part dans votre système de fichiers, puis parcourez simplement le dossier Documents.

73
RaffAl

Traduit la réponse de JaakL en Swift , la publie ici dans tous les cas, quelqu'un d'autre en a aussi besoin

Exécutez ce code quelque part dans votre application, à partir de ce moment, il stocke toutes les sorties NSLog () dans un fichier, dans le répertoire des documents.

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: Comment trouver le fichier journal avec Xcode:
Vous pouvez simplement accéder au journal depuis Xcode: Windows> Périphériques> Choisissez votre application> InfoWheelButton> conteneur de téléchargement . Affichez le fichier avec le Finder: cliquez avec le bouton droit de la souris sur le fichier> affichez le contenu du paquet> appdata> documents> Et là les fichiers sont 

7
AnitaD

J'ai trouvé la solution la plus simple au problème: Se connecter à un fichier sur l'iPhone . Nul besoin de changer le code NSLog ou le journal lui-même, il suffit d’ajouter ces 4 lignes à votre didFinishLaunchingWithOptions et d’être sûr que dans vos paramètres de construction cette version active ne sera pas activée (j’ai ajouté le drapeau LOG2FILE pour cela).

#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
6
JaakL

C’est ce que j’utilise et fonctionne bien:

http://parmanoir.com/Redirecting_NSLog_to_a_file

J'espère que ça aide.

Je vais juste le poster ici pour le contenu

- (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; 
}
3
Fran Sevillano

D'accord! Tout d’abord, je tiens à remercier Evan-Mulawski ……. Voici ma solution, cela aidera peut-être quelqu'un: 

Dans AppDelegate, j'ajoute une fonction:

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

et, ensuite, utilisez "replace for all" NSLog -> logThis. Ce code est adapté à mon application. Il peut être étendu pour différents besoins.


Merci pour l'aide. 

3
Vov4yk

Swift 2.0:

Ajoutez-les à 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
}

Accéder à console.log:  

Lorsque le chemin du journal est imprimé dans la zone du journal Xcode, sélectionnez le chemin, faites un clic droit, choisissez Services- Reaveal dans le Finder et ouvrez le fichier console.log.

2
A.G

Version 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)

La sortie de Swift print() sera dans stdout

1
Martin S

J'ai travaillé un peu avec la réponse d'Alvin George.

Pour garder le contrôle sur la taille des fichiers journaux, j'ai mis en œuvre (rapide et incorrect) une solution "10 générations de fichiers journaux" et ajouté une fonction permettant de les supprimer ultérieurement.

Chaque fois que l'application démarre, elle génère un nouveau fichier journal avec un index "0". Les fichiers existants seront renommés avec un index plus élevé qu'auparavant. L'index "10" sera supprimé.

Ainsi, chaque démarrage vous donne un nouveau fichier journal, maximum 10 générations

Ce n'est peut-être pas la façon la plus élégante de le faire, mais cela fonctionne très bien pour moi au cours des dernières semaines, car j'ai besoin d'une connexion de longue date "hors 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)")
            }
        }
    }
}
0
Hardy_Germany