В этой публикации попробуем понять, как и где Xcode хранит свои логи, что такое SLF0 и как все это читать, а может быть даже понять, и лучше без IDE, так интереснее. Без лишних предисловий. Чтобы посмотреть логи сборки проекта или скажем тестов, в обычной, повседневной разработке, мы идем и смотрим последнюю вкладку тулбара Xcode.  Организация лога в целом читаемая, даже есть возможность все это экспортировать в txt формат. Но, все это нам мало подходит для автоматизации сбора метрик, скажем для аккумулирования динамики времени сборки модулей при прогоне job'ы на CI. Простого пути экспортировать данный лог, в полном объеме для последующей интерпретации конкретных событий нет. Итак, все логи Xcode собирает и хранит в DerivedData/{ProjectName}/Logs. По умолчанию находится по следующему пути: Данный путь можно поменять в настройках Xcode: Собственно, DerivedData хранит всю промежуточную информацию (включая логи), а также артефакты, требуемые для оптимизации последующей сборки. Внутри папки Logs можно встретить множество логов различных стадий, с которыми мы сталкиваемся во время разработки: Здесь мы можем найти следующие виды файлов: *.xcactivitylog - собственно журнал, который нас будет интересовать далее; *.xcresult - bundle, который хранит сводную информацию. Можно открыть в Xcode и посмотреть журналы в том виде, как если бы мы просто открыли последнюю вкладку тулбара; LogStoreManifest.plist - содержит сводную информацию в читаемом виде о всех xcactivitylog файлах, можно использовать например для отслеживания готовности очередного журнала с логами. Итак, файл лога представляет собой сжатый gzip журнал, названием служит уникальный идентификатор UUID.  После распаковки получаем журнал, записанный в формате сериализации SLF0.   На просторах сети нет официальной документации, описывающий данный вид сериализации. Все, что удалось найти - плод трудов энтузиастов, за что им большое спасибо. Каждый лог начинается с обязательного хедера SLF0.  Далее последовательность токенов в следующем формате:  <side_value> - опциональное значение, и зависит от конкретного разделителя. Текущая версия SLF0 поддерживает 7 разделителей, каждый из которых является описанием значения конкретного типа: Описывает тип integer, например: Значение слева: UInt64, собственно само значение токена. Значение справа: отсутствует. Описывает тип string, например: Значение слева: UInt64, описывает количество символов, которое идет справа от разделителя и является значением строкового токена. В данном случае это значит, что искомым строковым значением является строка справа, состоящая из 21 символа. Подсчет символов ведется в utf-16, и не соответствует дефолтному count у экземпляров типа string, который возвращает количество графем).  Значение справа: строка, состоящая из количества символов, которое находится слева от разделителя.  Описывает тип double, например: Значение слева: представляет собой число с плавающей запятой с прямым порядком байтов, закодированное в шестнадцатеричном формате. Используется в журнале для представления типа timeInterval.  Пример приведения к обычному double из языка swift: Значение справа: отсутствует. Описывает null значение, т.е. значения какого-либо поля у нас просто нет, например: Значение слева: отсутствует. Значение справа: отсутствует. Описывает array, указывая количество элементов, которые тот содержит, например: Значение слева: количество элементов в массиве. Элементами массива являются значения с типом class_instance, о них будет чуть ниже. Значение справа: отсутствует. Описывает class_name, в SLF0 решающим фактором успеха является порядок всех токенов в журнале, при парсинге их порядок важно соблюсти. Полученная индексация class_name - искомая при парсинге class_instance. Значение слева: здесь все по аналогии с типом string, который описан выше. Значение справа: название класса. Описывает class_instance, т.е. указывает на название класса, исходя из которого должно стать понятно, как интерпретировать все токены, которые будут следовать далее за этим class_instance. Значение слева: UInt64, собственно само значение токена, указывающее на позицию соответствующего class_name в журнале (требуется, чтобы понять к какому типу относятся токены, которые идут далее). Значение справа: отсутствует. Для удобства понимания данного языка сериализации, набросал описание синтаксиса в виде формы Бэкуса-Наура (может пригодиться при разработке соответствующего синтаксического анализатора): Единственное, расписывать что такое <digit> и <character> не стал, думаю и так понятно. Итак, зная все вышеперечисленные синтаксические и семантические правила, попробуем прочитать какой-нибудь конкретный лог (из примера выше), после чтения получаем следующий список токенов, идущих один за одним (использую написанный tokenizer):  На первый взгляд, все очень страшно и не менее понятно, чем было до этого. Однако, попробуем все же разобраться. Для начала, прошу обратить внимание на имена классов, на самом деле их ограниченное количество, на момент написания статьи: IDEActivityLogSection IDEActivityLogCommandInvocationSection IDEActivityLogUnitTestSection IDEActivityLogMajorGroupSection IDEActivityLogMessage  DVTDocumentLocation DVTTextDocumentLocation При попытке найти информацию о вышеперечисленных типах, становится понятно, что все они описаны в приватном фреймворке IDEFoundation.framework, который является частью Xcode и по умолчанию находится по адресу: Далее, посредством Reverse engineering можно все эти классы отыскать и воспроизвести их набор полей, для последующего сопоставления со значениями токенов, полученных выше (все производимые манипуляции - в исследовательских целях). Ради интереса я покопался с ktool и смог без особого труда получить описание всех классов: Приведу в пример IDEActivityLogSection, ради удобства - только его инициализатор, который отражает все нужные нам поля: Единственная проблема, что порядок токенов не соответствует порядку, полученному при исследовании фреймворка.  Однако, ребята из тулзы XCLogParser уже все нашли и сопоставили, большое им спасибо, как выглядят все полученные типы с правильным порядком полей, можно посмотреть здесь. К примеру, итоговые поля с верным порядком для IDEActivityLogSection : Зная все это, попробуем расшифровать полученные токены: Первый токен - всегда версия формата сериализации SLF0, в нашем случае 10-я: Далее идет первое объявление имени типа, на которое будут ссылаться токены с типом class_instance: Ниже начинается описание первого объекта со значениями его параметров, class_instance(1) указывает, что следующие токены следует интерпретировать как class_name, который указан под соответствующим относительным индексом 1, в нашем случае - IDEActivityLogSection:  Следом идет объявление массива, за которым идет описание его элементов: И, напоследок, оставшиеся поля у корневой секции: Для удобства понимания, итоговой парсинг журнала, записанным в формате json: Собственно, это все, что я хотел рассказать. В полученном, удобочитаемом виде можно лог исследовать и вычленить ту информацию, которая может быть вам полезна в своих проектах. Спасибо за внимание! https://github.com/MobileNativeFoundation/XCLogParser/blob/master/docs/Xcactivitylog Format.md https://speakerdeck.com/iosdevelopersmexico/xclogparser-switui-primeras-impresiones https://techlife.cookpad.com/entry/2017/12/08/124532 LinkedIn Twitter Threads  iOS engineer