Преобразование файла журнала в CSV

#c# #csv #export-to-csv #powerpivot #weblog

#c# #csv #экспорт в csv #powerpivot #веб-журнал

Вопрос:

Мне нужно преобразовать файл журнала (веб-прокси-сервер Squid) в файл CSV, чтобы его можно было загрузить в powerpivot для анализа запросов. Итак, с чего мне начать, любая помощь была бы настоятельно оценена. Я должен использовать язык C # для этой задачи, журнал выглядит следующим образом:

Формат: Временная метка, прошедшее действие клиента / Размер кода, Иерархия идентификаторов URI метода / Из содержимого

1473546438.145 917 5.45.107.68 TCP_DENIED/403 4114 ПОЛУЧИТЬ http://atlantis.pennergame.de/pet / - НЕТ /- текст /html
1473546439.111 3 146.148.96.13 TCP_DENIED/403 4604 POST http://mobiuas.ebay.com/services/mobile/v1/UserAuthenticationService - НЕТ /- текст /html
1473546439.865 358 212.83.168.7 TCP_DENIED/403 3955 ПОЛУЧАЕМ http://www.theshadehouse.com/left-sidebar-post / - НЕТ /- текст /html
1473546439.985 218 185.5.97.68 TCP_DENIED/403 3600 ПОЛУЧИТЬ http://www.google.pl/search ? - НЕТ /- текст /html
1473546440.341 2 146.148.96.13 TCP_DENIED/403 4604 СООБЩЕНИЕ http://mobiuas.ebay.com/services/mobile/v1/UserAuthenticationService - НЕТ /- текст /html
1473546440.840 403 115.29.46.240 TCP_DENIED/403 4430 СООБЩЕНИЕ http://et.airchina.com.cn/fhx/consumeRecord/getCardConsumeRecordList.htm - НЕТ /- текст /html
1473546441.486 2 52.41.27.39 TCP_DENIED/403 3813 СООБЩЕНИЕ http://www.deezer.com/ajax/action.php - НЕТ /- текст /html
1473546441.596 2 146.148.96.13 TCP_DENIED/403 4604 СООБЩЕНИЕ http://mobiuas.ebay.com/services/mobile/v1/UserAuthenticationService - НЕТ /- текст /html

Комментарии:

1. Это формат CSV, в котором разделителем полей является « вместо ,

2. просто создайте цикл с числом от 0 до 3, заменив два пробела на один, а после цикла замените каждый пробел точкой с запятой. Затем запишите все в файл

3. Это не файл CSV. Просто что-то близкое к CSV. Спереди он имеет фиксированную ширину, а в конце вы можете увидеть несколько " - " разделителей.

4. Не понял вашу точку зрения, вы уверены, что для этого будет достаточно 3 циклов?

5. @HenkHolterman - — это данные, а не разделитель. В этом случае разделителем является «несколько пробелов». Power BI, вероятно, не справится с этим, но простого регулярного выражения достаточно, чтобы очистить его

Ответ №1:

Он уже близок к CSV, поэтому прочитайте его построчно и немного очистите каждую строку:

 ...
line = line
  .Replace("   ", " ")  // compress 3 spaces to 1
  .Replace("  ", " ")   // compress 2 spaces to 1
  .Replace("  ", " ")   // compress 2 spaces to 1, again
  .Replace(" ", "|")    // replace space by '|'
  .Replace(" - ", "|"); // replace  -  by '|'
  

Возможно, вы захотите изменить это для полей типа TCP_DENIED /403 .

это дает вам '|' разделенную строку. Легко конвертировать в любой разделитель, который вам нужен. Или разделить его:

 // write it out or process it further    
string[] parts = line.split('|');
  

Комментарии:

1. То есть вы хотите сказать, что мне не придется использовать regex для этой операции?

2. Регулярное выражение является более простым вариантом и использует НАМНОГО меньше памяти. Этот ответ создаст одну временную строку для каждой замены в строке, а затем 10 для каждого разделения. Это в 5 раз больше памяти для замен и примерно в 1 раз больше для разделения. За строку. Насколько велик ваш файл журнала?

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

4. Вот почему регулярное выражение в этом случае проще и намного дешевле

5. @PanagiotisKanavos — это строки с чрезвычайно коротким сроком службы, именно для этого оптимизирован GC. Регулярное выражение имеет свои преимущества, но не распространяйте эту ерунду о памяти.

Ответ №2:

 public static class SquidWebProxyServerCommaSeparatedWriter
{
    public static void WriteToCSV(string destination, IEnumerable<SquidWebProxyServerLogEntry> serverLogEntries)
    {
        var lines = serverLogEntries.Select(ConvertToLine);

        File.WriteAllLines(destination, lines);
    }

