For tracking online problems or user flow, custom log is a good solution to solve the problem. The main ideas are as follows:

This article mainly introduces two solutions. The first solution is to customize the Log file to replace the NSLog. The second is to use freopen to redirect the NSLog output to save it.

Customize Log files

Below is the Log class, a switch property, a custom Log format output method, and a Log file writing method. DEBUG directly to the console, release and Log to the file when on

  • Log.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

#define NSLog(frmt,...) [Log logWithLine:__LINE__ method:[NSString stringWithFormat:@"%s", __FUNCTION__] time:[NSDate date] format:[NSString stringWithFormat:frmt, ## __VA_ARGS__]]

@interface Log : NSObject

+ (void)setFileLogOnOrOff:(BOOL)on;
+ (void)logWithLine:(NSUInteger)line
             method:(NSString *)methodName
               time:(NSDate *)timeStr
             format:(NSString *)format;

@end

NS_ASSUME_NONNULL_END
Copy the code
  • Log.m
#import "Log.h" @implementation Log static BOOL _fileLogOnOrOff; + (void)setFileLogOnOrOff:(BOOL)on { _fileLogOnOrOff = on; [Log initHandler]; } + (void)logWithLine:(NSUInteger)line method:(NSString *)methodName time:(NSDate *)timeStr format:(NSString *)format { / / log time formatting NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian]; NSInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitNanosecond; NSDateComponents *comps = [calendar components:unitFlags fromDate:[NSDate date]]; NSString *time = [NSString stringWithFormat:@"%ld-%ld-%ld %ld:%ld:%ld:%@", (long)comps.year, (long)comps.month, (long)comps.day, (long)comps.hour, (long)comps.minute, (long)comps.second, [[NSString stringWithFormat:@"%ld", (long)comps.nanosecond] substringToIndex:2]]; #if DEBUG // DEBUG fprintf(stderr,"%s %s %tu line: %s.\n", [time UTF8String],[methodName UTF8String],line,[format UTF8String]); If (_fileLogOnOrOff) {NSString *logStr = [NSString stringWithFormat:@"[%@]%@ %tu Low % @. \ n ", time, methodName, line, the format]; [self writeLogWithString:logStr]; } #endif} + (void)writeLogWithString:(NSString *)content { Easy to download nsstrings * filePath = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"custom_log.text"]; NSError *error = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; // If there is no if(! [fileManager fileExistsAtPath:filePath]) { [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error]; If (error) {NSLog(@" file write failed errorInfo: %@", error.domain); } } NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath]; [fileHandle seekToEndOfFile]; NSData* stringData = [content dataUsingEncoding:NSUTF8StringEncoding]; [fileHandle writeData:stringData]; // Append [fileHandle synchronizeFile]; [fileHandle closeFile]; } #pragma mark + (void)initHandler {struct sigAction newSignalAction; memset(&newSignalAction, 0,sizeof(newSignalAction)); newSignalAction.sa_handler = &signalHandler; sigaction(SIGABRT, &newSignalAction, NULL); sigaction(SIGILL, &newSignalAction, NULL); sigaction(SIGSEGV, &newSignalAction, NULL); sigaction(SIGFPE, &newSignalAction, NULL); sigaction(SIGBUS, &newSignalAction, NULL); sigaction(SIGPIPE, &newSignalAction, NULL); / / function to be called by the abnormal NSSetUncaughtExceptionHandler (& handleExceptions); } void signalHandler(int sig) {// Prints the crash signal NSLog(@"signal = %d", sig); } void handleExceptions(NSException *exception) { NSLog(@"exception = %@",exception); // Print stack information NSLog(@"callStackSymbols = %@",[exception callStackSymbols]); } @endCopy the code

The 1.1 Log file internally identifies the DEBUG environment. Therefore, directly request the interface with the corresponding ID and enable the local write function based on the background Settings.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // I can put this code in multiple places, put it in here because there's less going into the background, Don't miss the log during the request information [self requestFileLogOnOrOffWithUseId: 12345 complete: ^ (BOOL offOrOn) {/ / is the DEBUG, here also can make synchronization, Using dispatch_semaphore CGD semaphore, generally do not need so extreme. [Log setFileLogOnOrOff: offOrOn];}]; return YES; }Copy the code

1.2 Triggering Log writing

In all the places where you want to write records, import header files and do normal NSLog printing, for example:

- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSLog(@" called %s method ", __func__); } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@" called %s method ", __func__); } - (void)viewDidLoad { [super viewDidLoad]; [self.button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside]; }Copy the code

As long as it is printed, it will be written to the file by default. For example, if the button is not clicked, the crash information will also be recorded:

[2022-2-21 14:28:59:32]-[ViewController viewWillAppear:] -[ViewController viewWillAppear:]

