#region Copyright
// Diagnostic Explorer, a .Net diagnostic toolset
// Copyright (C) 2010 Cameron Elliot
//
// This file is part of Diagnostic Explorer.
//
// Diagnostic Explorer is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Diagnostic Explorer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Diagnostic Explorer. If not, see <http://www.gnu.org/licenses/>.
//
// http://diagexplorer.sourceforge.net/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using DiagnosticExplorer.Util;
namespace DiagnosticExplorer
{
public class EventSink : IDisposable
{
private static Timer timer;
private static TimeSpan messageLife = TimeSpan.FromMinutes(1);
public const int MaxMessages = 1000;
private static WeakReferenceHash<EventSink> sinks = new WeakReferenceHash<EventSink>();
private const int MaxLength = 102400;
public static WeakReferenceHash<EventSink> Sinks
{
get { return sinks; }
}
static EventSink()
{
timer = new Timer(Purge, null, 0, 5000);
}
private EventSink(string name, string category)
{
Identifier = Guid.NewGuid().ToString("N");
Count = 1;
Events = new List<SystemEvent>();
Name = name;
Category = category;
}
~EventSink()
{
Dispose(false);
}
public void Info(string message)
{
LogEvent(EventSeverity.Low, message);
}
public void Info(string message, Exception ex)
{
LogEvent(EventSeverity.Low, ex, message);
}
public void InfoFormat(string format, params object[] args)
{
LogEvent(EventSeverity.Low, FormatMessage(format, args));
}
public void Warning(string message)
{
LogEvent(EventSeverity.Medium, message);
}
public void Warning(string message, Exception ex)
{
LogEvent(EventSeverity.Medium, ex, message);
}
public void WarningFormat(string format, params object[] args)
{
LogEvent(EventSeverity.Medium, FormatMessage(format, args));
}
public void Error(string message)
{
LogEvent(EventSeverity.High, message);
}
public void Error(string message, Exception ex)
{
LogEvent(EventSeverity.High, ex, message);
}
public void ErrorFormat(string format, params object[] args)
{
LogEvent(EventSeverity.High, FormatMessage(format, args));
}
private static string FormatMessage(string format, object[] args)
{
try
{
if (args == null || args.Length == 0)
return format;
return string.Format(format, args);
}
catch
{
return string.Format("Error formatting error message [{0}]", format);
}
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
sinks.Remove(Name);
}
public string Name { get; private set; }
public string Category { get; private set; }
public long Count { get; set; }
public string Identifier { get; private set; }
public List<SystemEvent> Events { get; private set; }
public static EventSink GetSink(string name)
{
return GetSink(name, null);
}
public static EventSink GetSink(string name, string category)
{
string key = string.Format("{0}.{1}", name, category);
lock (sinks)
{
EventSink existing = sinks.GetItem(key);
if (existing == null)
{
existing = new EventSink(name, category);
sinks.Add(key, existing);
}
return existing;
}
}
private static void Purge(object sender)
{
try
{
foreach (EventSink sink in sinks.GetItems())
sink.Purge();
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
}
private void Purge()
{
try
{
lock (Events)
{
while (Events.Count != 0 && ShouldPurgeFirst(Events))
Events.RemoveAt(0);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
}
private bool ShouldPurgeFirst(IList<SystemEvent> events)
{
if (events.Count == 0) return false;
if (events.Count > MaxMessages) return true;
if (DateTime.Now - events[0].Date > messageLife) return true;
return false;
}
public void LogEvent(EventSeverity severity, string message)
{
LogEvent(severity, message, null);
}
public void LogEvent(EventSeverity severity, Exception ex)
{
LogEvent(severity, ex.Message, ex.ToString());
}
public void LogEvent(EventSeverity severity, Exception ex, string message)
{
LogEvent(severity, message, ex == null ? null : ex.ToString());
}
public void LogEvent(EventSeverity severity, string message, string detail)
{
try
{
CleanMessageAndDetail(ref message, ref detail);
SystemEvent evt = new SystemEvent();
evt.Date = DateTime.Now;
evt.Severity = severity;
evt.Message = MaxLengthString(message, MaxLength);
evt.Detail = MaxLengthString(detail, MaxLength);
lock (Events)
{
evt.Id = Count++;
Events.Add(evt);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
finally
{
RemoveExcessMessages();
}
}
/// <summary>
/// If there is no detail but a massive message, put the whole message into detail
/// and leave only the first line in message
/// </summary>
private void CleanMessageAndDetail(ref string message, ref string detail)
{
if (!string.IsNullOrEmpty(detail)) return;
if (string.IsNullOrEmpty(message)) return;
int index = message.IndexOf("\n");
if (index != -1)
{
detail = message;
message = message.Substring(0, index);
}
}
private void RemoveExcessMessages()
{
try
{
lock (Events)
{
if (Events.Count > MaxMessages)
Events.RemoveRange(0, Events.Count - MaxMessages);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
}
private static string MaxLengthString(string s, int maxLength)
{
if (s == null) return s;
if (s.Length <= maxLength) return s;
return s.Substring(0, maxLength);
}
public EventResponse GetEvents(SinkContext req)
{
if (req == null) throw new ArgumentNullException("req");
EventResponse response = new EventResponse(Name, Category);
lock (Events)
{
if (req.Identifier != Identifier)
{
response.Events.AddRange(Events);
}
else
{
for (int i = Events.Count - 1; i >= 0; i--)
{
SystemEvent evt = Events[i];
if (evt.Id > req.LastEventId)
response.Events.Add(evt);
}
response.Events.Reverse();
}
req.Identifier = Identifier;
if (response.Events.Count != 0)
{
req.LastEventId = response.Events[response.Events.Count - 1].Id;
req.LastEventDate = response.Events[response.Events.Count - 1].Date;
}
return response;
}
}
}
}