NSLog на устройствах в iOS 10 / Xcode 8 кажется усеченным? Зачем?

Почему консольный вывод отображается не полностью в Xcode 8 / iOS 10?

8 ответов

Решение

Временное решение, просто переопределите все NSLOG в printf в глобальном заголовочном файле.

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

В iOS 10 и Xcode 8 Apple перешла от старого доброго ASL (Apple System Log) в новую систему регистрации под названием 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) и API-интерфейсы Syslog. Исторически сообщения журнала записывались в определенные места на диске, такие как /etc/system.log. Унифицированная система ведения журнала хранит сообщения в памяти и в хранилище данных, а не записывает их в текстовые файлы журнала.

А также

Важный

Строки сообщений журнала, превышающие максимальную длину сообщения системы, усекаются при сохранении системой ведения журнала. Полные сообщения видны при использовании инструмента командной строки журнала для просмотра прямого потока активности. Имейте в виду, однако, что потоковые данные журнала являются дорогостоящим занятием.

Ограничение "максимальная длина сообщения системы" в заголовке SDK составляет 1024 символа для форматированных переменных, как отмечено @Hot_Leaks (источник: <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 на развернутые приложения

В развернутых приложениях / сборках без отладки нет возможности увидеть NSLogс или printfs.

Единственный способ напечатать сообщения напрямую в журнал устройства (доступ к которому можно получить с помощью Xcode -> Window -> Devices, Mac's Console App или сторонних утилит, таких как deviceconsole) - это вызов os_log API (который является преемником ASL используется с iOS 10).

Вот глобальный заголовочный файл, который я использую, чтобы переопределить NSLog как призыв к _os_log_internal на 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 */

Это iOS 10 только "фича". Используйте это вместо:

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

Вы можете использовать этот метод. Сплит каждые 800 символов. Или может быть установлен. NSLOG я думаю, усечь каждые 1000 символов. Если строка меньше 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);
            }

    }

На iOS 10:

  1. printf() работает внутри консоли Xcode, но не работает в журнале консоли устройства.
  2. NSLog усекает в обоих местах.

Сейчас я делаю NSLog строки в строки и запись каждой строки в отдельности.

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

Это работает на консоли, но не легко читается.

Из ответа @xfdai добавьте красивую функцию и строку

#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])

Это не обеспечивает хорошего вывода, но выводит всю необходимую информацию для длинных журналов даже на консоли.

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

Вот мой обходной путь Swift для той же проблемы. Я сталкиваюсь с ограничением в 1000–1024 символа для каждого интерполированного символа.

Поскольку я чаще всего сталкиваюсь с этим при регистрации файлов, я решил просто разделить строку на куски, но не напрямую. Я стараюсь разделить на разрыв строки, чтобы сохранить общее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)") }

Результат:

Другие вопросы по тегам