Author Archive

Оптимизация Grok (Logstash)

Повествование о том, как неэффективный разбор grok забил процессоры на 100%.

Немного предыстории. С начала года у нас было настроено расширение к PostgreSQL под названием pg_audit (см. https://github.com/pgaudit/pgaudit) для аудита доступа к таблицам и колонкам таблиц. Логи pg_audit отправляются filebeat’ом в logstash, где разбираются через grok.
На тот момент grok-выражение было следующим:
%{TIMESTAMP_ISO8601:pg_aud_log_date} %{DATA:pg_aud_time_zone} %{DATA:pg_aud_db_name} %{DATA:pg_aud_db_user} %{DATA:pg_aud_db_host}: LOG:%{SPACE}AUDIT: %{DATA:pg_aud_audit_type},%{INT:pg_aud_statement_id},%{INT:pg_aud_sub_statement_id},%{DATA:pg_aud_class},%{DATA:pg_aud_command},%{DATA:pg_aud_object_type},%{DATA:pg_aud_object_name},%{GREEDYDATA:pg_aud_statement}>

В течении 5 месяцев все было нормально, пока в последние несколько дней CPU не достиг 100% и не спадал.

В логах logstash были следующие ошибки:

Timeout executing grok

Остро встала задача оптимизиции. Почему-то в интернете тяжело найти grok-паттерны.
Чтобы это исправить, приложу две ссылки и сами grok-паттерны внизу данной статьи.

Финальное оттюненное выражение имеет вид:
%{TIMESTAMP_ISO8601:pg_aud_log_date} %{NOTSPACE:pg_aud_time_zone} %{NOTSPACE:pg_aud_db_name} %{NOTSPACE:pg_aud_db_user} %{IPORHOST:pg_aud_db_host}: LOG:%{SPACE}AUDIT: %{WORD:pg_aud_audit_type},%{INT:pg_aud_statement_id},%{INT:pg_aud_sub_statement_id},%{WORD:pg_aud_class},%{WORD:pg_aud_command},%{WORD:pg_aud_object_type},%{USERNAME:pg_aud_object_name},%{GREEDYDATA:pg_aud_statement}

Видно, что заменены все DATA на более жесткие паттерны и оставлен только один GREEDYDATA.
Если у вас тоже проблемы с производительностью разбора grok, постарайтесь свести к минимуму использование паттернов DATA и GREEDYDATA.

Описанные мероприятия нормализовали работу logstash:

При создании grok-выражений очень помогает онлайн grokdebug:
http://grokdebug.herokuapp.com/

Совет
Начинайте писать выражение с

%{GREEDYDATA:pg_aud_statement}

постепенно выделяя нужные фрагменты записи лога.

Grok-паттерны

https://github.com/hpcugent/logstash-patterns/blob/master/files/grok-patterns
https://github.com/elastic/logstash/blob/v1.4.2/patterns/grok-patterns

USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
INT (?:[+-]?(?:[0-9]+))
BASE10NUM (?[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
NUMBER (?:%{BASE10NUM})
BASE16NUM (?(?"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}

# Networking
MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})
CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
IPV4 (?/(?>[\w_%!$@:.,-]+|\\.)*)+
TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))
WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
URIHOST %{IPORHOST}(?::%{POSINT:port})?
# uripath comes loosely from RFC1738, but mostly from what Firefox
# doesn't turn into %XX
URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+
#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?

# Months: January, Feb, 3, 03, 12, December
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
MONTHNUM (?:0?[1-9]|1[0-2])
MONTHNUM2 (?:0[1-9]|1[0-2])
MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])

# Days: Monday, Tue, Thu, etc...
DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)

# Years?
YEAR (?>\d\d){1,2}
HOUR (?:2[0123]|[01]?[0-9])
MINUTE (?:[0-5][0-9])
# '60' is a leap second in most time standards and thus is valid.
SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)
TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}
ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
ISO8601_SECOND (?:%{SECOND}|60)
TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
DATE %{DATE_US}|%{DATE_EU}
DATESTAMP %{DATE}[- ]%{TIME}
TZ (?:[PMCE][SD]T|UTC)
DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE}
DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}
DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND}

# Syslog Dates: Month Day HH:MM:SS
SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
PROG (?:[\w._/%-]+)
SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
SYSLOGHOST %{IPORHOST}
SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}

# Shortcuts
QS %{QUOTEDSTRING}

# Log formats
SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}

# Log Levels
LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)

Просмотр привилегий пользователей к таблицам

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

SELECT rtg.grantee, rtg.privilege_type, t.table_name 
FROM information_schema.tables t
JOIN information_schema.role_table_grants rtg ON t.table_name = rtg.table_name
ORDER BY rtg.grantee

Результат запроса содержит логин пользователя, тип привилегии и название таблицы:

Установка PostgreSQL из исходников на Ubuntu 16.04

Склонируйте репозиторий PostgreSQL