[2022-2-21 14:29:4:15]-[ViewController viewDidAppear:] 27 line: Call -[ViewController viewDidAppear:]. [2022-2-21 14:29:5:19]handleExceptions 83 line: exception = -[ViewController buttonClick]: Unrecognized selector sent to instance 0x7F7adDA07690. [2022-2-21 14:29:5:19]handleExceptions 85 line: callStackSymbols = ( 0 CoreFoundation 0x000000010f38cbb4 __exceptionPreprocess + 242 1 libobjc.A.dylib 0x000000010f240be7 objc_exception_throw + 48 2 CoreFoundation 0x000000010f39b821 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0 3 UIKitCore 0x000000011a57ff90 -[UIResponder doesNotRecognizeSelector:] + 264 4 CoreFoundation 0x000000010f3910bc forwarding + 1433 …… ######1.4 finally is to upload the content of the server to download down can be analyzed.

Second, the freopen

2.1 Principle:

  • The freopen() function is used to redirect file streams, typically stdin, stdout, and stderr to files.

  • Redirection means changing the source or destination of a file stream. Stdout (standard output stream) is destined for the display, and printf() prints the contents of the stream to the display; You can use freopen() to change the destination of stdout to a file (such as output.txt), and then call printf() to output the content to the file instead of the monitor.

  • The prototype freopen() function is:

FILE	*freopen(const char * __restrict, const char * __restrict,
                 FILE * __restrict) __DARWIN_ALIAS(freopen);
Copy the code
Use method: FILE *fp = freopen(" xx.txt ", "r",stdin); // Redirect the standard input stream to xx.txt. That is, read from xx.txt. The second argument (mode) : "r" opens a file for reading. The file must exist. "W" creates an empty file for writing. If the file name is the same as an existing file, the contents of the existing file are deleted and the file is treated as a new empty file. Append "a" to a file. Write operations append data to the end of a file. If the file does not exist, the file is created. "R +" opens a file for updates that can be read or written. The file must exist. "W +" creates an empty file for reading and writing. "A +" opens a file for reading and appending.Copy the code
  • “Parameters”

@return returns a pointer to FILE

The @param parameter specifies the file path, the file access mode, and the stream being redirected

2.2 Whether to enable log collection for a certain user (release& Background):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self requestFileLogOnOrOffWithUseId:12345 complete:^(BOOL offOrOn) { #ifdef DEBUG #else [self redirectNSlogToDocumentFolder];  #endif }]; return YES; }Copy the code
# pragma mark - log collection - (void) redirectNSlogToDocumentFolder {nsstrings * documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSDateFormatter *dateformat = [[NSDateFormatter alloc]init]; [dateformat setDateFormat:@"yyyy-MM-dd-HH-mm-ss"]; // Start time is the file name, NSString *fileName = [NSString stringWithFormat:@"LOG-%@.txt",[dateFormat stringFromDate:[NSDate date]]; NSString *logFilePath = [documentDirectory stringByAppendingPathComponent:fileName]; NSFileManager *defaultManager = [NSFileManager defaultManager]; [defaultManager removeItemAtPath:logFilePath error:nil]; / / will log the input to the file freopen ([logFilePath cStringUsingEncoding: NSASCIIStringEncoding], "a +", stdout); freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr); }Copy the code

2.3 Also triggers 1.2Log write. The Log content is as follows:

2022-02-21 15:03:42.446299+0800 LogDemo[3275:2248894] called -[ViewController viewWillAppear:]

2022-02-21 15:03:42.560687+0800 LogDemo[3275:2248894] called -[ViewController viewDidAppear: 15:05:10. 752340 + 0800 LogDemo [3275-2248894] – [ViewController buttonClick] : Unrecognized selector sent to instance 0x7F96b4107ba0 2022-02-21 15:05:10.760077+0800 LogDemo[3275:2248894] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[ViewController buttonClick]: unrecognized selector sent to instance 0x7f96b4107ba0’ *** First throw call stack: ( 0 CoreFoundation 0x0000000101d86bb4 __exceptionPreprocess + 242 1 libobjc.A.dylib 0x0000000101c3abe7 objc_exception_throw + 48 2 CoreFoundation 0x0000000101d95821 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0 3 UIKitCore 0x0000000107396f90 -[UIResponder doesNotRecognizeSelector:] + 264 4 CoreFoundation 0x0000000101d8b0bc forwarding + 1433 …… ######2.3 Whether to upload and download depends on individual needs.

Iii. Analysis of similarities and differences

3.1 Custom log file can be customized printing format and many other extended functions, but itself is based on macro definition to achieve, so for componentized engineering is not very friendly, invasive. 3.2 Freopen () function writing, original, no dependence, only need to judge the trigger conditions can be; There is a pit is the judgment of disk memory, more disgusting, when using attention can be.

Four, conclusion

The road ahead is long, I see no end, I will search high and low

The author Jane books

The author nuggets

The author making

.End