Last active
September 21, 2017 15:33
-
-
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.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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