git clone https://github.com/postgres/postgres.git

После окончания загрузки файлов перейдите в созданную директорию

cd postgres

Переключитесь на ветку REL_10_STABLE

git checkout REL_10_STABLE

Перед непосредственно сборкой проект нужно установить вспомогательно ПО.

sudo apt-get install libreadline-dev
sudo apt-get install bison
sudo apt-get install flex

Сконфигурируйте проект

./configure

Соберите проект

make

Установить postgres

sudo make install

Переключитесь в режим супер-пользователя:

sudo su

Добавьте нового пользователя postgres:

adduser postgres

Задайте пароль для пользователя postgres:

passwd postgres

Создайте директорию для данных postgres:

mkdir /usr/local/pgsql/data

Поменяйте владельца и группу для директории:

chown postgres:postgres /usr/local/pgsql/data

Далее поменяйте пользователя на postgres:

su - postgres

Проинициализируйте новый кластер баз данных:

/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data/

Установка Kubuntu c установочной флешки

Делаем на Kubuntu установочную флешку для установки Kubuntu (простите за тавтологию).
Для начала устанавливаем UNetbootin на странице https://unetbootin.github.io/linux_download.html можно его скачать, там же есть инструкция по установке.
Вот так выглядит главное окно программы:

Записываемый на флешку образ можно скачать либо по сети, либо выбрать iso на вашем компьютере.

Первая проблема, которую можно поймать:
/dev/sdb1 не смонтирован

Необходимо сначала примонтировать USB-накопитель /dev/sdb1 к точке монтирования. 
Большинство дистрибутивов делают это автоматически после переподключения USB-накопителя.

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

Также, желательно, чтобы в названии флешки были только латинские буквы.

После того, как вы успешно записали флешку, вставили ее компьютер, отключили в BIOS режим UEFI, можете поймать ошибку

(initramfs) Unable to find a medium containing a live file system

Проблема заключается в использовании USB 3.0.
У меня на ноутбуке HP все три USB-порта версии 3.0.
Советуют в BIOS отключить поддержку 3.0 в пользу 2.0.
Но на этом ноуте нет такой опции в BIOS.
А флешка у меня 16 GB USB 3.0.

С флеш-картой USB 2.0 загружался установщик, но где-то посередите он падал с ошибкой.

В конце концов записал ISO на компакт-диск и установил с переносного DVD-ROM.

Target lists can have at most 1664 entries в PostgreSQL

Давно не писал… На прошлой неделе поймал такую ошибку:

ERROR: target lists can have at most 1664 entries

Данная ошибка возникла из-за того, что появилось много зависимостей между сущностями(Entity).
Когда идет запрос данных через Hibernate, то JOIN’ится много таблиц, и общее количество получаемых полей начинает превышать 1664. В нашем случае это было около 1700 полей. Такое разумное ограничение есть у PostgreSQL.
Решить можно такими путями:
1) Переписать запросы, взяв только используемые поля;
2) Сделать некоторые поля Lazy.
Скорее всего, такая проблема и у вас решится вторым путем.

Удаление числа из строки

Иногда мы пользуемся трюком, сохраняя числа (ID из базы данных) в строке через запятую.
Например: «1024,15,8,0,55».
И теперь, допустим, какое-то число из этого списка не нужно, и его необходимо убрать из исходной строки. Как это реализовать?

Первый вариант:
1) разбить строку по запятой
2) отфильтровать удаляемое число
3) собрать строку заново

Второй вариант (допустим, удаляем число 55):
1) вырезать из строки подстроки вида: ,55,
2) удаляем подстроки вида 55, и ,55

Реализация 1-го варианта (Java):

String val = "55";
String collect = Arrays.stream(
        "55,1024,55,15,8,0,55".split(","))
        .filter(v -> !v.equals(val))
        .collect(Collectors.joining(","));
System.out.println(collect);//1024,15,8,0

Реализация 2-го варианта (Java):

int del = 55;
String result = "55,1024,55,15,8,0,55"
        .replaceAll("," + del + ",", ",")
        .replaceAll(",?" + del + ",?", "");
System.out.println(result);//1024,15,8,0

Некоторые вещи про закачку файлов

В любом web-проекте в определенное время возникает вопрос как загружать/скачивать файлы. В этой статье рассмотрим важные аспекты этих процедур:

  • Можно загружать файлы не только в виде байт, но и формате base64.
  • Файл загружается на веб-сервер и сохраняется как временный файл
  • При передаче файла на сервер используется кодирование multipart/form-data
  • Из временного файла можно получить оригинальное название файла, байты файла, тип содержимого(ContentType), размер.
  • На основе байтов можно рассчитать хэш. Например, алгоритмом MD5, SHA-1 или SHA-256.
  • Хэш файла может пригодиться при отдаче файла, чтобы проверить целостность.
  • При отдаче файла следует указать HTTP-заголовок Content-Disposition. Параметр inline сообщает браузеру, что файл можно открыть непосредственно в браузере, если конечно позволяет тип файла. Параметр attachment сообщает, что файл должен скачиваться. Также в Content-Disposition передается наименование файла, которое лучше кодировать с помощью URL Encoding.
  • Если в браузере вы хотите отобрать процесс загрузки файла, например, в процентах, то проще всего это сделать через javascript, который может отслеживать кол-во отправленных байтов.

