Log Parser für IIS Log Dateien

Der folgende Artikel soll eine einfache Möglichkeit aufzeigen, IIS-Log-Files unter Verwendung des .NET Frameworks in eine Struktur einzulesen, um diese für weitere Verarbeitungen zur Verfügung zu stellen. Dabei wird die Log-Datei zeilenweise eingelesen, entsprechende Feldinformationen werden aus den Kommentaren extrahiert und bis zur Verwendung zwischen gespeichert.

public class LogFile {
  private static Regex FieldPaser = new Regex(@"#Fields:(?<fields>.*)");
  private List<LogEntry> entries;

  public LogFile(string filename) {
    entries = new List<LogEntry>();
      Parse(filename);
    }

  private void Parse(string filename) {
    string line ;
    string[] currentFields = null ;
    Match m ;

    StreamReader r = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read));
      while ((line = r.ReadLine()) != null) {
        if (line.StartsWith("#")) {
          if ((m = FieldPaser.Match(line)).Success) {
            currentFields = m.Groups["fields"].Value.Trim().Split(' ');
          }
        } else {
            entries.Add(new LogEntry(currentFields, line.Split(' ')));
        }
      }
      r.Close();
    }
  }

Die Klasse LogEntry speichert die Daten einer Zeile in einem Dictionary<string,string>, ferner ermöglicht diese Klasse den Zugriff auf die einzelnen Felder, über den Index-Operator unter Verwendung des gewünschten Feldnamens. In Kombination mit der Klasse LogFile können so alle IIS-Logfiles eingelesen werden, unabhängig von den konfigurierten Feldern.

public class LogEntry {
          private Dictionary<string, string> fields;

          /// <summary>
          /// Initializes a new instance of the <see cref="LogEntry"/> class.
          /// </summary>
          /// <param name="fieldNames">The field names.</param>
          /// <param name="values">The values.</param>
          public LogEntry(string[] fieldNames, string[] values) {
              if (fieldNames.Length != values.Length) {
                  throw new Exception("length");
              }
              fields = new Dictionary<string, string>();
              for (int i = 0; i < fieldNames.Length; i++) {
                  fields[fieldNames[i]] = values[i];
              }
          }

          /// <summary>
          /// Gets or sets the <see cref="System.String"/> at the specified index.
          /// </summary>
          /// <value></value>
          public string this[string index] {
              get {
                  string ret = "-";
                  if (fields.ContainsKey(index)) {
                      ret = fields[index];
                  }
                  return ret;
              }
              private set {
                  if (fields.ContainsKey(index)) {
                      fields[index] = value;
                  } else {
                      fields.Add(index, value);
                  }
              }
          }
   }
Um den Zugriff für den Benutzer "bequemer" zu gestalten, wurden entsprechenden öffentliche Eigenschaften hinzugefügt, welche die gebräuchlichsten Felder direkt exportieren. Dabei bietet es sich an, eine entsprechenden Typkonvertierung spezieller Felder durchzuführen.
#region -=[ Public Access for fields ]=-

  public DateTime Date {
      get {
          DateTime ret = new DateTime();
          try {
               ret = DateTime.Parse(this["date"] + " " + this["time"]);
           } catch (Exception) {
           }
           return ret; 
       }
  }

  public int Time {
       get { return (this["time-taken"] == "-") ? 0 : Int32.Parse(this["time-taken"]); }
  }

  public int SendBytes {
      get { return (this["sc-bytes"] == "-") ? 0 : Int32.Parse(this["sc-bytes"]); }
  }

  public int ReceivedBytes {
      get { return (this["sc-bytes"] == "-") ? 0 : Int32.Parse(this["cs-bytes"]); }
  }

  public string Status {
      get { return this["sc-status"]; }
  }

  public string Referer {
       get { return this["cs(Referer)"]; }
  }

  public string Cookie {
      get { return this["cs(Cookie)"]; }
  }


  public string ClientIP {
      get { return this["c-ip"]; }
  }

  public string Username {
      get { return this["cs-username"]; }
  }

  public string Version {
      get { return this["cs-version"]; }
  }

  public string UserAgent {
      get { return this["cs(User-Agent)"]; }
  }

  public string ServerIP {
     get { return this["s-ip"]; }
  }

  public string Method {
     get { return this["cs-method"]; }
  }

  public string Query {
      get { return this["cs-uri-query"]; }
  }

  public string Uri {
      get { return this["cs-uri-stem"]; }
  }

  #endregion

Im zweiten Teil wird eine Möglichkeit aufgezeigt werden, mit Linq einfache Statistiken zu erstellen. Teil drei wird die Integration in eine ASP.NET Anwendung demonstrieren.