    private static string ConvertToLine(SquidWebProxyServerLogEntry serverLogEntry)
    {
        return string.Join(@",", serverLogEntry.Timestamp, serverLogEntry.Elapsed.ToString(),
            serverLogEntry.ClientIPAddress, serverLogEntry.ActionCode, serverLogEntry.Size.ToString(),
            serverLogEntry.Method.ToString(), serverLogEntry.Uri, serverLogEntry.Identity,
            serverLogEntry.HierarchyFrom, serverLogEntry.MimeType);
    }
}    

public static class SquidWebProxyServerLogParser
{
    public static IEnumerable<SquidWebProxyServerLogEntry> Parse(FileInfo fileInfo)
    {
        using (var streamReader = fileInfo.OpenText())
        {
            string row;

            while ((row = streamReader.ReadLine()) != null)
            {
                yield return ParseRow(row)
            }
        }
    }

    private static SquidWebProxyServerLogEntry ParseRow(string row)
    {
        var fields = row.Split(new[] {"t", " "}, StringSplitOptions.None);

        return new SquidWebProxyServerLogEntry
        {
            Timestamp = fields[0],
            Elapsed = int.Parse(fields[1]),
            ClientIPAddress = fields[2],
            ActionCode = fields[3],
            Size = int.Parse(fields[4]),
            Method =
                (SquidWebProxyServerLogEntry.MethodType)
                Enum.Parse(typeof(SquidWebProxyServerLogEntry.MethodType), fields[5]),
            Uri = fields[6],
            Identity = fields[7],
            HierarchyFrom = fields[8],
            MimeType = fields[9]
        };
    }

    public static IEnumerable<SquidWebProxyServerLogEntry> Parse(IEnumerable<string> rows) => rows.Select(ParseRow);
}

public sealed class SquidWebProxyServerLogEntry
{
    public enum MethodType
    {
        Get = 0,
        Post = 1,
        Put = 2
    }

    public string Timestamp { get; set; }
    public int Elapsed { get; set; }
    public string ClientIPAddress { get; set; }
    public string ActionCode { get; set; }
    public int Size { get; set; }
    public MethodType Method { get; set; }
    public string Uri { get; set; }
    public string Identity { get; set; }
    public string HierarchyFrom { get; set; }
    public string MimeType { get; set; }
}
  

Комментарии:

1. Если бы я только мог дотянуться до своей бороды, чтобы похлопать себя по спине.

2. Помимо того, что этот код не отвечает на вопрос, он не будет работать — в файле есть пробелы , а не табуляции

3. Я прочитал это, когда он пытался разобрать файл, а не модифицировать его. Тем не менее, мое разделение строк затрагивает как табуляцию, так и пробелы.

4. Я исправил, что теперь он может записывать их обратно в формат CSV.

5. Приведенный выше код у меня не работает, P.S: что плохого, если мы просто заменим все множественные пробелы одним пробелом и в конце один пробел на , (запятая). Не будет ли он полностью преобразован в CSV и его можно будет открыть в powerpivot?

Ответ №3:

CSV — это файл с разделителями, разделителем полей которого является ,. Почти все программы позволяют указывать разные разделители полей и записей, используя , и n по умолчанию.

Ваш файл можно было бы рассматривать как разделитель, если бы он не содержал нескольких пробелов для отступов. Вы можете заменить несколько пробелов одним, используя регулярное выражение s{2,} , например:

 var regex=new Regex(@"s{2,}");
var original=File.ReadAllText(somePath);
var delimited=regex.Replace(original," ");
File.WriteAllText(somePath,delimited);
  

Power BI Desktop уже позволяет использовать пробел в качестве разделителя. Даже если бы этого не произошло, вы могли бы просто заменить все пробелы запятой, изменив шаблон на s , т.Е.:

 var regex=new Regex(@"s ");
...
var delimited=regex.Replace(original,",");
...
  

Файлы журналов большие, поэтому очень хорошей идеей будет уменьшить объем используемой ими памяти. Вы можете избежать чтения всего файла в памяти, если используете ReadLines для чтения по одной строке за раз, выполните замену и запишите ее:

 using(var writer=File.CreateText(targetPath))
{
    foreach(var line in File.ReadLines(somePath))
    {
        var newline=regex.Replace(line," ");
        writer.WriteLine(newline);
    }
}
  

В отличие от ReadAllLines , который загружает все строки в массив, ReadLines это итератор, который считывает и возвращает по одной строке за раз.

Комментарии:

1. ReadAllText() … Теперь это раздувание памяти. Этот подход подходит только для файлов журнала, которые помещаются в память, дважды. Примените регулярное выражение построчно, и вы будете очень близки к объему памяти моего ответа.

2. Что может быть очень легко исправлено с помощью ReadLines, который возвращает IEnumerable<строка>. Но вопрос был задан не об этом