Бинарный алгоритм вычисления НОД

Наибольшим общим делителем (НОД) для двух целых чисел называется наибольший из их общих делителей.
Например: для чисел 8 и 12 наибольший общий делитель равен 4.

Ранее мы уже рассматривали нахождение НОД с помощью Алгоритма Евклида:

long gcd(long a,long b){
	return b == 0 ? a : gcd(b,a % b);		
}

Сейчас рассмотрим Бинарный алгоритм Евклида, который быстрее обычного алгоритма Евклида.
Бинарный алгоритм основан на следующих свойствах НОД:

НОД(2m, 2n) = 2 НОД(m, n),
НОД(2m, 2n+1) = НОД(m, 2n+1),
НОД(-m, n) = НОД(m, n)

Теперь реализуем этот алгоритм на Java:

static int gcd(int m, int n){
    if(m == 0) return n;
    if(n == 0) return m;
    if(n == m) return n;
    if(m == 1) return 1;
    if(n == 1) return 1;
    boolean em = (m & 1) == 0;
    boolean en = (n & 1) == 0;
    if(em && en) return gcd(m >> 1, n >> 1) << 1;
    if(em) return gcd(m >> 1, n);
    if(en) return gcd(m, n >> 1);
    if(n > m) return gcd((n - m) >> 1, m);
    return gcd((m - n) >> 1, n);
}

Для ускорения деления на два используется сдвиг битов на позицию вправо(>> 1), для умножения на два используется сдвиг на позицию влево (<< 1). Проверка четности числа осуществляется проверкой последнего бита числа (m & 1), если выражение равно 0, то число четно, иначе нечетно.

Как из Java обратиться к сервису по протоколу HTTPS

Это юбилейная 100-я статья!!!

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

Для начала открываем сайт в браузере:

В адресной строке браузера рядом с текстом https:// есть иконка, нажав на которую появится возможность просмотреть / скачать / экспортировать сертификат.
Нужно экспортировать сертификат в файл test.crt

Файл будет иметь примерно такое содержание:

-----BEGIN CERTIFICATE-----
MIIEdjCCA16gAwIBAgIUGyZeQnd4LMFso5FQwrzjHmrNWVswDQYJKoZIhvcNAQEF
utCwINC/0L7QtNC70LjQvdC90L7RgdGC0Lgg0YHQtdGA0LLQtdGA0LAwMgYDVR0f
KqOTBEhH50jwo6WaQIUrD54ElD5gVO3VIT+eAMZm0HzXF+NKpkNaiR1b
-----END CERTIFICATE-----

Далее необходимо выполнить следующую команду:

sudo keytool -import -alias yourname -file test.crt
 -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

-alias — произвольное уникальное название
-file — путь к файлу
-keystore — путь к хранилищу сертификатов Java
$JAVA_HOME — путь к домашней директории Java, которую вы используете для запуска вашего приложения
-storepass — пароль к хранилищу, по умолчанию changeit

Получение содержимого текстового файла (Spring Boot)

Вот уже несколько месяцев последние два проекта делаю с использованием Spring Boot.
Бывает так, что нужно интегрироваться с внешней системой, но эта система не готова по каким-то причинам, а нужно показать свой функционал, в этом случае пишем заглушку. Т.к. в моем случае внешний сервис давал данные в формате JSON, я решил положить пример ответа сервиса в ресурсы (resource) по пути files/cities.json.

Далее встал вопрос как его вытянуть. Погуглил, в итоге собрал такую функцию (сперва получаем контекст приложения, из которого получаем ресурс):

private static String getFileContent(String filePath) throws IOException {
    ApplicationContext appContext =
        new ClassPathXmlApplicationContext(new String[] {});
 
    Resource resource = appContext.getResource(filePath);
 
    StringBuilder sb = new StringBuilder();
    BufferedReader br = null;
    try{
        br = new BufferedReader(
            new InputStreamReader(resource.getInputStream(), "UTF-8"));
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
    }finally {
        if(br != null) try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return sb.toString();
}

Пример вызова функции:

try {
    String text = getFileContent("files/cities.json");
} catch (IOException e) {
    e.printStackTrace();
}

Я использовал чтение файла для временной заглушки, но можно использовать и для других целей.
p.s. Обратите внимание, что в функции указана кодировка UTF-8. Можно её не указывать, тогда возьмется кодировка системы. Кодировку можно указать и при запуске приложения:

java -Dfile.encoding=UTF8

Совет: лучше в функции явно прописать UTF-8, если, конечно, она вам нужна.