NSLog on devices in iOS 10 / Xcode 8 seems to truncate? Why?
IosObjective CXcodeIos10Xcode8Ios Problem Overview
Ios Solutions
Solution 1 - Ios
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 - Ios
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 NSLog
s or printf
s.
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 - Ios
It's an iOS 10 only "feature". Use this instead:
printf("%s", [logString UTF8String]);
Solution 4 - Ios
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 - Ios
On iOS 10:
printf()
works inside Xcode's console but doesn't work on the device's console log.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.
Solution 6 - Ios
Pretty function and line
#define NSLog(FORMAT, ...) printf("%s:%d %s\n", __PRETTY_FUNCTION__,__LINE__,[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String])
with date
#define NSLog(FORMAT, ...) printf("%s %s:%d %s\n", [[[NSDate date] description] UTF8String],__PRETTY_FUNCTION__,__LINE__,[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String])
improve @xfdai answer
Solution 7 - Ios
This doesn't provide a nice output, but prints all necessary information for long logs, even on console.
func Log(_ logString: String?) {
if logString?.isEmpty ?? false { return }
NSLog("%@", logString!)
Log(String(logString!.dropFirst(1024)))
}