views:

157

answers:

3

I am trying to make a log viewer displaying events from different sources but ordered by a time stamp. I have a felling I can use C# Linq for this but how ?

example: I have one list of events read from files into a strig list sorted by a datetime stamp.

Another source of events are database inserts for which I use Linq to extract the same time segment as the first list. The database also have a time stamp.

What I want is to have list showing all events listed as they happen in real time. i.e a dabase insert may lead to an exception logged in a disk file a second later.

I guess I am looking for a way to join and sort these to lists sharing only one common field the timestamp, ending up with a collection I can use foreach over even though each element could be of different kind.

Any Ideas ?

Martin

+1  A: 

i think the code below should achieve what your looking for.

basically you just need to create a list of objects and then add your lists of db & file logs to that list. once that's done you can write a delegate to handle the sorting

    List<Db> db_inserts = new List<Db>();
    // populate list of db events here

    List<Fi> files = new List<Fi>();
    // populate list of file events here

    List<object> all = new List<object>();
    all.AddRange(db_inserts.Cast<object>());
    all.AddRange(files.Cast<object>());

    // sort the list by time
    all.Sort(delegate(object a, object b)
    {
        DateTime aTime = DateTime.MinValue;
        DateTime bTime = DateTime.MinValue;

        if (a is Db)
            aTime = ((Db)a).time;
        else if (a is Fi)
            aTime = ((Fi)a).time;

        if (b is Db)
            bTime = ((Db)b).time;
        else if (b is Fi)
            bTime = ((Fi)b).time;

        return aTime.CompareTo(bTime);
    });

EDIT: The code above could be improved using the code below (assume LogItem is a container class like in @Mark Byers reply:

    List<LogItem> all = new List<LogItem>();
    all.AddRange(db_inserts.Select(x => new LogItem { time = x.time, msg = x.name, source=x}));
    all.AddRange(files.Select(x => new LogItem{time = x.time, msg = x.name, source = x}));

    var query = all.OrderBy(x => x.time);
tt83
+1  A: 

You can use Linq to transform both data sources to the same type, and then combine them and sort them. Here I have some objects from a pretend database table T_Log which has a Timestamp field and some other fields, and another source is some strings from a fake file where each string contains a timestamp at the start of the line. I transformed them both to a custom class CommonLog and then used this to sort. The CommonLog contains a reference to the original objects so if I need more detailed information I can cast and get that information.

A more lightweight implementation could convert to a class that already exists, such as KeyValuePair<DateTime, object>.

Here's the code:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{    
    // Fake database class.
    class T_Log
    {
        public DateTime Timestamp { get; set; }
        public string Info { get; set; }
        public int Priority { get; set; }
    }

    static void Main(string[] args)
    {
        // Create some events in the fake database.
        List<T_Log> dbLogs = new List<T_Log> {
            new T_Log { Timestamp = new DateTime(2009, 2, 5), Info = "db: foo", Priority = 1 },
            new T_Log { Timestamp = new DateTime(2009, 2, 9), Info = "db: bar", Priority = 2 }
        };

        // Create some lines in a fake file.
        List<string> fileLogs = new List<string> {
            "2009-02-06: File foo",
            "2009-02-10: File bar"
        };


        var logFromDb =
            dbLogs.Select(x => new CommonLog(
                          x.Timestamp,
                          string.Format("{1} [Priority={2}]",
                                        x.Timestamp,
                                        x.Info,
                                        x.Priority),
                          x));

        var logFromFile =
            fileLogs.Select(x => new CommonLog(
                            DateTime.Parse(x.Substring(0, x.IndexOf(':'))),
                            x.Substring(x.IndexOf(':') + 2),
                            x
                ));

        var combinedLog = logFromDb.Concat(logFromFile).OrderBy(x => x.Timestamp);
        foreach (var logEntry in combinedLog)
            Console.WriteLine("{0}: {1}", logEntry.Timestamp, logEntry.Log);
    }
}

// This class is used to store logs from any source.
class CommonLog
{
    public CommonLog(DateTime timestamp,
                     string log,
                     object original)
    {
        this.Timestamp = timestamp;
        this.Log = log;
        this.Original = original;
    }

    public DateTime Timestamp { get; private set; }
    public string Log { get; private set; }
    public object Original { get; private set; }
}

Output:

05-02-2009 00:00:00: db: foo [Priority=0]
06-02-2009 00:00:00: file: baz
09-02-2009 00:00:00: db: bar [Priority=0]
10-02-2009 00:00:00: file: quux

Update: Martin replied the following in a comment to this post, but it was hard to read due to lack of formatting in comments. Here it is with formatting:

var ld = rs.Select(x => new KeyValuePair<DateTime, object>(DateTime.Parse(x[0]), x))
           .Concat(ta.Select(y => new KeyValuePair<DateTime, object>(y.Tidspunkt, y)))
           .OrderBy(d => d.Key);
Mark Byers
Thanks a lot folks! Now I just had to pick a little here and there to get it working :)As my first list was List<string[]> with the timestamp at s[0]Second list was of a table class with one element a datetimeCombining all I ended up with:var ld = rs.Select(x => new KeyValuePair<DateTime, object>(DateTime.Parse(x[0]), x)) .Concat(ta.Select(y => new KeyValuePair<DateTime, object>(y.Tidspunkt, y))).OrderBy(d => d.Key);And as the value type is polymorphic:foreach (var m in ld){ if (m.Value is Nettbud) ... else ...}Martin
Martin
Thanks for letting us know how you got on. And don't forget to accept an answer. :)
Mark Byers
sorry it's my first time here. Can't see any guidance to formatting, expanding 600chars and marking answered !Martin
Martin
Welcome to StackOverflow! You can't format in comments unfortunately - only in questions and asnwers. I added your code to the end of my answer so that it has formatting. If you want to write more than fits in a comment, you can make a new "answer" and link to it from a comment. To accept an answer, just click the tick by the answer to make it go green.
Mark Byers
OK ThanksMartin
Martin
If more than one answer helped you, accept the one that was most helpful. If you can't decide, pick any answer that helped you. If you found the answer yourself, post your own answer to your question and accept that instead. If you make a habit of not accepting answers it will register on your public profile.
Mark Byers
But also in the FAQ it says a good idea to wait 24 hours before accepting any answers, in case someone comes with a better solution later.
Mark Byers
+1  A: 

Concatenate the log sources, then sort the results. Assuming each log source is an IEnumerable<LogEntry> :

var logSources = new []
{
    GetFileLogs(),
    GetDbLogs()
    // whatever other sources you need...
};

var entries = Enumerable.Empty<LogEntry>();
foreach (var source in logSources)
{
    entries = entries.Concat(source);
}

entries = entries.OrderBy(e => e.Timestamp);
Thomas Levesque