English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Here are some tips for handling log file extraction. Suppose we are looking at some Enterprise Splunk extraction. We can use Splunk to explore the data. Or we can get a simple extraction and play with these data in Python.
Запуск различных экспериментов в Python seems to be more effective than trying to perform such exploratory operations in Splunk. This is mainly because we can do anything with the data without any restrictions. We can create very complex statistical models in one place.
Теоретически, мы можем сделать много исследований в Splunk. У него есть различные функции отчетов и анализа.
Но...
Использование Splunk требует предположения, что мы знаем, что ищем. В многих случаях мы не знаем, что ищем: мы исследуем. Может быть, есть некоторые迹象, что некоторые RESTful API работают медленно, но это не все. Как мы продолжим?
Первым шагом является получение исходных данных в формате CSV. Как это сделать?
Чтение исходных данных
Мы начнем с 包装 CSV.DictReader объектом с помощью некоторых дополнительных функций.
Пуритане объектно-ориентированного программирования могут возразить против этой стратегии. «Почему не расширить DictReader?», — спрашивают они. У меня нет хорошего ответа. Я предпочитаю функциональное программирование и ортогональность компонентов. Для чисто объектно-ориентированного подхода нам пришлось бы использовать более сложные гибриды для достижения этого.
Общий подход к обработке логов у нас такой.
with open("somefile.csv") as source: rdr = csv.DictReader(source)
Это позволяет нам читать извлечения Splunk в формате CSV. Мы можем итеративно читать строки из читателя. Это хитрость #1. Это не очень сложно, но мне это нравится.
with open("somefile.csv") as source: rdr = csv.DictReader(source) for row in rdr: print( "{host} {ResponseTime} {source} {Service}".format_map(row) )
Мы можем - в определенной степени - представлять исходные данные в полезном формате. Если мы хотим улучшить видoutput, мы можем изменить строку формата. Это может быть что-то вроде «{host:30s} {ResponseTime:8s} {source:s}» или что-то подобное.
Фильтрация
Часто мы получаем слишком много данных, но нам нужно только смотреть на подмножество. Мы можем изменить фильтр Splunk, но до завершения нашего исследования чрезмерное использование фильтров может быть раздражающим. В Python фильтрация намного проще. Как только мы узнаем, что нам нужно, мы можем завершить это в Splunk.
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') for row in rdr_perf_log: print("{host} {ResponseTime} {Service}".format_map(row))
Мы добавили генератор выражения для фильтрации исходных строк, который может обрабатывать значительный подмножество.
Проекция
В некоторых случаях мы добавляем дополнительные столбцы исходных данных, которые мы не хотим использовать. Поэтому мы удаляем эти данные, проецируя их на каждую строку.
Принципиально, Splunk никогда не создает пустые столбцы. Однако, логи RESTful API могут привести к тому, что данные будут содержать большое количество заголовков столбцов, основанных на части URI запроса. Эти столбцы будут содержать строку данных из одного запроса, использующего этот агентский ключ. Для других строк в этом столбце нет никакой пользы. Поэтому их нужно удалить.
Мы также можем сделать это с помощью генераторного выражения, но это будет немного дольше. Генераторная функция легче для чтения.
def project(reader): for row in reader: yield {k:v for k,v in row.items() if v}
Мы создали новый словарь строк из части исходного читателя. Мы можем использовать его для 包装 вывода наших фильтров.
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') for row in project(rdr_perf_log): print("{host} {ResponseTime} {Service}".format_map(row))
Это уменьшит количество незадействованных столбцов, видимых в цикле for.
Изменение символов
Символ row['source'] становится несколько громоздким. Использование types.SimpleNamespace лучше, чем использование словаря. Это позволяет нам использовать row.source.
Это очень полезный трюк для создания более полезных вещей.
rdr_ns = (types.SimpleNamespace(**row) for row in reader)
Мы можем сложить это в такую последовательность шагов.
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) for row in rdr_ns: print("{host} {ResponseTime} {Service}".format_map(vars(row)))
Обратите внимание на наше небольшое изменение в методе format_map(). Из свойств SimpleNamespace мы добавили функцию vars() для извлечения словаря.
Мы можем записать это в функцию с помощью другой функции, чтобы сохранить грамматическую симметрию.
def ns_reader(reader): return (types.SimpleNamespace(**row) for row in reader)
Действительно, мы можем записать это в структуру lambda, которую можно использовать как функцию.
ns_reader = lambda reader: (types.SimpleNamespace(**row) for row in reader)
Хотя функция ns_reader() и использование lambda в ns_reader() аналогичны, документация и doctest-тестирование для lambda несколько сложнее. Поэтому следует избегать использования структуры lambda.
Мы можем использовать map(lambda row: types.SimpleNamespace(**row), reader). Кто-то любит этот генераторный выражение.
Мы можем использовать подходящий цикл for и внутреннее выражение yield, но писать большие выражения из маленьких, кажется, не имеет большого смысла.
У нас есть много вариантов, потому что Python предоставляет так много функциональных программных функций. Хотя мы не часто рассматриваем Python как функциональный язык. Но у нас есть несколько способов обработки простых маппингов.
Маппинг: преобразование и производные данных
Часто у нас есть очень明显的 список преобразований данных. Кроме того, у нас будет список越来越多 производных данных. Производные данные будут динамическими и основываться на различных гипотезах, которые мы тестируем. Каждую раз, когда у нас есть эксперимент или проблема, мы можем изменить производные данные.
Каждый из этих шагов: фильтрация, проекция, преобразование и производные являются этапами "map" в管道 map-reduce. Мы можем создать несколько较小的 функций и применить их к map(). Поскольку мы обновляем объект с состоянием, мы не можем использовать обычную функцию map(). Если мы хотим реализовать более чистый стиль функционального программирования, мы будем использовать неизменяемый namedtuple вместо изменяемого SimpleNamespace.
def convert(reader): for row in reader: row._time = datetime.datetime.strptime(row.Time, "%Y-%m-%dT%H:%M:%S.%F%Z") row.response_time = float(row.ResponseTime) yield row
В процессе нашего исследования мы будем корректировать的主要内容 функции преобразования. Возможно, мы начнем с самых маленьких преобразований и производных. Мы продолжим исследование с помощью вопросов типа "Эти ли это правильные?". Когда мы обнаружим, что что-то не работает, мы будем извлекать это.
Наш整个过程 обработки показан следующим образом:
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) rdr_converted = convert(rdr_ns) for row in rdr_converted: row.start_time = row._time - datetime.timedelta(seconds=row.response_time) row.service = some_mapping(row.Service) print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
Обратите внимание на изменение的主要内容. Функция convert() генерирует确定的 значения. Мы добавили некоторые дополнительные переменные в цикл for, и мы не можем быть на 100% уверены. Перед обновлением функции convert() мы посмотрим, полезны ли они (даже правильны ли они).
Уменьшение
В вопросе уменьшения можно использовать немного другой способ обработки. Нам нужно重构 наш предыдущий пример и сделать его генераторной функцией.
def converted_log(some_file): with open(some_file) as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) rdr_converted = convert(rdr_ns) for row in rdr_converted: row.start_time = row._time - datetime.timedelta(seconds=row.response_time) row.service = some_mapping(row.Service) yield row
Затем вместо print() был использован yield.
Это еще одна часть рефакторинга.
for row in converted_log("somefile.csv"): print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
Идеально, если бы все наши программы были такими. Мы используем генераторные функции для генерации данных. В конечном итоге данные отображаются полностью изолированно. Это позволяет нам более свободно перерабатывать и изменять обработку.
Теперь мы можем сделать что-то, например, собрать строки в объект Counter() или возможно вычислить некоторые статистические данные. Мы можем использовать defaultdict(list) для группировки строк по сервису.
by_service= defaultdict(list) for row in converted_log("somefile.csv"): by_service[row.service] = row.response_time for svc in sorted(by_service): m = statistics.mean( by_service[svc] ) print( "{svc:15s} {m:.2f}".format_map(vars()) )
Мы решили создать конкретный объект списка здесь. Мы можем использовать itertools для группировки ответного времени по сервисам. Это выглядит как правильное функциональное программирование, но это указывает на некоторые ограничения в Pythonic функциональном программировании. Либо我们必须 сортировать данные (создавать объект списка), либо создавать списки при группировке данных. Для выполнения нескольких различных статистических вычислений группировка данных с помощью конкретных списков обычно легче.
Теперь мы делаем две вещи, а не просто выводим объект строки.
Создайте некоторые локальные переменные, такие как svc и m. Мы можем легко добавить изменения или другие меры.
Использование функции vars() без параметров создает словарь из локальных переменных.
Использование vars() без параметров — это удобный прием, как и locals(). Это позволяет нам просто создавать любые локальные переменные, которые мы хотим, и включать их в форматированный вывод. Мы можем внедряться в различные статистические методы, которые我们认为 могут быть связаны.
Поскольку наш базовый обработчик циклов направлен на строки в converted_log (“somefile.csv”), мы можем исследовать множество вариантов обработки с помощью небольшого, легко модифицируемого скрипта. Мы можем исследовать некоторые гипотезы, чтобы понять, почему некоторые RESTful API работают медленно, а другие быстро.
Резюме
Как уже было сказано, я хочу представить вам exploratory data analysis (функциональный) в Python, надеюсь, это поможет вам. Если у вас есть какие-либо вопросы, пожалуйста, оставляйте комментарии, я постараюсь ответить вам как можно скорее. Вновь благодарю всех за поддержку呐喊 урока!
Заявление: содержимое этой статьи взято из Интернета, авторские права принадлежат соответствующему автору. Контент предоставлен пользователями Интернета, самостоятельно загружен, сайт не имеет права собственности, не был обработан вручную и не несет ответственности за связанные с этим юридические вопросы. Если вы обнаружите подозрительное нарушение авторских прав, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма замените # на @),并提供相关证据. При обнаружении факта нарушения авторских прав сайт незамедлительно удаляет涉嫌侵权的内容.