Question

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.

Was it helpful?

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
            });
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top