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?
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.
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.
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
.
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
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
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;
}
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.
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.
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
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)")
}
}
}
}