Забудьте о regex. Научите ИИ искать в логах на человеческом языке
Вы когда-нибудь тратили полчаса на составление регулярного выражения, чтобы найти в гигабайтах логов одну строчку? Тот самый момент, когда вы понимаете, что \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z.*ERROR.*user_id=\d+ должно было работать, но не работает. Потому что в логах оказались миллисекунды с двумя цифрами, а не с тремя.
Регулярные выражения — это боль. Они точны, но требуют экспертизы, которую не хочется держать в голове в 3 часа ночи, когда падает продакшен.
Что если вместо этого вы могли бы написать: "Найди все ошибки авторизации для пользователя 12345 за последний час"? И получить готовый regex, который сразу работает?
Это не фантастика. Это AI-поиск в логах. И он работает уже сегодня. Я покажу, как сделать его production-ready, безопасным и быстрым.
Почему простой промпт "сгенерируй regex" не работает
Кажется, что задача простая. Берем GPT-4, даем ему описание поиска, получаем regex. На практике вы столкнетесь с тремя проблемами:
- ИИ не знает структуру ваших логов. Он может сгенерировать regex для идеализированного формата, который не совпадет с реальностью.
- Безопасность Extension Host. Если вы встраиваете это в VS Code расширение, нельзя просто слать логи в OpenAI. Они могут содержать чувствительные данные.
- Контекст из случайных строк. ИИ плохо работает с абсолютно случайными данными. Ему нужны паттерны, а не шум.
Вот как выглядит типичная неудача:
// ПЛОХОЙ ПРОМПТ
const badPrompt = `Сгенерируй regex для поиска ошибок 500 в логах`;
// Результат: /ERROR.*500/g
// Проблема: пропустит "HTTP 500", "status=500", "code:500"ИИ не видит примеров ваших логов. Он генерирует абстракцию, которая почти никогда не подходит.
Архитектура: как дать ИИ контекст, не отправляя логи в облако
Ключевая идея: мы не отправляем сами логи в LLM. Мы отправляем образцы структуры и метаданные формата.
Вот как выглядит безопасная архитектура:
- Пользователь пишет запрос на естественном языке
- Система анализирует первые 100 строк логов локально
- Извлекает паттерны: форматы дат, уровни логирования, структура сообщений
- Собирает промпт с этими паттернами + запрос пользователя
- Отправляет только промпт (без самих логов) в LLM через Vercel AI SDK
- Получает regex, валидирует его на локальных образцах
- Применяет regex ко всем логам
Никакие данные не уходят за пределы вашего контроля. Это критично для VS Code расширений, которые работают с корпоративными логами.
1Подготовка: извлекаем паттерны из логов
Сначала напишем функцию, которая анализирует логи и вытаскивает структуру. Мы ищем:
- Форматы дат и времени
- Уровни логирования (ERROR, WARN, INFO, DEBUG)
- Шаблоны идентификаторов (user_id, request_id, session_id)
- Разделители между полями
function extractLogPatterns(logLines) {
const patterns = {
timestampFormats: [],
logLevels: new Set(),
commonFields: {},
separators: new Set()
};
// Анализируем первые 50 строк для скорости
const sampleLines = logLines.slice(0, 50);
sampleLines.forEach(line => {
// Ищем timestamp - самый распространенный паттерн
const timestampMatch = line.match(
/(\d{4}[-/]\d{2}[-/]\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d{1,6})?(Z|[+-]\d{2}:?\d{2})?)/
);
if (timestampMatch) {
patterns.timestampFormats.push(timestampMatch[1]);
}
// Ищем уровни логирования
const levelMatch = line.match(/\b(ERROR|WARN|INFO|DEBUG|FATAL|TRACE)\b/);
if (levelMatch) {
patterns.logLevels.add(levelMatch[1]);
}
// Ищем common fields типа user_id=123 или requestId="abc"
const fieldMatches = line.matchAll(/(\w+)[=:"']([^\s"']+)/g);
for (const match of fieldMatches) {
const [_, key, value] = match;
if (!patterns.commonFields[key]) {
patterns.commonFields[key] = new Set();
}
patterns.commonFields[key].add(value);
}
});
// Убираем дубликаты и преобразуем Set в массивы
patterns.timestampFormats = [...new Set(patterns.timestampFormats)];
patterns.logLevels = [...patterns.logLevels];
// Для common fields оставляем только ключи с несколькими значениями
Object.keys(patterns.commonFields).forEach(key => {
if (patterns.commonFields[key].size < 2) {
delete patterns.commonFields[key];
} else {
patterns.commonFields[key] = [...patterns.commonFields[key]].slice(0, 5);
}
});
return patterns;
}Эта функция работает локально, не отправляя данные в сеть. Она создает "отпечаток" формата ваших логов.
2Промпт-инжиниринг: учим ИИ думать как парсер логов
Теперь главное — промпт. Он должен дать ИИ достаточно контекста, но не перегружать его. Вот структура, которая работает:
function buildRegexPrompt(userQuery, logPatterns) {
return `Ты — эксперт по парсингу логов. Твоя задача — сгенерировать точное регулярное выражение для поиска в логах.
СТРУКТУРА ЛОГОВ:
- Форматы timestamp: ${logPatterns.timestampFormats.join(', ') || 'не обнаружено'}
- Уровни логирования: ${logPatterns.logLevels.join(', ') || 'не обнаружено'}
- Частые поля: ${Object.entries(logPatterns.commonFields)
.map(([key, values]) => `${key} (примеры: ${values.slice(0, 3).join(', ')}...)`)
.join('; ')}
ПРАВИЛА ГЕНЕРАЦИИ REGEX:
1. Используй named groups для важных полей: (?...), (?...)
2. Учитывай, что между полями могут быть пробелы разной длины
3. Логи чувствительны к регистру, если не указано иное
4. Предпочитай нежадные квантификаторы (*?, +?)
5. Экранируй специальные символы в тексте сообщений
6. Включай флаги: 'g' для глобального поиска, 'm' для multiline если нужно
ЗАПРОС ПОЛЬЗОВАТЕЛЯ: "${userQuery}"
Сгенерируй ТОЛЬКО регулярное выражение в формате /regex/flags.
Не добавляй объяснений, комментариев или дополнительного текста.`;
} Обратите внимание на ключевые моменты:
- Роль: "эксперт по парсингу логов" — задает контекст
- Структура логов: конкретные примеры из реальных данных
- Правила генерации: технические требования к regex
- Строгий формат ответа: только regex, без лишнего текста
Важно: если ваш AI-агент начинает "халтурить" и генерировать неточные regex, проблема может быть в недостатке контекста. В моей статье про проблемы контекста в бизнес-среде я разбираю эту тему подробно.
3Интеграция с Vercel AI SDK: безопасный вызов LLM
Vercel AI SDK дает удобный интерфейс для работы с разными провайдерами. Вот как выглядит вызов:
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function generateLogRegex(userQuery, logPatterns) {
const prompt = buildRegexPrompt(userQuery, logPatterns);
try {
const { text: regex } = await generateText({
model: openai('gpt-4-turbo-preview'),
prompt,
temperature: 0.1, // Низкая температура для детерминированности
maxTokens: 500,
});
// Валидируем, что ответ — валидное регулярное выражение
if (!regex.match(/^\/(?:[^\/\\]|\\.)*\/[gimuy]*$/)) {
throw new Error('Некорректный формат regex от ИИ');
}
// Тестируем на sample данных
const testResult = testRegexOnSamples(regex, sampleLogs);
if (testResult.matchCount === 0) {
// Если не нашел ничего, возможно regex слишком строгий
return await generateFallbackRegex(userQuery, logPatterns);
}
return {
regex,
testResults: testResult
};
} catch (error) {
console.error('Ошибка генерации regex:', error);
// Fallback: простой текстовый поиск
return {
regex: `/${escapeRegex(userQuery)}/gi`,
isFallback: true
};
}
}
function testRegexOnSamples(regexStr, samples) {
const regex = new RegExp(
regexStr.slice(1, regexStr.lastIndexOf('/')),
regexStr.slice(regexStr.lastIndexOf('/') + 1)
);
const matches = [];
samples.forEach((line, index) => {
if (regex.test(line)) {
matches.push({ line: line.substring(0, 100), index });
}
// Сбрасываем lastIndex для глобального regex
regex.lastIndex = 0;
});
return {
matchCount: matches.length,
matches: matches.slice(0, 5) // Первые 5 совпадений для демонстрации
};
}Температура 0.1 — это важно. Мы хотим детерминированные результаты, а не креативность. Если ИИ "выдумывает" форматы, которых нет в логах, поиск не сработает.
Ошибки, которые сломают вашу систему
Я видел десятки попыток внедрить AI-поиск в логах. Вот что идет не так:
| Ошибка | Последствие | Как исправить |
|---|---|---|
| Отправка полных логов в OpenAI | Утечка данных, блокировка API | Отправлять только паттерны, как показано выше |
| Температура > 0.5 | Разные regex для одинаковых запросов | Использовать temperature 0.1-0.2 |
| Нет валидации regex | Синтаксические ошибки, падение приложения | Тестировать на sample перед применением |
| Игнорирование multiline логов | Пропуск stack traces и исключений | Добавлять флаг 'm' и обрабатывать \n |
Самая коварная ошибка — доверять ИИ без проверки. Всегда тестируйте сгенерированный regex на подмножестве данных. Если он не находит ничего там, где должен находить, нужен fallback.
Производительность: когда AI-поиск тормозит
Генерация regex через LLM занимает 2-5 секунд. Это приемлемо для интерактивного поиска, но не для скриптов, которые обрабатывают терабайты логов.
Решение — кэширование. Хэшируйте пару (запрос пользователя, отпечаток формата логов) и сохраняйте результат. 80% запросов повторяются: "ошибки за сегодня", "медленные запросы", "пользователь X".
const regexCache = new Map();
async function getCachedRegex(query, patterns) {
const cacheKey = `${query}-${JSON.stringify(patterns)}`;
if (regexCache.has(cacheKey)) {
const cached = regexCache.get(cacheKey);
// Проверяем, не устарел ли кэш (формат логов мог измениться)
if (Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5 минут
return cached.regex;
}
}
const regex = await generateLogRegex(query, patterns);
regexCache.set(cacheKey, {
regex,
timestamp: Date.now()
});
// Ограничиваем размер кэша
if (regexCache.size > 100) {
const oldestKey = [...regexCache.keys()][0];
regexCache.delete(oldestKey);
}
return regex;
}С кэшированием повторные запросы выполняются за миллисекунды. Это меняет UX: пользователь не ждет генерации каждый раз.
Расширение для VS Code: полная реализация
Вот как выглядит основная логика расширения для поиска в логах:
// extension.js
const vscode = require('vscode');
const { getCachedRegex } = require('./regex-generator');
const { extractLogPatterns } = require('./log-analyzer');
export function activate(context) {
const provider = vscode.languages.registerCompletionItemProvider('log', {
async provideCompletionItems(document, position) {
// Триггерим AI-поиск при вводе "? "
const linePrefix = document.lineAt(position).text.substr(0, position.character);
if (!linePrefix.endsWith('? ')) return [];
const userQuery = linePrefix.slice(0, -2); // Убираем "? "
// Получаем весь текст логов
const fullText = document.getText();
const logLines = fullText.split('\n');
// Извлекаем паттерны (локально, безопасно)
const patterns = extractLogPatterns(logLines);
// Показываем индикатор загрузки
vscode.window.setStatusBarMessage('Генерация AI-поиска...');
try {
// Получаем regex через кэшированный AI-вызов
const { regex, testResults } = await getCachedRegex(userQuery, patterns);
// Создаем completion item с готовым regex
const item = new vscode.CompletionItem(`AI Search: ${userQuery}`);
item.insertText = regex;
item.detail = `Найдено совпадений в sample: ${testResults.matchCount}`;
item.documentation = new vscode.MarkdownString(`
**Сгенерированный regex:**
\`\`\`
${regex}
\`\`\`
**Тестовые совпадения:**
${testResults.matches.map(m => `- Строка ${m.index}: ${m.line}...`).join('\n')}
`);
return [item];
} catch (error) {
vscode.window.showErrorMessage(`Ошибка AI-поиска: ${error.message}`);
return [];
} finally {
vscode.window.setStatusBarMessage('');
}
}
});
context.subscriptions.push(provider);
}Пользователь вводит в логах "ошибки базы данных за последний час? ", получает автодополнение с готовым regex, нажимает Tab — и поиск выполняется.
Что делать, когда ИИ генерирует неправильный regex
Даже с идеальным промптом ИИ ошибается в 10-15% случаев. Нужна стратегия graceful degradation:
- Fallback на текстовый поиск: если regex не находит ничего, ищем просто подстроку
- Упрощение запроса: "ошибки базы данных" → "ERROR.*database"
- Логирование провалов: собирайте запросы, где ИИ ошибся, чтобы улучшить промпт
- Ручная коррекция: дайте пользователю возможность исправить сгенерированный regex
async function generateFallbackRegex(userQuery, patterns) {
// Упрощаем запрос: удаляем стоп-слова, оставляем ключевые
const keywords = userQuery
.toLowerCase()
.split(/\s+/)
.filter(word => !['найди', 'все', 'за', 'для', 'с'].includes(word))
.map(escapeRegex)
.join('.*');
// Добавляем контекст из паттернов
let regex = '';
if (patterns.logLevels.includes('ERROR')) {
regex = `ERROR.*${keywords}`;
} else {
regex = keywords;
}
return `/${regex}/gi`;
}Простой fallback лучше, чем полный отказ. Пользователь получит хоть какие-то результаты, даже если не идеальные.
Будущее: от regex к семантическому поиску
Следующий шаг — вообще отказаться от regex. Вместо преобразования естественного языка в регулярные выражения, преобразовывать логи в эмбеддинги и искать семантически.
Запрос "ошибки при оплате" найдет не только строки с "ERROR" и "payment", но и "transaction failed", "charge declined", "payment gateway timeout". Это требует векторной базы и эмбеддинг-модели, но становится все доступнее.
Пока что гибридный подход работает лучше: AI генерирует regex для точного поиска, а семантический поиск добавляется для сложных запросов. Как в дорожной карте RAG 2026 — комбинируем разные техники.
Главное — начать с чего-то работающего. С regex-генерацией через ИИ. Это дает немедленную пользу, не требуя инфраструктурной революции. А когда будете готовы к семантическому поиску, у вас уже будет понимание, как работать с AI в контексте логов.
Попробуйте. Сэкономленные 30 минут на составлении одного сложного regex окупят время на внедрение. Особенно в 3 часа ночи.