diff --git a/README.md b/README.md index 7d662de..3cb577b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # log4net-sample Sample app to test log4net appender + + ++ [Sample App](./log4net-loggly-sample-app) to send logs to Loggly: Update loggly customer token in [App.config](./log4net-loggly-sample-app/App.config) file + ++ [log4net-loggly library performance test App](./library-performance-test-app): Update loggly customer token in [App.config](./library-performance-test-app/App.config) file diff --git a/library-performance-test-app/library-performance-test-app.sln b/library-performance-test-app/library-performance-test-app.sln new file mode 100644 index 0000000..18803f1 --- /dev/null +++ b/library-performance-test-app/library-performance-test-app.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "library-performance-test-app", "library-performance-test-app\library-performance-test-app.csproj", "{950ACA28-5E61-44D2-B1E5-BB9198FB4822}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "log4net-loggly", "log4net-loggly\log4net-loggly.csproj", "{ABE2B1B6-A83C-4604-AF87-FAAC3976530D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {950ACA28-5E61-44D2-B1E5-BB9198FB4822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {950ACA28-5E61-44D2-B1E5-BB9198FB4822}.Debug|Any CPU.Build.0 = Debug|Any CPU + {950ACA28-5E61-44D2-B1E5-BB9198FB4822}.Release|Any CPU.ActiveCfg = Release|Any CPU + {950ACA28-5E61-44D2-B1E5-BB9198FB4822}.Release|Any CPU.Build.0 = Release|Any CPU + {ABE2B1B6-A83C-4604-AF87-FAAC3976530D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABE2B1B6-A83C-4604-AF87-FAAC3976530D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABE2B1B6-A83C-4604-AF87-FAAC3976530D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABE2B1B6-A83C-4604-AF87-FAAC3976530D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/library-performance-test-app/library-performance-test-app/App.config b/library-performance-test-app/library-performance-test-app/App.config new file mode 100644 index 0000000..7ceb923 --- /dev/null +++ b/library-performance-test-app/library-performance-test-app/App.config @@ -0,0 +1,25 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library-performance-test-app/library-performance-test-app/Program.cs b/library-performance-test-app/library-performance-test-app/Program.cs new file mode 100644 index 0000000..1a08ba4 --- /dev/null +++ b/library-performance-test-app/library-performance-test-app/Program.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using log4net; +using log4net.loggly; + +namespace library_performance_test_app +{ + class Program + { + static void Main(string[] args) + { + var logger = LogManager.GetLogger(typeof(Program)); + Timer t = new Timer(printLogStatus, null, 0, 3 * 1000); + for (int i = 0; i < 20000; i++) + { + logger.Info(i + ": Hi to all Serialization using Json.NET is even easier. In this next sample, a Dictionary of strings (similar to the one used above for deserialization) is declared and then serialized to JSON format.Hi to all Serialization using Json.NET is even easier. In this next sample, a Dictionary of strings (similar to the one used above for deserialization) is declared and then serialized to JSON format."); + } + + Console.ReadKey(); + + } + static void printLogStatus(Object o) + { + Console.WriteLine(LogglyClient.getLogStatus()); + GC.Collect(); + } + } +} diff --git a/library-performance-test-app/library-performance-test-app/Properties/AssemblyInfo.cs b/library-performance-test-app/library-performance-test-app/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c8f2a06 --- /dev/null +++ b/library-performance-test-app/library-performance-test-app/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("library-performance-test-app")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("library-performance-test-app")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4e49d06d-b3f9-471d-bc47-be3f43acf60d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: log4net.Config.XmlConfigurator(Watch = true)] diff --git a/library-performance-test-app/library-performance-test-app/library-performance-test-app.csproj b/library-performance-test-app/library-performance-test-app/library-performance-test-app.csproj new file mode 100644 index 0000000..860386b --- /dev/null +++ b/library-performance-test-app/library-performance-test-app/library-performance-test-app.csproj @@ -0,0 +1,73 @@ + + + + + Debug + AnyCPU + {950ACA28-5E61-44D2-B1E5-BB9198FB4822} + Exe + Properties + library_performance_test_app + library-performance-test-app + v4.6.1 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\log4net.2.0.5\lib\net45-full\log4net.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + {abe2b1b6-a83c-4604-af87-faac3976530d} + log4net-loggly + + + + + \ No newline at end of file diff --git a/library-performance-test-app/library-performance-test-app/packages.config b/library-performance-test-app/library-performance-test-app/packages.config new file mode 100644 index 0000000..c961a39 --- /dev/null +++ b/library-performance-test-app/library-performance-test-app/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/ILogglyAppenderConfig.cs b/library-performance-test-app/log4net-loggly/ILogglyAppenderConfig.cs new file mode 100644 index 0000000..200728f --- /dev/null +++ b/library-performance-test-app/log4net-loggly/ILogglyAppenderConfig.cs @@ -0,0 +1,14 @@ +namespace log4net.loggly +{ + public interface ILogglyAppenderConfig + { + string RootUrl { get; set; } + string InputKey { get; set; } + string UserAgent { get; set; } + string LogMode { get; set; } + int TimeoutInSeconds { get; set; } + string Tag { get; set; } + string LogicalThreadContextKeys { get; set; } + string GlobalContextKeys { get; set; } + } +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/ILogglyClient.cs b/library-performance-test-app/log4net-loggly/ILogglyClient.cs new file mode 100644 index 0000000..2610c85 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/ILogglyClient.cs @@ -0,0 +1,7 @@ +namespace log4net.loggly +{ + public interface ILogglyClient + { + void Send(ILogglyAppenderConfig config, string message); + } +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/ILogglyFormatter.cs b/library-performance-test-app/log4net-loggly/ILogglyFormatter.cs new file mode 100644 index 0000000..414aec8 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/ILogglyFormatter.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using log4net.Core; +using System; + +namespace log4net.loggly +{ + public interface ILogglyFormatter + { + void AppendAdditionalLoggingInformation(ILogglyAppenderConfig unknown, LoggingEvent loggingEvent); + string ToJson(LoggingEvent loggingEvent); + string ToJson(IEnumerable loggingEvents); + + /// + /// Merged Layout formatted log with the formatted timestamp + /// + /// + /// + /// + string ToJson(string renderedLog, DateTime timeStamp); + + } +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/LogglyAppender.cs b/library-performance-test-app/log4net-loggly/LogglyAppender.cs new file mode 100644 index 0000000..b45de7b --- /dev/null +++ b/library-performance-test-app/log4net-loggly/LogglyAppender.cs @@ -0,0 +1,102 @@ +using log4net.Appender; +using log4net.Core; +using System; +using System.Collections.Generic; +using Timer = System.Timers; + + + +namespace log4net.loggly +{ + public class LogglyAppender : AppenderSkeleton + { + List lstLogs = new List(); + string[] arr = new string[100]; + public static readonly string InputKeyProperty = "LogglyInputKey"; + public static ILogglyFormatter Formatter = new LogglyFormatter(); + public static ILogglyClient Client = new LogglyClient(); + private ILogglyAppenderConfig Config = new LogglyAppenderConfig(); + public string RootUrl { set { Config.RootUrl = value; } } + public string InputKey { set { Config.InputKey = value; } } + public string UserAgent { set { Config.UserAgent = value; } } + public string LogMode { set { Config.LogMode = value; } } + public int TimeoutInSeconds { set { Config.TimeoutInSeconds = value; } } + public string Tag { set { Config.Tag = value; } } + public string LogicalThreadContextKeys { set { Config.LogicalThreadContextKeys = value; } } + public string GlobalContextKeys { set { Config.GlobalContextKeys = value; } } + + private LogglyAsyncHandler LogglyAsync; + + public LogglyAppender() + { + LogglyAsync = new LogglyAsyncHandler(); + Timer.Timer t = new Timer.Timer(); + t.Interval = 20000; + t.Enabled = true; + t.Elapsed += t_Elapsed; + } + + void t_Elapsed(object sender, Timer.ElapsedEventArgs e) + { + if (lstLogs.Count != 0) + { + SendAllEvents(lstLogs.ToArray()); + } + } + + protected override void Append(LoggingEvent loggingEvent) + { + SendLogAction(loggingEvent); + } + + private void SendLogAction(LoggingEvent loggingEvent) + { + //we should always format event in the same thread as + //many properties used in the event are associated with the current thread + //like threadname, ndc stacks, threadcontent properties etc. + + //initializing a string for the formatted log + string _formattedLog = string.Empty; + + //if Layout is null then format the log from the Loggly Client + if (this.Layout == null) + { + Formatter.AppendAdditionalLoggingInformation(Config, loggingEvent); + _formattedLog = Formatter.ToJson(loggingEvent); + } + else + { + _formattedLog = Formatter.ToJson(RenderLoggingEvent(loggingEvent), loggingEvent.TimeStamp); + } + + //check if logMode is bulk or inputs + if (Config.LogMode == "bulk/") + { + addToBulk(_formattedLog); + } + else if (Config.LogMode == "inputs/") + { + //sending _formattedLog to the async queue + LogglyAsync.PostMessage(_formattedLog, Config); + } + } + + public void addToBulk(string log) + { + // store all events into a array max lenght is 100 + lstLogs.Add(log.Replace("\n", "")); + if (lstLogs.Count == 100) + { + SendAllEvents(lstLogs.ToArray()); + } + } + + private void SendAllEvents(string[] events) + { + lstLogs.Clear(); + String bulkLog = String.Join(System.Environment.NewLine, events); + LogglyAsync.PostMessage(bulkLog, Config); + } + + } +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/LogglyAppenderConfig.cs b/library-performance-test-app/log4net-loggly/LogglyAppenderConfig.cs new file mode 100644 index 0000000..6c83811 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/LogglyAppenderConfig.cs @@ -0,0 +1,56 @@ +namespace log4net.loggly +{ + public class LogglyAppenderConfig : ILogglyAppenderConfig + { + private string _rootUrl; + private string _logMode; + public string RootUrl + { + get { return _rootUrl; } + set + { + //TODO: validate http and uri + _rootUrl = value; + if (!_rootUrl.EndsWith("/")) + { + _rootUrl += "/"; + } + } + } + + public string LogMode + { + get { return _logMode; } + set + { + _logMode = value; + if (!_logMode.EndsWith("/")) + { + _logMode = _logMode.ToLower() + "/"; + } + } + } + + public string InputKey { get; set; } + + public string UserAgent { get; set; } + + public int TimeoutInSeconds { get; set; } + + public string Tag { get; set; } + + public string LogicalThreadContextKeys { get; set; } + + public string GlobalContextKeys { get; set; } + + public LogglyAppenderConfig() + { + UserAgent = "loggly-log4net-appender"; + TimeoutInSeconds = 30; + Tag = "log4net"; + LogMode = "bulk"; + LogicalThreadContextKeys = null; + GlobalContextKeys = null; + } + } +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/LogglyAsyncHandler.cs b/library-performance-test-app/log4net-loggly/LogglyAsyncHandler.cs new file mode 100644 index 0000000..4aa4ad4 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/LogglyAsyncHandler.cs @@ -0,0 +1,57 @@ +using System.Collections.Concurrent; +using System.Threading; + +namespace log4net.loggly +{ + class LogglyAsyncHandler + { + // Size of the internal event queue. + //protected const int QueueSize = 32768; + protected ILogglyAppenderConfig _config; + + protected bool IsRunning = false; + //static list of all the queues the le appender might be managing. + private static ConcurrentBag> _allQueues = new ConcurrentBag>(); + + public static ILogglyClient Client = new LogglyClient(); + public LogglyAsyncHandler() + { + Queue = new BlockingCollection(); + _allQueues.Add(Queue); + WorkerThread = new Thread(new ThreadStart(SendLogs)); + WorkerThread.Name = "Loggly Log Appender"; + WorkerThread.IsBackground = true; + } + + protected readonly BlockingCollection Queue; + protected Thread WorkerThread; + + protected virtual void SendLogs() + { + if (this._config != null) + { + while (true) + { + var msg = Queue.Take(); + Client.Send(this._config, msg); + } + } + } + + public void PostMessage(string msg, ILogglyAppenderConfig config) + { + this._config = config; + if (!IsRunning) + { + WorkerThread.Start(); + IsRunning = true; + } + if (!Queue.TryAdd(msg)) + { + Queue.Take(); + Queue.TryAdd(msg); + } + } + } + +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/LogglyBufferringAppender.cs b/library-performance-test-app/log4net-loggly/LogglyBufferringAppender.cs new file mode 100644 index 0000000..2c8a725 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/LogglyBufferringAppender.cs @@ -0,0 +1,33 @@ +using log4net.Appender; +using log4net.Core; + +namespace log4net.loggly +{ + public class LogglyBufferringAppender : BufferingAppenderSkeleton + { + public static readonly string InputKeyProperty = "LogglyInputKey"; + + public static ILogglyFormatter Formatter = new LogglyFormatter(); + public static ILogglyClient Client = new LogglyClient(); + + private ILogglyAppenderConfig Config = new LogglyAppenderConfig(); + + public string RootUrl { set { Config.RootUrl = value; } } + public string InputKey { set { Config.InputKey = value; } } + public string UserAgent { set { Config.UserAgent = value; } } + public string LogMode { set { Config.LogMode = value; } } + public int TimeoutInSeconds { set { Config.TimeoutInSeconds = value; } } + public string Tag { set { Config.Tag = value; } } + + protected override void Append(LoggingEvent loggingEvent) + { + Formatter.AppendAdditionalLoggingInformation(Config, loggingEvent); + base.Append(loggingEvent); + } + + protected override void SendBuffer(LoggingEvent[] loggingEvents) + { + Client.Send(Config, Formatter.ToJson(loggingEvents)); + } + } +} \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/LogglyClient.cs b/library-performance-test-app/log4net-loggly/LogglyClient.cs new file mode 100644 index 0000000..9070f33 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/LogglyClient.cs @@ -0,0 +1,111 @@ +using System; +using System.Net; +using System.Text; +using System.Collections.Generic; + +namespace log4net.loggly +{ + + public class LogglyClient : ILogglyClient + { + static float totalThroughPutInKB = 0; + static int grossRetryCount = 0; + static int failureLogCount = 0; + static float eventsPerSeconds = 0; + static int logLost = 0; + static float totalTimeInSeconds = 0; + static int successLogCount = 0; + + public static string getLogStatus() + { + + var logStatus = new + { + ThroughputInKB = totalThroughPutInKB, + EventPerSecond = eventsPerSeconds, + Success = successLogCount, + Failure = failureLogCount, + Retries = grossRetryCount, + LogLost = logLost + }; + + resetLogStatus(); + return Newtonsoft.Json.JsonConvert.SerializeObject(logStatus); + } + private static void resetLogStatus() + { + totalThroughPutInKB = 0; + grossRetryCount = 0; + failureLogCount = 0; + eventsPerSeconds = 0; + logLost = 0; + totalTimeInSeconds = 0; + successLogCount = 0; + } + public virtual void Send(ILogglyAppenderConfig config, string message) + { + int maxRetryAllowed = 5; + int totalRetries = 0; + DateTime startTime = DateTime.UtcNow; + DateTime endTime = DateTime.UtcNow; + + string _tag = config.Tag; + + //keeping userAgent backward compatible + if (!string.IsNullOrWhiteSpace(config.UserAgent)) + { + _tag = _tag + "," + config.UserAgent; + } + + while (totalRetries < maxRetryAllowed) + { + totalRetries++; + + if (totalRetries > 1) + grossRetryCount++; + try + { + var bytes = Encoding.UTF8.GetBytes(message); + var webRequest = CreateWebRequest(config, _tag); + using (var dataStream = webRequest.GetRequestStream()) + { + dataStream.Write(bytes, 0, bytes.Length); + dataStream.Flush(); + dataStream.Close(); + } + + var webResponse = webRequest.GetResponse(); + webResponse.Close(); + + // Some calculation to count event per sec, total throughput in kb + successLogCount++; + endTime = DateTime.UtcNow; + totalTimeInSeconds = (float)((endTime - startTime).TotalSeconds); + eventsPerSeconds = successLogCount / totalTimeInSeconds; + totalThroughPutInKB = totalThroughPutInKB + ((message.Length * 2) / 1024f); + break; + } + catch + { + failureLogCount++; + if (totalRetries == maxRetryAllowed) + logLost++; + } + } + } + + protected virtual HttpWebRequest CreateWebRequest(ILogglyAppenderConfig config, string tag) + { + var url = String.Concat(config.RootUrl, config.LogMode, config.InputKey); + //adding userAgent as tag in the log + url = String.Concat(url, "/tag/" + tag); + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "POST"; + request.ReadWriteTimeout = request.Timeout = config.TimeoutInSeconds * 1000; + request.UserAgent = config.UserAgent; + request.KeepAlive = true; + request.ContentType = "application/json"; + return request; + } + } +} diff --git a/library-performance-test-app/log4net-loggly/LogglyFormatter.cs b/library-performance-test-app/log4net-loggly/LogglyFormatter.cs new file mode 100644 index 0000000..1b431e2 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/LogglyFormatter.cs @@ -0,0 +1,304 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using log4net.Core; +using Newtonsoft.Json; +using System.Dynamic; +using Newtonsoft.Json.Linq; + +namespace log4net.loggly +{ + public class LogglyFormatter : ILogglyFormatter + { + private Process _currentProcess; + private ILogglyAppenderConfig _config; + + public LogglyFormatter() + { + _currentProcess = Process.GetCurrentProcess(); + } + + public virtual void AppendAdditionalLoggingInformation(ILogglyAppenderConfig config, LoggingEvent loggingEvent) + { + this._config = config; + } + + public virtual string ToJson(LoggingEvent loggingEvent) + { + return PreParse(loggingEvent); + } + + public virtual string ToJson(IEnumerable loggingEvents) + { + return JsonConvert.SerializeObject(loggingEvents.Select(PreParse),new JsonSerializerSettings(){ + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }); + } + + public virtual string ToJson(string renderedLog, DateTime timeStamp) + { + return ParseRenderedLog(renderedLog, timeStamp); + } + + /// + /// Formats the log event to various JSON fields that are to be shown in Loggly. + /// + /// + /// + private string PreParse(LoggingEvent loggingEvent) + { + //formating base logging info + dynamic _loggingInfo = new ExpandoObject(); + _loggingInfo.timestamp = loggingEvent.TimeStamp.ToString(@"yyyy-MM-ddTHH\:mm\:ss.fffzzz"); + _loggingInfo.level = loggingEvent.Level.DisplayName; + _loggingInfo.hostName = Environment.MachineName; + _loggingInfo.process = _currentProcess.ProcessName; + _loggingInfo.threadName = loggingEvent.ThreadName; + _loggingInfo.loggerName = loggingEvent.LoggerName; + + //handling messages + object _loggedObject = null; + string _message = GetMessageAndObjectInfo(loggingEvent, out _loggedObject); + + if (_message != string.Empty) + { + _loggingInfo.message = _message; + } + + //handling exceptions + dynamic _exceptionInfo = GetExceptionInfo(loggingEvent); + if (_exceptionInfo != null) + { + _loggingInfo.exception = _exceptionInfo; + } + + //handling threadcontext properties + string[] _threadContextProperties = ThreadContext.Properties.GetKeys(); + if (_threadContextProperties != null && _threadContextProperties.Any()) + { + var p = _loggingInfo as IDictionary; + foreach (string key in _threadContextProperties) + { + if ((ThreadContext.Properties[key] as IFixingRequired) != null + && (ThreadContext.Properties[key] as IFixingRequired).GetFixedObject() != null) + { + p[key] = (ThreadContext.Properties[key] as IFixingRequired).GetFixedObject(); + } + else + { + p[key] = ThreadContext.Properties[key].ToString(); + } + } + } + + //handling logicalthreadcontext properties + if (this._config.LogicalThreadContextKeys != null) + { + var ltp = _loggingInfo as IDictionary; + string[] _LogicalThreadContextProperties = this._config.LogicalThreadContextKeys.Split(','); + foreach (string key in _LogicalThreadContextProperties) + { + if (LogicalThreadContext.Properties[key] != null) + { + if ((LogicalThreadContext.Properties[key] as IFixingRequired) != null + && (LogicalThreadContext.Properties[key] as IFixingRequired).GetFixedObject() != null) + { + ltp[key] = (LogicalThreadContext.Properties[key] as IFixingRequired).GetFixedObject(); + } + else + { + ltp[key] = LogicalThreadContext.Properties[key].ToString(); + } + } + } + } + + //handling globalcontext properties + if (this._config.GlobalContextKeys != null) + { + var gcp = _loggingInfo as IDictionary; + string[] _globalContextProperties = this._config.GlobalContextKeys.Split(','); + foreach (string key in _globalContextProperties) + { + if (GlobalContext.Properties[key] != null) + { + if ((GlobalContext.Properties[key] as IFixingRequired) != null + && (GlobalContext.Properties[key] as IFixingRequired).GetFixedObject() != null) + { + gcp[key] = (GlobalContext.Properties[key] as IFixingRequired).GetFixedObject(); + } + else + { + gcp[key] = GlobalContext.Properties[key].ToString(); + } + } + } + } + + string jsonMessage = string.Empty; + if (TryGetParsedJsonFromLog(_loggingInfo, _loggedObject, out jsonMessage)) + { + return jsonMessage; + } + else + { + //converting event info to Json string + return JsonConvert.SerializeObject(_loggingInfo, + new JsonSerializerSettings() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + }); + } + } + + /// + /// Merged Rendered log and formatted timestamp in the single Json object + /// + /// + /// + /// + private string ParseRenderedLog(string log, DateTime timeStamp) + { + dynamic _loggingInfo = new ExpandoObject(); + _loggingInfo.timestamp = timeStamp.ToString(@"yyyy-MM-ddTHH\:mm\:ss.fffzzz"); + + string jsonMessage = string.Empty; + if (TryGetParsedJsonFromLog(_loggingInfo, log, out jsonMessage)) + { + return jsonMessage; + } + else + { + _loggingInfo.message = log; + return JsonConvert.SerializeObject(_loggingInfo, + new JsonSerializerSettings() + { + PreserveReferencesHandling = PreserveReferencesHandling.Arrays, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + }); + } + } + + /// + /// Returns the exception information. Also takes care of the InnerException. + /// + /// + /// + private object GetExceptionInfo(LoggingEvent loggingEvent) + { + if (loggingEvent.ExceptionObject == null) + return null; + + dynamic exceptionInfo = new ExpandoObject(); + exceptionInfo.exceptionType = loggingEvent.ExceptionObject.GetType().FullName; + exceptionInfo.exceptionMessage = loggingEvent.ExceptionObject.Message; + exceptionInfo.stacktrace = loggingEvent.ExceptionObject.StackTrace; + + //most of the times dotnet exceptions contain important messages in the inner exceptions + if (loggingEvent.ExceptionObject.InnerException != null) + { + dynamic innerException = new + { + innerExceptionType = loggingEvent.ExceptionObject.InnerException.GetType().FullName, + innerExceptionMessage = loggingEvent.ExceptionObject.InnerException.Message, + innerStacktrace = loggingEvent.ExceptionObject.InnerException.StackTrace + }; + exceptionInfo.innerException = innerException; + } + return exceptionInfo; + } + + /// + /// Returns a string type message if it is not a custom object, + /// otherwise returns custom object details + /// + /// + /// + private string GetMessageAndObjectInfo(LoggingEvent loggingEvent, out object objInfo) + { + string message = string.Empty; + objInfo = null; + + if (loggingEvent.MessageObject != null) + { + if (loggingEvent.MessageObject.GetType() == typeof(string) + //if it is sent by using InfoFormat method then treat it as a string message + || loggingEvent.MessageObject.GetType().FullName == "log4net.Util.SystemStringFormat" + || loggingEvent.MessageObject.GetType().FullName.Contains("StringFormatFormattedMessage")) + { + message = loggingEvent.MessageObject.ToString(); + } + else + { + objInfo = loggingEvent.MessageObject; + } + } + else + { + //adding message as null so that the Loggly user + //can know that a null object is logged. + message = "null"; + } + return message; + } + + /// + /// Tries to merge log with the logged object or rendered log + /// and converts to JSON + /// + /// + /// + /// + /// + private bool TryGetParsedJsonFromLog(dynamic loggingInfo, object loggingObject, out string _loggingEventJSON) + { + //serialize the dynamic object to string + _loggingEventJSON = JsonConvert.SerializeObject(loggingInfo, + new JsonSerializerSettings() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + }); + + //if loggingObject is null then we need to go to further step + if (loggingObject == null) + return false; + + try + { + string _loggedObjectJSON = string.Empty; + if (loggingObject.GetType() == typeof(string)) + { + _loggedObjectJSON = loggingObject.ToString(); + } + else + { + _loggedObjectJSON = JsonConvert.SerializeObject(loggingObject, + new JsonSerializerSettings() + { + PreserveReferencesHandling = PreserveReferencesHandling.Arrays, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + }); + } + + //try to parse the logging object + JObject jObject = JObject.Parse(_loggedObjectJSON); + JObject jEvent = JObject.Parse(_loggingEventJSON); + + //merge these two objects into one JSON string + jEvent.Merge(jObject, new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Union + }); + + _loggingEventJSON = jEvent.ToString(); + + return true; + } + catch + { + return false; + } + } + } +} diff --git a/library-performance-test-app/log4net-loggly/Properties/AssemblyInfo.cs b/library-performance-test-app/log4net-loggly/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ee78412 --- /dev/null +++ b/library-performance-test-app/log4net-loggly/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("log4net-loggly")] +[assembly: AssemblyDescription("Log4net client for Loggly Gen 2")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("log4net-loggly")] +[assembly: AssemblyProduct("log4net-loggly")] +[assembly: AssemblyCopyright("Copyright © log4net-loggly 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ad4ffe16-a464-400d-9650-bc4d1c43da8f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("7.2.0.0")] +[assembly: AssemblyFileVersion("7.2.0.0")] diff --git a/library-performance-test-app/log4net-loggly/log4net-loggly.csproj b/library-performance-test-app/log4net-loggly/log4net-loggly.csproj new file mode 100644 index 0000000..b7d92fc --- /dev/null +++ b/library-performance-test-app/log4net-loggly/log4net-loggly.csproj @@ -0,0 +1,74 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {ABE2B1B6-A83C-4604-AF87-FAAC3976530D} + Library + Properties + log4net.loggly + log4net-loggly + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\log4net.2.0.5\lib\net40-full\log4net.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library-performance-test-app/log4net-loggly/packages.config b/library-performance-test-app/log4net-loggly/packages.config new file mode 100644 index 0000000..fe559dd --- /dev/null +++ b/library-performance-test-app/log4net-loggly/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/log4net-loggly-sample-app/App.config b/log4net-loggly-sample-app/App.config new file mode 100644 index 0000000..a197830 --- /dev/null +++ b/log4net-loggly-sample-app/App.config @@ -0,0 +1,25 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4net-loggly-sample-app/Program.cs b/log4net-loggly-sample-app/Program.cs new file mode 100644 index 0000000..cd14477 --- /dev/null +++ b/log4net-loggly-sample-app/Program.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using log4net; + +namespace log4net_loggly_sample_app +{ + class Program + { + static void Main(string[] args) + { + var logger = LogManager.GetLogger(typeof(Program)); + //Send plaintext + logger.Info("your log message"); + + //Send an exception + logger.Error("your log message", new Exception("your exception message")); + + //Send a JSON object + var items = new Dictionary(); + items.Add("key1", "value1"); + items.Add("key2", "value2"); + logger.Info(items); + + //Wait atleast 5 seconds before terminating the application to send logs to loggly + Console.WriteLine("Log is sending to loggly. Wait atleast 5 seconds before terminating the application."); + Console.ReadKey(); + } + } +} diff --git a/log4net-loggly-sample-app/Properties/AssemblyInfo.cs b/log4net-loggly-sample-app/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e96f31c --- /dev/null +++ b/log4net-loggly-sample-app/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("log4net-loggly-sample-app")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("log4net-loggly-sample-app")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8436517f-9a7e-44c2-b62c-0072ee0b2d34")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: log4net.Config.XmlConfigurator(Watch = true)] \ No newline at end of file diff --git a/log4net-loggly-sample-app/log4net-loggly-sample-app.csproj b/log4net-loggly-sample-app/log4net-loggly-sample-app.csproj new file mode 100644 index 0000000..949a9fe --- /dev/null +++ b/log4net-loggly-sample-app/log4net-loggly-sample-app.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {03C04317-287B-44D3-8229-0D767659CBF4} + Exe + Properties + log4net_loggly_sample_app + log4net-loggly-sample-app + v4.6.1 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll + True + + + ..\packages\log4net-loggly.7.2.3\lib\net40\log4net-loggly.dll + True + + + ..\packages\Newtonsoft.Json.8.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4net-loggly-sample-app/packages.config b/log4net-loggly-sample-app/packages.config new file mode 100644 index 0000000..4d8f302 --- /dev/null +++ b/log4net-loggly-sample-app/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file