LINUX.ORG.RU
Форум — Admin  

Сколько ошибок с прошлого раза

 , ,


0

3

Программа создает новые и дописывает прежние файлы журналов в /var/log/app/*.log

Необходимо усовершенстовать скрипт (или изменить подход), что бы считать сколько ошибок появилось с прошлого запуска.

grep -r "ERROR" /var/log/app/|wc -l

★★★★★

Последнее исправление: petav (всего исправлений: 1)

Если «после предыдущей» имеется в виду конкретное время (в unix time или там в YYYY-MM-DDTHH:MM:SS, или ещё как), то

grep -A 999999999 "ВРЕМЯ_ТВОЕГО_ПРОШЛОГО_ЗАПУСКА" имя_файла

Если такой инфы нет, то… никак? Ну или нужны подробности, что именно разделяет записи в логе от прошлогоо запуска, позапрошлого и нынешнего.

CrX ★★★★★
()
Ответ на: комментарий от CrX

Понял.

Пример строки в файле:

[ERROR] 2026/09/26 05:30:51 text
Милисекунды возможно добавить.

petav ★★★★★
() автор топика

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

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

1) Если всё это происходит в рамках одного сеанса работы сервера (т.е. комп не перезапускается в середине), то удобнее будет сделать демоном, который открывает все логфайлы в этой директории, следит за их размерами и парсит новопоявившиеся строки. В том числе - обрабатывает события «размер уменьшился» и «у файла сменился номер inode» - это значит что лог скорее всего ротировали.

2) Если надо более долговременно, то можно рядом с каждым qwe.log создавать qwe.metadata и хранить в нём последний виденный его размер и inode number.

firkax ★★★★★
()

или изменить подход

This. Не пытайтесь изобрести Elastic/Opensearch. Его давно уже написали.

ugoday ★★★★★
()

После завершения приложения добавлять в лог разделитель. По нему (им/между ними) и вычленять. Если саму программу трогать нельзя и она просто наваливает в лог всё подряд.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Постановка вопроса подсказывает, что в данном случае автору будет проще использовать избыточно мощные готовые инструменты. Иначе бы он просто написал скрипт на awk и не беспокоил бы благородное собрание.

ugoday ★★★★★
()

Спасибо всем. Проверил себя. В логах есть признаки, что части программы завершили работу, именно эти файлы (завершенные) и буду обрабатывать на предмет наличия ошибок.

petav ★★★★★
() автор топика
Последнее исправление: petav (всего исправлений: 2)

Можно написать скрипт, который бы писал сколько ошибок произошло за каждый час/день/неделю/итд. Типа гистограммы.

kaldeon ★★
()

Если это будет складываться в какую-то систему мониторинга типа прометеуса, то там есть метрика типа counter, а разница между отсчетами считается при выборке и отображении

cobold ★★★★★
()

Короче, судя по этому и прошлому запросу, тебе таки нужен fluentbit.

Впрочем, с удовольствием побуду зрителем на этой увлекательном мероприятии, если надо все на коленке писать.

bbc69
()
Ответ на: комментарий от LINUX-ORG-RU

Я бы добавил, что даже если так, есть смысл посмотреть на готовую реализацию и утащить себе например архитектуру. А не выдумывать, как будто эта задача только ему в голову пришла.

bbc69
()

есть модная консолькая cli утилитка duckdb открывай ею лог как csv и опрашивай - для скорости можно лог ухранять в паркете каком

как раз мелко таблцу с результатом и дельтой - тебе дельта с прошлого опроса эклекментарно

qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 1)

Программа создает новые и дописывает прежние файлы журналов в /var/log/app/*.logНеобходимо усовершенстовать скрипт (или изменить подход), что бы считать сколько ошибок появилось с прошлого запуска.

grep -r «ERROR» /var/log/app/|wc -l

+duckdb

  • что то на иишном для медитации и добиливания:

#!/usr/bin/env bash
set -e

# Путь к базе данных DuckDB, где хранится состояние (кол-во строк с прошлого раза)
STATE_DB="log_state.db"

# 1. Инициализируем таблицу состояний, если её ещё нет
duckdb "$STATE_DB" "CREATE TABLE IF NOT EXISTS file_offsets (filename VARCHAR PRIMARY KEY, last_row_count BIGINT);"

# 2. Выполняем один быстрый SQL-запрос, который:
#    - Считывает текущие логи и нумерует строки внутри каждого файла
#    - Сравнивает текущие номера строк с сохраненными в file_offsets
#    - Фильтрует по слову 'ERROR' только НОВЫЕ строки
#    - Выводит количество новых ошибок
#    - Обновляет таблицу состояний (сохраняет текущую длину файлов)
duckdb "$STATE_DB" << 'EOF'
-- Шаг A: Читаем все логи на лету, добавляя имя файла и порядковый номер строки
CREATE TEMP TABLE current_logs AS 
SELECT 
    filename,
    row_number() OVER (PARTITION BY filename) as row_num,
    column0 as log_line
FROM read_csv(
    '/var/log/app/*.log', 
    header=false, 
    columns={'column0': 'VARCHAR'}, 
    filename=true,
    on_error='return_null'
);

-- Шаг B: Считаем и выводим дельту (сколько ошибок появилось с прошлого раза)
SELECT 
    COALESCE(SUM(CASE WHEN cl.log_line LIKE '%ERROR%' THEN 1 ELSE 0 END), 0) as new_errors_count
FROM current_logs cl
LEFT JOIN file_offsets fo ON cl.filename = fo.filename
WHERE cl.row_num > COALESCE(fo.last_row_count, 0);

-- Шаг C: Считаем текущий размер файлов (максимальный номер строки)
CREATE TEMP TABLE new_offsets AS 
SELECT filename, MAX(row_num) as current_row_count 
FROM current_logs 
GROUP BY filename;

-- Шаг D: Обновляем состояние в базе данных (INSERT OR REPLACE)
INSERT OR REPLACE INTO file_offsets (filename, last_row_count)
SELECT filename, current_row_count FROM new_offsets;
EOF

если более серьёзно (пре все данные очевидно можно в какой cli sqlite duckdb

то 1. делай ls -l для снимка размеров лог файлов по маске (исходим что они только дописываются и точность момента не критична) и сохраняй в строку таблицы текущие концы

  1. магией определеяшь отдельные счётчики(либо полностью если малы либо от предыдущего отсч\та который сохранил позиции предыдущих концов для уже существующих ну либо ноль для новых - вот и дефалтдикт) и сумму

  2. сохраняешь дельту и позиции

2 итерация с предыдущей интервенцией:

  • что то на иишном для медитации и вычленении зерна(жемчужины) из сего вчерашнего сена :
#!/usr/bin/env bash
set -e

LOG_DIR="/var/log/app"
LOG_MASK="*.log"
STATE_DB="log_state.db"

# 0. Время текущего замера (snapshot_id)
NOW=$(date +%s)

# 1. Создаем служебные таблицы, если их нет
duckdb "$STATE_DB" << 'EOF'
CREATE TABLE IF NOT EXISTS file_positions (
    filename VARCHAR,
    byte_offset BIGINT,
    scan_time BIGINT
);
CREATE TABLE IF NOT EXISTS delta_history (
    scan_time BIGINT,
    filename VARCHAR,
    new_errors BIGINT,
    bytes_processed BIGINT
);
EOF

# 2. ШАГ 1: Делаем снимок текущих размеров файлов через find + stat (надежнее, чем парсить ls)
# Формат вывода: "имя_файла размер_в_байтах"
CURRENT_SNAPSHOT=$(find "$LOG_DIR" -maxdepth 1 -name "$LOG_MASK" -type f -exec stat --format="%n %s" {} +)

if [ -z "$CURRENT_SNAPSHOT" ]; then
    echo "Лог-файлы по маске не найдены."
    exit 0
fi

# Передаем снимок в DuckDB через временный CSV-конвейер
duckdb "$STATE_DB" << EOF
-- Загружаем текущий снимок размеров во временную таблицу
CREATE TEMP TABLE current_snapshot (filename VARCHAR, current_size BIGINT);
INSERT INTO current_snapshot 
SELECT column0, CAST(column1 AS BIGINT) 
FROM read_csv_auto(FROM_STRING('$CURRENT_SNAPSHOT'), sep=' ');

-- Находим предыдущие позиции (самый свежий offset для каждого файла)
CREATE TEMP TABLE previous_positions AS 
SELECT filename, byte_offset 
FROM (
    SELECT filename, byte_offset, 
           ROW_NUMBER() OVER (PARTITION BY filename ORDER BY scan_time DESC) as rn
    FROM file_positions
) WHERE rn = 1;

-- ШАГ 2 & 3: Магия вычисления дельт для каждого файла (реализация defaultdict)
CREATE TEMP TABLE tasks AS 
SELECT 
    cur.filename,
    COALESCE(prev.byte_offset, 0) as start_offset,  -- Если файла не было, читаем с 0 (defaultdict)
    cur.current_size as end_offset,
    (cur.current_size - COALESCE(prev.byte_offset, 0)) as bytes_to_read
FROM current_snapshot cur
LEFT JOIN previous_positions prev ON cur.filename = prev.filename
WHERE cur.current_size > COALESCE(prev.byte_offset, 0); -- Обрабатываем только увеличившиеся файлы
EOF

# 3. Читаем ТОЛЬКО дельты файлов и считаем ошибки
# Чтобы передать бинарные куски в DuckDB, мы сгенерируем SQL-запрос динамически
# Используем встроенную функцию read_csv с параметрами skip и length (в байтах!)

DUCK_QUERIES=$(duckdb "$STATE_DB" -noheader -list << 'EOF'
SELECT 
    'SELECT ' || quote_literal(filename) || ' as fname, COUNT(*) as err_cnt FROM read_csv(' || 
    quote_literal(filename) || ', skip=' || start_offset || ', length=' || bytes_to_read || 
    ', header=false, columns={''line'': ''VARCHAR''}, on_error=''return_null'') WHERE line LIKE ''%ERROR%'' UNION ALL '
FROM tasks;
EOF
)

# Если обновлений в файлах нет, просто выходим
if [ -z "$DUCK_QUERIES" ]; then
    echo "--- Нет новых данных с прошлого запуска ---"
    echo "Всего новых ошибок: 0"
    exit 0
fi

# Убираем последний 'UNION ALL ' для валидного SQL
FINAL_SCAN_QUERY=$(echo "$DUCK_QUERIES" | sed 's/ UNION ALL $/;/')

# Запускаем финальный расчет дельты и сохранение позиций
duckdb "$STATE_DB" << EOF
-- 1. Считаем ошибки по дельтам
CREATE TEMP TABLE scanned_errors AS $FINAL_SCAN_QUERY;

-- 2. Фиксируем дельту в историю
INSERT INTO delta_history
SELECT 
    $NOW as scan_time,
    t.filename,
    COALESCE(e.err_cnt, 0) as new_errors,
    t.bytes_to_read as bytes_processed
FROM tasks t
LEFT JOIN scanned_errors e ON t.filename = e.fname;

-- 3. Сохраняем новые позиции концов файлов для следующего раза
INSERT INTO file_positions
SELECT filename, current_size, $NOW FROM current_snapshot;

-- Выводим красивый результат в консоль
SELECT '📊 Лог:' as "Файл", 'Новых ошибок' as "Метрика", 'Прочитано байт' as "Объем"
UNION ALL
SELECT '  ' || filename, CAST(new_errors AS VARCHAR), CAST(bytes_processed AS VARCHAR)
FROM delta_history WHERE scan_time = $NOW;

SELECT 'ИТОГО НОВЫХ ОШИБОК:' as "Результат", SUM(new_errors) as "Сумма" 
FROM delta_history WHERE scan_time = $NOW;
EOF
 

если время перечтения всех логов меньше попеременного кочегаренья баша и утки возможно первый вариант и потянтей и вжух-удобней

[upd finality] для по играть и пообмазываться логами вот наряду с ранее упомянутым fluentbit есть lnav

qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 4)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.