Why the console output shows incomplete in Xcode 8 / iOS 10?
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]);
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 */
os_log
API. You are welcome to edit the code as you see fit. –
Bowman It's an iOS 10 only "feature". Use this instead:
printf("%s", [logString UTF8String]);
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);
}
}
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.
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
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)))
}
Here's my Swift workaround for the same problem. I run into this 1000~1024 characters limitation for each interpolated symbol.
Since I mostly run into this when logging json
files, I've decided to just split the string into chunks, but not directly. I try to split on the line break to preserve the overall json
string between multiple logs.
Another reason behind the provided workaround was to keep the logger.debug(_:)
call in the same place where it was, so I won't lose the ability to go to the source code location where it was actually logged.
So here's my solution:
extension String {
/// Splits the string into chunks to accommodate a specified maximum length,
/// considering line breaks as the preferred splitting points.
///
/// - Parameters:
/// - maxLength: The maximum length of each output chunk. The default value is 1000.
/// - Returns: An array of `Substring` chunks.
///
/// - Note: The function iterates through the original string and creates chunks of text
/// based on the specified maximum length. If a line break character is found
/// within the chunk, the split occurs at the line break. If no line break
/// character is present, the function tries to split at the last space character
/// before the maxLength. If no space is found, the chunk is split at the
/// maxLength. The line break character or space character (if used for
/// splitting) is dropped from the output.
///
/// - Complexity: The time complexity is O(n), where n is the number of characters
/// in the string. The function iterates through the string once to create the
/// chunks.
public func splittedForLogger(maxLength: Int = 1000) -> [Substring] {
var chunks: [Substring] = []
var currentIndex = self.startIndex
while currentIndex < self.endIndex {
let remainingLength = self.distance(from: currentIndex, to: self.endIndex)
let chunkLength = min(maxLength, remainingLength)
let nextIndex = self.index(currentIndex, offsetBy: chunkLength)
let chunk = self[currentIndex..<nextIndex]
if chunkLength == remainingLength {
/// Last chunk
chunks.append(chunk)
break
}
/// Attempt to find the last line break character within the chunk
/// If not found, attempt to find the last space character
/// If neither line break nor space character is found, split at the maxLength
let splitIndex = chunk.lastIndex { character in
CharacterSet.newlines.contains(character.unicodeScalars.first ?? .init(0))
} ?? chunk.lastIndex { character in
CharacterSet.whitespaces.contains(character.unicodeScalars.first ?? .init(0))
} ?? chunk.endIndex
let splitChunk = self[currentIndex..<splitIndex]
chunks.append(splitChunk)
currentIndex = splitIndex < chunk.endIndex ? self.index(after: splitIndex) : nextIndex
}
return chunks
}
@inlinable public func forEachLoggerChunk(
maxLength: Int = 1000,
_ body: (Substring) throws -> Void
) rethrows {
try self
.splittedForLogger(maxLength: maxLength)
.forEach(body)
}
}
And now you can use it like this for logging long strings. You just change your
logger.debug("\(someLongString)")
into this
someLongString.forEachLoggerChunk { logger.debug("\($0)") }
Result:
© 2022 - 2025 — McMap. All rights reserved.
dic[@"key"]
instead of the entire dictionary? – Jerryleeprintf
instead ofNSLog
forums.developer.apple.com/message/161367#161367 – Marybethmaryellen