NSLog on devices in iOS 10 / Xcode 8 seems to truncate? Why?

17,793

Solution 1

A temporary solution, just redefine all NSLOG to printf in a global header file.

#define NSLog(FORMAT, ...) printf("%s\n", [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);

Solution 2

In iOS 10 & Xcode 8, Apple switched from the good old ASL (Apple System Log) to a new logging system called Unified logging. NSLog calls are in fact delegating to new os_log API's. (source: https://developer.apple.com/reference/os/logging):

Important

Unified logging is available in iOS 10.0 and later, macOS 10.12 and later, tvOS 10.0 and later, and watchOS 3.0 and later, and supersedes ASL (Apple System Logger) and the Syslog APIs. Historically, log messages were written to specific locations on disk, such as /etc/system.log. The unified logging system stores messages in memory and in a data store, rather than writing to text-based log files.

And

Important

Log message lines greater than the system’s maximum message length are truncated when stored by the logging system. Complete messages are visible when using the log command-line tool to view a live stream of activity. Bear in mind, however, that streaming log data is an expensive activity.

The "system’s maximum message length" limitation is revealed in the SDK's header to be 1024 characters for formatted variables, as noted by @Hot_Leaks (source: <os/log.h>):

/*!  
 * @function os_log  
 *   
 * ...  
 *  
 * There is a physical cap of 1024 bytes per log line for dynamic content,  
 * such as %s and %@, that can be written to the persistence store.  
 * All content exceeding the limit will be truncated before it is  
 * written to disk.  
 *
 * ... 
 *
 */  
#define os_log(log, format, ...)    os_log_with_type(log, OS_LOG_TYPE_DEFAULT, format, ##__VA_ARGS__)

Since the buffer size limitation seems to be hard-coded into libsystem_trace.dylib, I don't see a way around it but to print a string literal instead of a formatted variable (%@), or split the formatted string variables to < 1024 strings.

printf will work during debugging, since the debugger (Xcode) shows the process's out / error streams, but it will not be sent to the device log itself. This means that xfdai's solution will not help you when using other log applications such as macOS's Console App, or with issue's emerging on non-debugged applications (such as AppStore application running on customer's device).


Extending xfdai's answer to deployed applications

In deployed applications / non-debug builds, there's no way to see either NSLogs or printfs.

The only way to have messages printed directly to the device log (which can be accessed using Xcode -> Window -> Devices, mac's Console App or 3rd party utilities such as deviceconsole) is calling os_log API's (which is the successor of ASL used since iOS 10).

Here's a global header file I'm using to redefine NSLog as a call to _os_log_internal on iOS 10:

#ifndef PrefixHeader_pch
#define PrefixHeader_pch

#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#endif

#import <os/object.h>
#import <os/activity.h>

/*
 *  System Versioning Preprocessor Macros
 */

#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

// os_log is only supported when compiling with Xcode 8.
// Check if iOS version > 10 and the _os_log_internal symbol exists,
// load it dynamically and call it.
// Definitions extracted from #import <os/log.h>

#if OS_OBJECT_SWIFT3
OS_OBJECT_DECL_SWIFT(os_log);
#elif OS_OBJECT_USE_OBJC
OS_OBJECT_DECL(os_log);
#else
typedef struct os_log_s *os_log_t;
#endif /* OS_OBJECT_USE_OBJC */

extern struct os_log_s _os_log_default;

extern __attribute__((weak)) void _os_log_internal(void *dso, os_log_t log, int type, const char *message, ...);

// In iOS 10 NSLog only shows in device log when debugging from Xcode:
#define NSLog(FORMAT, ...) \
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {\
    void(*ptr_os_log_internal)(void *, __strong os_log_t, int, const char *, ...) = _os_log_internal;\
    if (ptr_os_log_internal != NULL) {\
        _Pragma("clang diagnostic push")\
        _Pragma("clang diagnostic error \"-Wformat\"")\
        _os_log_internal(&__dso_handle, OS_OBJECT_GLOBAL_OBJECT(os_log_t, _os_log_default), 0x00, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);\
        _Pragma("clang diagnostic pop")\
    } else {\
        NSLog(FORMAT, ##__VA_ARGS__);\
    }\
} else {\
    NSLog(FORMAT, ##__VA_ARGS__);\
}

#endif /* PrefixHeader_pch */

Solution 3

It's an iOS 10 only "feature". Use this instead:

printf("%s", [logString UTF8String]);

Solution 4

You can use this method. Split every 800 chars. Or can be set. NSLOG i think truncate every 1000 chars. If string is less than 800 will use a simple NSLog. This is useful for Json long strings and uses the console. printf uses Xcode debug window not the console.

    -(void) JSLog:(NSString*)logString{

            int stepLog = 800;
            NSInteger strLen = [@([logString length]) integerValue];
            NSInteger countInt = strLen / stepLog;

            if (strLen > stepLog) {
            for (int i=1; i <= countInt; i++) {
                NSString *character = [logString substringWithRange:NSMakeRange((i*stepLog)-stepLog, stepLog)];
                NSLog(@"%@", character);

            }
            NSString *character = [logString substringWithRange:NSMakeRange((countInt*stepLog), strLen-(countInt*stepLog))];
            NSLog(@"%@", character);
            } else {

            NSLog(@"%@", logString);
            }

    }

Solution 5

On iOS 10:

  1. printf() works inside Xcode's console but doesn't work on the device's console log.
  2. NSLog truncates in both places.

What I'm doing for now is splitting my NSLog strings into lines and logging each line individually.

- (void) logString: (NSString *) string
{
    for (NSString *line in [string componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]])
    {
        NSLog(@"%@", line);
    }
}

This works on the console, but isn't easy to read.

Share:
17,793
iPeta
Author by

iPeta

Updated on June 17, 2022

Comments

  • iPeta
    iPeta almost 2 years

    Why the console output shows incomplete in Xcode 8 / iOS 10?

    enter image description here

  • Deepak Sharma
    Deepak Sharma over 7 years
    What if I need to print an NSDictionary ? This is crazy.
  • pir800
    pir800 over 7 years
    @DeepakSharma, the solution provided by xfdai works for NSDictionaries.
  • d00dle
    d00dle over 7 years
    This is a great answer. Confirming it's not a bug.
  • ishahak
    ishahak over 7 years
    In my opinion this is a poor implementation. Instead of calling these 'if' statements and variable assignment on every call to NSLog, you should better test them once on startup and set their result into global variables that you can refer to in your macro
  • Elist
    Elist over 7 years
    @ishahak my purpose was to demonstrate the use of the os_log API. You are welcome to edit the code as you see fit.
  • Ankit
    Ankit over 7 years
    Worked for me also :)
  • AndrewJC
    AndrewJC about 7 years
    This works great if you need to get over the 1024 character limit in the device console log. Thanks!
  • Carl Hine
    Carl Hine over 6 years
    My NSLog output was truncating my serialised JSON NSData. Quite annoying. This worked for me too. I'd just suggest taking the semi-colon off the end if pasting in to the top of you .m file.
  • matt bezark
    matt bezark about 6 years
    and if you want the date you can do something like this: printf("%s %s\n", [[[NSDate date] description] UTF8String], [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String])