Search code examples
c#jsonasp.net-mvc-4loggingblockingcollection

Website asynchronously posting to Loggly


I'm trying to work out how to make an asynchronous logging solution for application logging to Loggly. Looking at Loggly's ducumentation, and thinking of this as a classic Producer-Consumer problem, I came up with this:

Message Models to use for JSON Serialization of data:

[DataContract]
public abstract class LogMessage
{
    [DataMember]
    public DateTime TimeStamp {get;set;}

    [DataMember]
    public Guid Id {get;set;}

    [DataMember]
    public int SentAttemptCount {get;set;}
}

[DataContract]
public class ExceptionMessage : LogMessage
{
    [DataMember]
    public ExceptionMessageDetails ExceptionDetails {get;set;}
}

[DataContract]
public class ExceptionMessageDetails
{
    [DataMember]
    public string Message {get;set;}

    [DataMember]
    public string Type {get;set;}

    [DataMember]
    public string StackTrace {get;set;}

    [DataMember]
    public ExceptionMessageDetails InnerException {get;set;}
}

Logger class, that will be passed to anything that needs to log (like an ExceptionFilter). This uses a BlockingCollection to queue messages for sending to Loggly.

public class LogglyLogger
{

    private readonly string logglyUrl = "https://logs-01.loggly.com/inputs/xxxx/";
    private readonly HttpClient client; 
    private readonly BlockingCollection<LogMessage> logQueue;
    private readonly int maxAttempts = 4;

    public LogglyLogger()
    {
        logQueue = new BlockingCollection<LogMessage>();
        client = new HttpClient();

        Task.Run(async () =>
        {
            foreach(var msg in logQueue.GetConsumingEnumerable())
            {
                try
                {           
                    await SendMessage(msg);
                }
                catch (Exception)
                {
                    if (msg.SentAttemptCount <= maxAttempts)
                    {
                        msg.SentAttemptCount += 1;
                        logQueue.Add(msg);
                    }
                }
            }
        });
    }


    public void SendLogMessage<T>(T msg) where T : LogMessage
    {
        logQueue.Add(msg);
    }

    private async Task SendMessage<T>(T msg) where T : LogMessage
    {
        await client.PostAsJsonAsync(logglyUrl, msg);
    }
}

Here are my questions:

  • Is there something wrong with this pattern of setting up the BlockingCollection?
  • Will JSON.Net figure out the correct subclass of LogMessage, or do I need to send the message differently?
  • Swallowing exceptions is definitely a code smell, but I'm not sure what should happen if the logger fails to send the message. Any thoughts?

Thanks in advance, SO.


Solution

  • I ended up solving this by taking more direct control of what to send.

    The LogMessageEvelope class matured somewhat, adding a non-serialized MessageTags property to pass along desired tag(s) to Loggly.

    /// <summary>
    /// Send the log message to loggly
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    private void SendMessage(LogMessageEnvelope message)
    {
        // build list of tags
        string tags = string.Join(",", message.MessageTags); 
    
        // serialize the message
        JsonSerializerSettings settings = new JsonSerializerSettings 
        { 
            NullValueHandling = NullValueHandling.Ignore,
        };
        string content = 
            JsonConvert.SerializeObject(message, Formatting.Indented, settings);
    
        // build the request
        HttpRequestMessage request = new HttpRequestMessage();
        request.RequestUri = new Uri(logglyUrl);
        request.Method = HttpMethod.Post;
        request.Content = new StringContent(content, Encoding.UTF8);
        request.Headers.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Add("X-LOGGLY-TAG", tags);
    
        // send the request
        HttpClient client = new HttpClient();
        client.SendAsync(request)
              .ContinueWith(sendTask =>
                {
                    // handle the response
                    HttpResponseMessage response = sendTask.Result;
                    if (!response.IsSuccessStatusCode)
                        // handle a failed log message post
                });
    }