Справочник LogsQL
Подзапросы
LogsQL поддерживает подзапросы в следующих местах:
- В фильтрах — см. фильтр подзапроса.
- В следующих пайпах:
Контекст потока
См. пайп stream_context.
Трансформации
LogsQL поддерживает различные трансформации записей логов, выбранных с помощью фильтров, включая:
- Извлечение произвольного текста из полей логов в соответствии с указанным шаблоном.
Подробнее см. пайп
extract. - Распаковка JSON-полей из полей логов. См. пайп
unpack_json. - Распаковка полей формата logfmt из полей логов. См. пайп
unpack_logfmt. - Распаковка сообщений Syslog из полей логов. См. пайп
unpack_syslog. - Создание нового поля из существующих полей логов в соответствии с указанным форматом. См. пайп
format. - Замена подстрок в указанном поле лога.
См. пайп
replaceи пайпreplace_regexp. - Создание нового поля на основе математических вычислений над существующими полями логов. См. пайп
math.
См. также другие пайпы, которые могут быть применены к выбранным логам.
Также возможно выполнять различные трансформации выбранных записей логов на стороне клиента
с помощью команд Unix, таких как jq, awk, cut и других, используя CLI для работы с логами.
Пост-фильтрация
Пост-фильтрацию результатов запроса можно выполнять на любом этапе с помощью пайпа filter.
Также возможно выполнять пост-фильтрацию выбранных записей логов на стороне клиента с помощью grep и аналогичных команд Unix, используя CLI для работы с логами.
Статистика
Статистика по выбранным логам может быть вычислена с помощью пайпа stats.
Также возможно выполнять вычисления статистики по выбранным записям логов на стороне клиента с помощью команд Unix, таких как sort, uniq и других, используя CLI для работы с логами.
Сортировка
По умолчанию хранилище логов не сортирует возвращаемые результаты из соображений производительности. Используйте пайп sort для сортировки результатов.
Ограничители
LogsQL предоставляет следующие пайпы для ограничения возвращаемых результатов:
- пайп
fieldsи пайпdeleteпозволяют ограничить набор возвращаемых полей логов. - пайп
limitпозволяет ограничить количество возвращаемых записей логов.
Запрос определённых полей
Определённые поля логов можно запрашивать с помощью пайпа fields.
Строковые литералы
LogsQL поддерживает следующие строковые литералы:
"строка в двойных кавычках". Двойные кавычки и обратная косая черта внутри такой строки должны быть экранированы символом\:"экранируем\"двойные кавычки и \\ обратную косую черту". Строки в двойных кавычках могут содержать специальные последовательности, такие как\n,\t,\f,\x8cи другие. Они декодируются в соответствии со спецификацией строковых литералов Go.'строка в одинарных кавычках'. Одинарные кавычки и обратная косая черта внутри такой строки должны быть экранированы символом\:'экранируем\'одинарные кавычки и \\ обратную косую черту'.`строка в обратных кавычках`. Строки с обратными косыми чертами, двойными и одинарными кавычками не нужно экранировать внутри строк в обратных кавычках. Символ обратной кавычки не может быть использован внутри строк, заключённых в обратные кавычки.
Комментарии
Запрос LogsQL может содержать комментарии в любом месте. Комментарий начинается с символа # и продолжается до конца текущей строки.
Пример запроса с комментариями:
error # найти логи со словом `error`
| stats by (_stream) count() logs # затем подсчитать количество логов по метке `_stream`
| sort by (logs) desc # затем отсортировать по найденным логам в порядке убывания
| limit 5 # и показать топ-5 потоков с наибольшим количеством логов
Числовые значения
LogsQL принимает числовые значения в следующих форматах:
- Обычные целые числа, например
12345или-12345 - Обычные числа с плавающей точкой, например
0.123или-12.34 - Сокращённый числовой формат
- Формат длительности
Сокращённые числовые значения
LogsQL принимает целочисленные значения и значения с плавающей точкой со следующими суффиксами:
B— байты.KиKB— значение умножается на10^3MиMB— значение умножается на10^6GиGB— значение умножается на10^9TиTB— значение умножается на10^12KiиKiB— значение умножается на2^10MiиMiB— значение умножается на2^20GiиGiB— значение умножается на2^30TiиTiB— значение умножается на2^40
Несколько значений с суффиксами могут быть объединены. Например, 1MiB500KiB200B.
Все числа могут содержать разделители _, которые могут улучшить читаемость запроса. Например, 1_234_567 эквивалентно 1234567, а 1.234_567 эквивалентно 1.234567.
Значения длительности
LogsQL принимает значения длительности со следующими суффиксами в местах, где допускается указание длительности:
ns— наносекунды. Например,123ns.µs— микросекунды. Например,1.23µs.ms— миллисекунды. Например,1.23456ms.s— секунды. Например,1.234s.m— минуты. Например,1.5m.h— часы. Например,1.5h.d— дни. Например,1.5d.w— недели. Например,1w.y— годы (365 дней). Например,1.5y.
Несколько значений длительности могут быть объединены. Например, 1h33m55s.
Внутри платформы значения длительности преобразуются в наносекунды.
Советы по производительности
- Настоятельно рекомендуется указывать фильтр по времени для сужения поиска до определённого временного диапазона.
- Настоятельно рекомендуется указывать фильтр потока для сужения поиска до определённых потоков логов.
- Рекомендуется указывать нужные поля логов в результатах запроса с помощью пайпа
fields, если выбранные записи логов содержат большое количество полей, которые вам не нужны. Это экономит дисковый ввод-вывод и процессорное время, необходимые для чтения и распаковки всех полей логов с диска. - Перемещайте более быстрые фильтры, такие как фильтр по слову и фильтр по фразе, в начало запроса. Это правило не относится к фильтру по времени и фильтру потока, которые могут быть размещены в любом месте запроса.
- Перемещайте более специфичные фильтры, которые соответствуют меньшему количеству записей логов, в начало запроса. Это правило не относится к фильтру по времени и фильтру потока, которые могут быть размещены в любом месте запроса.
- Если выбранные логи передаются в пайпы для дальнейших трансформаций и вычисления статистики, рекомендуется уменьшить количество выбранных логов, используя более специфичные фильтры, которые возвращают меньшее количество логов для обработки пайпами.
- Если логи хранятся на системах хранения с высокой задержкой, таких как NFS или S3, увеличение количества параллельных читателей может помочь улучшить производительность запросов. Подробнее см. опция
parallel_readers.
См. также устранение неполадок производительности запросов.
Опции запросов
Хранилище логов поддерживает следующие опции, которые могут быть переданы в начале запроса LogsQL <q> через синтаксис options(opt1=v1, ..., optN=vN) <q>:
Опция запроса concurrency
Хранилище логов выполняет каждый запрос на всех доступных ядрах CPU параллельно. Обычно это обеспечивает наилучшую производительность запросов. Иногда необходимо уменьшить количество используемых ядер CPU для снижения потребления оперативной памяти и/или нагрузки на процессор. Это можно сделать, установив опцию concurrency в значение, меньшее количества доступных ядер CPU. Например, следующий запрос выполняется максимум на 2 ядрах CPU:
options(concurrency=2) _time:1d | count_uniq(user_id)
Опция concurrency применяется индивидуально к каждому узлу хранения в кластерной конфигурации платформы.
См. также опция запроса parallel_readers.
Опция запроса parallel_readers
Хранилище логов использует параллельные читатели данных для выполнения запросов. Количество параллельных читателей по умолчанию подходит для большинства практических случаев использования. Иногда может потребоваться настроить его на уровне отдельного запроса (например, для повышения производительности запросов путём увеличения количества параллельных читателей, когда логи хранятся на постоянном хранилище с высокой задержкой чтения, таком как NFS или S3). Это можно сделать с помощью опции запроса parallel_readers. Например, следующий запрос использует 100 параллельных читателей:
options(parallel_readers=100) _time:1d error | count()
Если опция parallel_readers не задана, но задана опция concurrency, то количество параллельных читателей равно значению concurrency (не более 2000).
Количество параллельных читателей по умолчанию можно настроить с помощью флага командной строки -defaultParallelReaders.
Опция parallel_readers применяется индивидуально к каждому узлу хранения в кластерной конфигурации платформы.
Обратите внимание, что слишком большое количество параллельных читателей может привести к чрезмерному потреблению оперативной памяти и нагрузке на CPU.
Опция запроса ignore_global_time_filter
При работе через веб-интерфейс или HTTP API запросов, которые могут применять глобальный временной диапазон через параметры запроса start и end, хранилище логов добавляет глобальный фильтр _time в запрос и каждый подзапрос. Добавленный фильтр времени соответствует временному диапазону [start, end), переданному через HTTP-параметры запроса, где end трактуется как невключительный (внутри он преобразуется в end-1ns, поскольку временные диапазоны LogsQL являются включительными). Установите ignore_global_time_filter=true, чтобы предотвратить добавление этого глобального фильтра времени в данный запрос и все его подзапросы.
Например, следующий запрос сохраняет оригинальную логику времени в теле запроса без добавления глобального фильтра _time:
options(ignore_global_time_filter=true) _time:>1h | count()
Эту опцию можно использовать в подзапросах для исключения глобального временного диапазона для данного подзапроса. Например, следующий запрос возвращает количество логов со значениями user_id, встречавшимися в логах за декабрь 2024 года, на временном диапазоне [start...end), переданном в API запроса:
user_id:in(options(ignore_global_time_filter=true) _time:2024-12Z | keep user_id) | count()
Подзапрос in(...) без options(ignore_global_time_filter=true) учитывает только значения user_id на пересечении декабря 2024 года и временного диапазона [start...end), переданного в API запроса:
user_id:in(_time:2024-12Z | keep user_id) | count()
Опция запроса allow_partial_response
В кластерном режиме работы платформы некоторые узлы хранения могут быть временно недоступны. Установите allow_partial_response=true, чтобы возвращать частичные результаты от доступных узлов вместо ошибки всего запроса.
Например:
options(allow_partial_response=true) _time:1h error | stats count()
Это может привести к некорректным результатам, поэтому используйте эту опцию с осторожностью. Опция запроса переопределяет HTTP-параметр allow_partial_response и флаг командной строки -search.allowPartialResponse, поэтому лучше использовать опцию запроса для более явного контроля.
Опция запроса time_offset
Опция запроса time_offset вычитает указанное смещение из всех фильтров по времени в запросе, а затем добавляет указанное смещение к выбранным значениям поля _time перед передачей их в пайпы запроса. Позволяет сравнивать результаты запросов за одинаковый период с разным смещением. Принимает значения длительности, такие как 12h, 1d, 1y. Например, следующий запрос возвращает количество логов со словом error за последний час 7 дней назад:
options(time_offset=7d) _time:1h error | stats count() as 'errors_7d_ago'
Устранение неполадок
LogsQL хорошо работает в большинстве случаев при правильной настройке. Но иногда вы можете наблюдать медленные запросы. Наиболее частая причина — запрос слишком большого количества логов без достаточной фильтрации. Всегда будьте конкретны при построении ваших запросов.
Используйте следующие шаги, чтобы понять ваш запрос и улучшить его скорость.
Проверка количества совпадающих логов
Вы можете сделать это, запуская несколько версий запроса, каждый раз заканчивая его | count() после фильтра или пайпа, который может изменить количество строк.
Предположим, у вас есть следующий запрос, который выполняется медленно:
_time:5m host:"api-" level:error "database" | stats by (app) count()
Замените все пайпы в запросе на | count() и выполните обновлённый запрос, чтобы увидеть общее количество совпадающих логов:
_time:5m host:"api-" level:error "database" | count()
Пример вывода (получен через CLI для работы с логами, но вы можете использовать любой поддерживаемый метод запросов):
executing [_time:5m level:error database host:"api-" | stats count(*) as "count(*)"]...; duration: 0.474s
{
"count(*)": "19217008"
}
Данные фильтры соответствуют 19 217 008 логам, и сопоставление занимает 0,474 секунды.
Если время выполнения велико, попробуйте изменить порядок фильтров. Поставьте наиболее селективные и дешёвые условия первыми. Фильтры выполняются один за другим, поэтому ранний фильтр, который отбрасывает много логов, ускорит работу последующих фильтров. Дополнительные рекомендации см. в разделе Советы по производительности.
Если вы не уверены, какой фильтр наиболее селективный или наиболее затратный, вы можете добавить | count() после каждого фильтра, убирая остальные фильтры. Это поможет увидеть, сколько логов соответствует каждому фильтру, и даст представление об их производительности:
_time:5m level:error | count()
_time:5m host:"api-" | count()
_time:5m "database" | count()
Фильтр _time является ключевым — если он отсутствует, хранилище логов может сканировать логи за весь период хранения вместо определённого временного диапазона. Фильтр _time позволяет сократить объём сканируемых логов до указанного временного диапазона. Обратите внимание, что веб-интерфейс и плагин Grafana автоматически устанавливают фильтр _time на выбранный временной диапазон, поэтому нет необходимости указывать его вручную в запросе.
Тестирование фильтров потока
Если запрос не содержит фильтров потока логов, хранилище логов не может предварительно выбрать подходящие потоки логов и должно выполнять поиск по всем потокам логов в выбранном временном диапазоне. Если вы добавите фильтр потока логов, например:
{app="nginx"}
Тогда хранилище логов ищет только в потоках логов, соответствующих этому фильтру потока, и пропускает блоки данных, принадлежащие другим потокам. Это значительно быстрее. Поэтому наличие хорошего фильтра потока важно для производительности запросов.
Однако, если ваш поток логов имеет поле потока app="nginx", но вы записываете фильтр как:
app:=nginx
Тогда хранилище логов рассматривает это как обычный фильтр точного совпадения, поэтому он не будет работать так быстро, как соответствующий фильтр потока. Убедитесь, что вы используете правильный синтаксис фильтра потока. Подробнее см. документацию по фильтрам потока.
Проверка количества уникальных потоков логов
Фильтры потока логов могут помочь улучшить производительность запросов, но они не являются универсальным решением. Остерегайтесь следующих распространённых проблем:
- Если у вас слишком много потоков логов, и каждый поток охватывает лишь несколько логов, производительность запросов может значительно снизиться.
- Если поток логов, по которому вы выполняете поиск, содержит большое количество логов (например, сотни миллионов и более), поиск в этом потоке может быть медленным.
Чтобы проверить количество потоков логов на заданном временном диапазоне, оставьте только фильтр по времени и добавьте | count_uniq(_stream_id) в конец запроса (см. документацию count_uniq). Например, чтобы увидеть, сколько потоков логов у вас за последний день:
_time:1d | count_uniq(_stream_id)
Результат может быть таким:
{
"count_uniq(_stream_id)": "954"
}
Это означает, что логи за последний день содержат 954 уникальных потока логов.
Следующий запрос возвращает топ-10 потоков логов с наибольшим количеством записей (используется пайп top):
_time:1d | top 10 by (_stream)
Следующий запрос возвращает количество уникальных потоков логов и количество логов для фильтра потока {app="nginx"} за последний день:
_time:1d {app="nginx"}
| stats
count_uniq(_stream) as streams,
count() as logs
Он использует пайп stats.
Потоки с малым количеством логов обычно возникают, когда одно или несколько полей потока имеют слишком много различных значений. В таких случаях лучше удалить эти поля из набора полей потока логов — это поможет снизить кардинальность.
Определение наиболее затратных частей запроса
Чтобы увидеть, какие части ваших логов занимают больше всего места или замедляют поиск, вы можете использовать пайп block_stats. Он возвращает детальную статистику по блокам для ваших данных.
Начните с вашего обычного запроса. Затем добавьте пайп | keep <список полей> | block_stats:
_time:1d | keep kubernetes.pod_name, kubernetes.pod_namespace | block_stats
Пайп keep оставляет только перечисленные поля логов и удаляет остальные, так что вы получаете статистику только по интересующим вас полям. Включите поля, которые хотите проанализировать, вместе с block_stats.
Иногда необработанные числа, возвращаемые пайпом block_stats, слишком детальны, чтобы быть полезными. Вы можете добавить пайп stats для обобщения чисел:
_time:1d
| keep kubernetes.pod_name, kubernetes.pod_namespace
| block_stats
| stats by (field)
sum(values_bytes) values_bytes_on_disk,
sum(rows) rows
| sort by (values_bytes_on_disk) desc
Пример вывода:
values_bytes_on_disk: 561 field: kubernetes.pod_name rows: 172
values_bytes_on_disk: 101 field: kubernetes.pod_namespace rows: 172
Суммирование байтов значений и строк позволяет увидеть с первого взгляда, какие поля занимают больше всего дискового пространства или заставляют хранилище логов сканировать больше данных.
Когда вы знаете, какие поля являются затратными, вы можете решить, удалить ли шумное поле из запроса, вынести его отдельно или изменить фильтры, чтобы избежать чтения лишних данных.
Также может быть полезно добавить пайп query_stats в конец запроса, чтобы понять, сколько данных различных типов запрос читает и обрабатывает.
Пошаговое профилирование пайпов
Предположим, вам нужно профилировать и оптимизировать следующий запрос:
_time:5m -"cannot open file" error
| extract "user_id=(<uid>)"
| top 5 by (uid)
Удалите все пайпы из запроса и оставьте только фильтр по времени, например _time:5m. Этот запрос возвращает все логи за заданный временной диапазон. Если запрос выполняется через встроенный веб-интерфейс или через плагин Grafana, просто оставьте * в поле ввода запроса, поскольку и веб-интерфейс, и плагин Grafana автоматически фильтруют логи по выбранному временному диапазону. Добавьте | count() в конец запроса и измерьте время выполнения. Это даёт базовый показатель времени, затрачиваемого на чтение и сканирование логов за выбранный временной диапазон (дополнительные фильтры и пайпы добавят дополнительную работу сверх этого базового показателя). Запрос также возвращает количество логов за выбранный временной диапазон, что является верхней границей количества логов, которые могут быть обработаны последующими шагами запроса:
_time:5m | count()
Затем добавляйте фильтры из оригинального запроса по одному и измеряйте результирующую производительность запроса. Пробуйте разные фильтры из оригинального запроса, оставляя тот фильтр, который выполняется быстрее, для каждого шага.
_time:5m error | count()
_time:5m error -"cannot open file" | count()
Если вы обнаружили медленный фильтр, попробуйте заменить его более быстрым и более специфичным фильтром. Подробнее см. советы по производительности. Например, фильтры отрицания фраз, такие как -"cannot open file", могут быть медленнее, чем более специфичные позитивные фильтры. В некоторых случаях может быть лучше явно выбрать нужные логи с помощью contains_any(phrase1, ..., phraseN), где phrase1, …, phraseN — фразы, встречающиеся в логах, которые вы хотите выбрать:
_time:5m error contains_any("access denied", "unauthorized", "403") | count()
После добавления всех необходимых фильтров к запросу посмотрите на количество совпадающих логов. Если их слишком много (например, превышает десятки миллионов), вероятно, можно добавить более специфичные фильтры к запросу, чтобы уменьшить количество логов для обработки пайпами. Например, добавление фильтров фразы по константным строковым частям из шаблона extract может значительно уменьшить количество логов для обработки пайпом extract:
_time:5m error contains_any("access denied", "unauthorized", "403") "user_id=(" | count()
Затем добавляйте пайпы из оригинального запроса по одному и измеряйте длительность запроса для каждого шага:
_time:5m error contains_any("access denied", "unauthorized", "403") "user_id=("
| extract "user_id=(<uid>)"
| count()
_time:5m error contains_any("access denied", "unauthorized", "403") "user_id=("
| extract "user_id=(<uid>)"
| top 5 by (uid)
| count()
Если запрос становится медленным или начинает потреблять много оперативной памяти после добавления очередного фильтра или пайпа, вы можете быстро определить, какая часть запроса нуждается в оптимизации.
Также может быть полезно добавить пайп query_stats в конец запроса, чтобы понять, сколько данных различных типов запрос читает и обрабатывает.
Если вы обнаружили медленный фильтр или пайп, попробуйте следующие идеи:
- Регулярные выражения и разбор JSON являются затратными операциями. По возможности используйте более быстрые альтернативы. См. советы по производительности.
- Сортировка без ограничения с помощью пайпа
sortхранит все сортируемые логи в памяти (или может завершиться с ошибкой, если требуется слишком много памяти). Добавьтеlimitили уменьшите количество входных логов. - Функции высокой кардинальности, такие как
count_uniq(), отслеживают уникальные значения в памяти (до настроенногоlimit, если он задан). Подумайте, как уменьшить количество уникальных значений для отслеживания. - Большое количество групп в
stats by (...)может потреблять много памяти. Фильтруйте или трансформируйте ваши данные, чтобы уменьшить количество групп.