iOS 10 / Xcode 8设备上的NSLog似乎被截断了?为什么?为什么?

vc9ivgsu  于 2023-08-07  发布在  iOS
关注(0)|答案(8)|浏览(153)

为什么在Xcode 8 / iOS 10中控制台输出显示不完整?
x1c 0d1x的数据

deikduxw

deikduxw1#

一个临时的解决方案,只是在一个全局头文件中将所有的NSLOG重新定义为printf

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

字符串

bweufnob

bweufnob2#

在iOS 10和Xcode 8中,苹果从旧的ASL(苹果系统日志)切换到一个名为Unified logging的新日志系统。NSLog调用实际上是委托给新的os_log API。(来源:https://developer.apple.com/reference/os/logging):

重要

统一日志记录在iOS 10.0及更高版本、macOS 10.12及更高版本、tvOS 10.0及更高版本以及watchOS 3.0及更高版本中可用,并取代ASL(Apple System Logger)和Syslog API。历史上,日志消息被写入磁盘上的特定位置,例如/etc/system. log。统一日志记录系统将消息存储在内存和数据存储中,而不是写入基于文本的日志文件。
还有

重要

日志记录系统存储时,将截断大于系统最大消息长度的日志消息行。使用log命令行工具查看实时活动流时,完整的消息是可见的。但是,请记住,流式日志数据是一项昂贵的活动。
正如@Hot_Leaks所指出的那样,SDK的头文件中显示的“系统最大消息长度”限制为格式化变量的1024个字符(来源:<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__)

字符串
由于缓冲区大小限制似乎是硬编码到libsystem_trace.dylib中的,所以我看不到解决方法,只能打印字符串字面量而不是格式化变量(%@),或者将格式化字符串变量拆分为< 1024个字符串。
printf将在调试期间工作,因为调试器(Xcode)显示进程的out / error流,但它不会被发送到设备日志本身。这意味着xfdai的解决方案在使用其他日志应用程序(如macOS的Console应用程序)或未调试应用程序(如客户设备上运行的AppStore应用程序)出现问题时无法帮助您。

将xfdai的解决方案扩展到已部署的应用程序

在部署的应用程序/非调试版本中,无法看到NSLogprintf
将消息直接打印到设备日志(可以使用Xcode -> Window -> Devices,mac的Console App或第三方实用程序(如deviceconsole)访问)的唯一方法是调用os_log API(这是自iOS 10以来使用的ASL的继承者)。
下面是一个全局头文件,我使用它将NSLog重新定义为iOS 10上对_os_log_internal的调用:

#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 */

x759pob2

x759pob23#

这是iOS 10唯一的“功能”。使用这个代替:

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

字符串

nwsw7zdq

nwsw7zdq4#

可以使用此方法。每800个字符拆分一次。或者可以设定。NSLOG我认为每1000个字符截断一次。如果string小于800将使用简单的NSLog。这对于Json长字符串很有用,并使用控制台。printf使用Xcode调试窗口而不是控制台。

-(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);
            }

    }

字符串

siv3szwd

siv3szwd5#

在iOS 10上:

  1. printf()在Xcode的控制台内工作,但在设备的控制台日志上不工作。
  2. NSLog在两个位置都截断。
    我现在要做的是将NSLog字符串拆分成几行,并分别记录每一行。
- (void) logString: (NSString *) string
{
    for (NSString *line in [string componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]])
    {
        NSLog(@"%@", line);
    }
}

字符串
这在控制台上工作,但不容易阅读。

00jrzges

00jrzges6#

漂亮的功能和线条

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

字符串
带日期

#define NSLog(FORMAT, ...) printf("%s %s:%d %s\n", [[[NSDate date] description] UTF8String],__PRETTY_FUNCTION__,__LINE__,[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String])


改进@xfdai答案

qco9c6ql

qco9c6ql7#

这并没有提供一个很好的输出,但是打印了长日志的所有必要信息,甚至在控制台上也是如此。

func Log(_ logString: String?) {
    if logString?.isEmpty ?? false { return }
    NSLog("%@", logString!)
    Log(String(logString!.dropFirst(1024)))
}

字符串

aor9mmx1

aor9mmx18#

下面是我的Swift解决同样问题的方法。我遇到了这个1000~1024字符的限制,每个插值符号。
由于我在记录json文件时经常遇到这种情况,所以我决定将字符串拆分成块,但不是直接拆分。我尝试在换行符处进行拆分,以保留多个日志之间的整个json字符串。
所提供的解决方法背后的另一个原因是将logger.debug(_:)调用保持在原来的位置,这样我就不会失去访问实际记录它的源代码位置的能力。
所以我的解决方案是:

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)
    }
}

字符串
现在你可以像这样使用它来记录长字符串。你只要改变你的

logger.debug("\(someLongString)")


变成这样

someLongString.forEachLoggerChunk { logger.debug("\($0)") }


测试结果:
x1c 0d1x的数据

相关问题