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.