Skip to content

Instantly share code, notes, and snippets.

@ctigeek
Last active September 21, 2017 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ctigeek/9ae184fe2e3a8d075cd6768ab62484ed to your computer and use it in GitHub Desktop.
Save ctigeek/9ae184fe2e3a8d075cd6768ab62484ed to your computer and use it in GitHub Desktop.
TcpAppender for log4net. Also a json converter and example app.config. (I'm using it to write to an instance of logstash.)
<log4net>
<appender name="TcpAppender" type="TestLog4Net.TcpAppender, TestLog4Net">
<remoteAddress value="127.0.0.1" />
<remotePort value="5151" />
<maxBacklogLength value="10000" />
<layout type="log4net.Layout.PatternLayout" >
<converter>
<name value="json" />
<type value="TestLog4Net.JsonConverter, TestLog4Net" />
</converter>
<conversionPattern value="%json%newline" />
</layout>
</appender>
<logger name="Logstash" additivity="false">
<level value="ALL" />
<appender-ref ref="TcpAppender" />
</logger>
</log4net>
/// This code is copywright 2017 by Steven Swenson and released under the MIT open source license.
/// https://opensource.org/licenses/MIT
using System;
using System.IO;
using log4net.Core;
using log4net.Layout.Pattern;
using Newtonsoft.Json;
namespace TestLog4Net
{
public class JsonConverter : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
var writeThis = JsonConvert.SerializeObject(loggingEvent.MessageObject);
writer.Write(writeThis);
}
}
}
/// This code is copywright 2017 by Steven Swenson and released under the MIT open source license.
/// https://opensource.org/licenses/MIT
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using log4net;
using log4net.Appender;
using log4net.Core;
namespace TestLog4Net
{
public class TcpAppender : AppenderSkeleton
{
private static object lockObject = new object();
private static bool serverIsDown = false;
private static ILog logger = LogManager.GetLogger(typeof(TcpAppender));
private int _maxBacklogLength { get; set; } = 10000;
public string maxBacklogLength { set => _maxBacklogLength = int.Parse(value); }
public string remoteAddress { get; set; }
public string remotePort { get; set; }
private ConcurrentQueue<LoggingEvent> backlog = new ConcurrentQueue<LoggingEvent>();
private TcpClient _client;
private TcpClient client
{
get
{
if (_client == null || !_client.Connected)
{
_client = null;
logger.InfoFormat("Opening TCP connection to {0}:{1}.", remoteAddress, remotePort);
_client?.Close();
_client = new TcpClient(remoteAddress, int.Parse(remotePort));
_client.LingerState = new LingerOption(false, 0);
_client.NoDelay = true;
}
return _client;
}
}
protected override void OnClose()
{
logger.Info("Closing TCP connection.");
_client?.Close();
while (backlog.Count > 0)
{
LoggingEvent loggingEvent;
if (backlog.TryDequeue(out loggingEvent))
{
logger.ErrorFormat("Log item not sent and service is shutting down: \r\n{0}", loggingEvent.RenderedMessage);
}
}
}
protected override void Append(LoggingEvent loggingEvent)
{
try
{
if (serverIsDown)
{
backlog.Enqueue(loggingEvent);
}
else
{
if (!string.IsNullOrEmpty(remoteAddress))
{
WriteObject(loggingEvent);
}
else
{
logger.Info("TcpAppender logger is disabled. No remote address specified.");
}
}
}
catch (Exception ex)
{
backlog.Enqueue(loggingEvent);
logger.Error("Error sending event. Most likely the destination server is down. Logging event will be queued until server is back up.", ex);
if (backlog.Count < 4) //This is an expensive operation, so only try to start the background thread if the queue is near-empty.
{
ThreadPool.QueueUserWorkItem(Emptybacklog, null);
}
}
}
private void Emptybacklog(object nada)
{
if (Monitor.TryEnter(lockObject))
{
logger.InfoFormat("Background thread to empty queue has started... ThreadID={0}", Thread.CurrentThread.ManagedThreadId);
while (backlog.Count > 0)
{
try
{
LoggingEvent loggingEvent;
if (backlog.TryPeek(out loggingEvent))
{
WriteObject(loggingEvent);
while (!backlog.TryDequeue(out loggingEvent)) { }
logger.Info("Successfully delivered item from backlog.");
}
Thread.Sleep(50); //don't flood the destination all at once... this will throttle backlog being delivered.
serverIsDown = false;
}
catch (Exception ex)
{
logger.WarnFormat("Error trying to deliver backlog item. Most likely the server is still down. \r\n {0}", ex);
serverIsDown = true;
Thread.Sleep(2000); // if the server is down.
CullQueue();
}
}
logger.Info("Hurrah! The backlog is now empty.");
Monitor.Exit(lockObject);
}
}
private void CullQueue()
{
try
{
while (backlog.Count > _maxBacklogLength)
{
LoggingEvent discard;
if (backlog.TryDequeue(out discard))
{
logger.WarnFormat("Backlog is full. Had to discard an item.\r\n{0}", discard.RenderedMessage);
}
}
}
catch (Exception ex)
{
logger.Error("", ex);
}
}
private void WriteObject(LoggingEvent loggingEvent)
{
using (var writer = new StreamWriter(client.GetStream(), Encoding.UTF8, client.SendBufferSize, true))
{
RenderLoggingEvent(writer, loggingEvent);
writer.Flush();
if (logger.IsInfoEnabled)
{
logger.InfoFormat("Delivered loggingEvent: {0}", loggingEvent.RenderedMessage);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment