О языке VRL
Применение VRL для преобразований данных
VRL работает на базе инструмента обработки данных Vector. Изначально в Vector преобразование данных осуществлялось на основе встроенного языка конфигурации, и появление VRL позволило существенно сократить описание трансформаций.
Рассмотрим распространенный случай использования — преобразование общего журнала Apache, поступающего из Docker.
{
"time": "2021-02-03T21:13:54.713161211Z",
"stream": "stdout",
"log": "198.51.100.12 - user [03/Feb/2021:21:13:55 -0200] \"GET /embrace/supply-chains/dynamic/vertical HTTP/1.0\" 201 20574"
}
Анализ этого журнала без VRL в Vector выглядит следующим образом:
# ... источники ...
# Анализ внутреннего журнала Syslog.
[transforms.parse_syslog]
type = "regex_parser"
inputs = ["parse_docker"]
patterns = ['^(?P<host>\S+) (?P<client>\S+) (?P<user>\S+) \[(?<timestamp>[\w:/]+\s[+\-]\d{4})\] "(?<method>\S+) (?<resource>.+?) (?<protocol>\S+)" (?<status>\d{3}) (?<bytes_out>\S+)$']
field = "log"
# Удаление дублирующихся полей времени и журнала Docker.
[transform.remove_log]
type = "remove_fields"
inputs = ["parse_syslog"]
fields = ["time", "log"]
# Принудительная обработка полей.
[transforms.coerce_fields]
type = "coercer"
inputs = ["remove_log"]
types.timestamp = "timestamp"
types.status = "int"
types.bytes_out = "int"
# Приемники данных.
...
Результат преобразования:
{
"bytes_out": 20574,
"host": "198.51.100.12",
"method": "GET",
"resource": "/embrace/supply-chains/dynamic/vertical",
"protocol": "HTTP/1.0",
"status": 201,
"timestamp": "2021-02-03T23:13:55Z",
"client": "-",
"user": "user"
}
С помощью языка VRL можно проанализировать вышеуказанный фрагмент так:
. = parse_common_log!(.log)
.total_bytes = del(.size)
.internal_request = ip_cidr_contains("198.51.100.0/16", .host) ?? false
Результат преобразования:
{
"host": "198.51.100.12",
"internal_request": true,
"user": "user",
"timestamp": "2021-02-03T23:13:55Z",
"message": "GET /embrace/supply-chains/dynamic/vertical HTTP/1.0",
"method": "GET",
"path": "/embrace/supply-chains/dynamic/vertical",
"protocol": "HTTP/1.0",
"total_bytes": 20574,
"status": 201
}
Конфигурация преобразования на VRL проще в написании, чтении и управлении, чем аналогичная на Vector. Добавлено дополнительное поле internal_request
, чтобы продемонстрировать возможности встроенных функций VRL.
Принципы и характеристики языка VRL
VRL разработан с использованием принципов безопасности и производительности, оставаясь при этом гибким, поэтому он подходит для задач высокопроизводительной обработки событий ИБ.
Типобезопасность и надежность
VRL реализует принципы типобезопасности и надежности путем явного контроля над обработкой возможных ошибок. Это можно проиллюстрировать на примере фрагмента, который упоминался выше.
{
"time":"2021-02-03T21:13:54.713161211Z",
"stream": "stdout",
"log": "198.51.100.12 - user [03/Feb/2021:21:13:55 -0200] \"GET /embrace/supply-chains/dynamic/vertical HTTP/1.0\" 201 20574"
}
Его необходимо проанализировать и привести к результату:
{
"host": "198.51.100.12",
"user": "user",
"timestamp": "2021-02-03T23:13:55Z",
"message": "GET /embrace/supply-chains/dynamic/vertical HTTP/1.0",
"method": "GET",
"path": "/embrace/supply-chains/dynamic/vertical",
"protocol": "HTTP/1.0",
"total_bytes": 20574,
"status": 201
}
Пользователь, только начавший работать с VRL, может написать следующую программу:
. = parse_common_log(.log)
.total_bytes = del(.size)
При компиляции он получит следующее сообщение об ошибке (Vector):
error[E103]: unhandled fallible assignment ┌─ :1:5 │ 1 │ . = parse_common_log(.log) │ --- ^^^^^^^^^^^^^^^^^^^^^^ │ │ │ │ │ this expression is fallible │ │ update the expression to be infallible │ or change this to an infallible assignment: │ ., err = parse_common_log(.log) │ = see documentation about error handling at https://errors.vrl.dev/#handling = learn more about error code 103 at https://errors.vrl.dev/103 = see language documentation at https://vrl.dev
Как видно, VRL требует от пользователя обработки любого выражения, которое может привести к ошибке во время выполнения. В нашем случае .log
может не быть строкой, поэтому нужно либо указать тип поля .log
, либо обработать ошибку в случае, если .log
не является строкой. Чтобы устранить эту ошибку, пользователь должен выполнить одно из следующих действий:
-
Обработка ошибки:
., err = parse_common_log(.log) if err != null { .malformed = true log("Failed to parse common-log: " + err, level: "error") } else { .total_bytes = del(.size) }
Если событие имеет неправильную форму (
.log
не является отформатированной строкой common-log), мы не выводим ошибку в лог и добавляем поле.malformed
. Это сохраняет исходные данные и позволяет легко направить искаженные данные на проверку. Часто обработкой этой ошибки пренебрегают, что приводит к потере данных и простою системы. -
Выявление ошибки и прерывание программы:
. = parse_common_log!(.log) .total_bytes = del(.size)
Иногда наличие искаженных данных бывает неприемлемым, и программа должна быть прервана. В этом случае VRL предлагает варианты ошибочных функций. Здесь указывается, что добавление к вызову функции постфикса
!
вызовет сбой и прервет выполнение программы. При этом Vector перейдет к обработке следующего события, но одновременно запишет в журнал сообщение об ошибке. Таким образом, пользователям предлагается выбрать метод обработки ошибок. -
Указание типов:
.log = to_string!(.log) ., err = parse_common_log(.log) if err != null { # This error only occurs for malformed *strings*. log("Failed to parse common-log: " + err, level: "error") } else { .total_bytes = del(.size) }
В приведенных примерах можно заметить, что ошибку типа трудно отличить от ошибки синтаксического анализа. Возможно, вы предпочтете прерывать выполнение программы при возникновении ошибок типа и обрабатывать ошибки синтаксического анализа. Это достигается с помощью прогрессивной безопасности типов. В ходе оценки программа на языке VRL накапливает информацию о типах ваших полей. Когда тип распознается, ошибки типа исключаются при последующем использовании этого поля. Таким образом, в приведенном выше примере пользователь знает, что ошибка может возникнуть только в случае сбоя синтаксического анализа.
Эргономическая безопасность
VRL обеспечивает эргономическую безопасность, то есть делает сложным создание медленных или нестабильных программ. Проверки, выполняемые во время компиляции, предотвращают ошибки во время выполнения. Однако они не могут выявить некоторые более тонкие проблемы с производительностью и поддерживаемостью, которые возникают из-за сложности программы. Эти проблемы могут привести к нестабильности системы сбора и анализа метрик, а также непредвиденным затратам ресурсов.
Для защиты от эргономических трудностей, VRL был строго ограничен, предлагая только те функции, которые необходимы для преобразования данных наблюдения. Любые функции, которые не соответствуют данной задаче или могут ухудшить эргономику, были исключены из языка.
Безопасность доступа к памяти
VRL наследует от Rust гарантии безопасности доступа к памяти. Это позволяет избежать распространенных программных ошибок и уязвимостей, которые возникают из-за неправильного обращения к памяти. Благодаря этому VRL подходит для инфраструктурных приложений, таких как конвейеры обработки событий, где надежность и безопасность являются первостепенными целями.