Tuesday, November 20, 2007

Posting errors to a website

The exception logger I've written is one of the most popular pages on my proper website but I've never been completely happy with it. I've always wanted to be able to get the details of an unhandled exception from the user of my software to me easily. I added support for sending an email some time ago, but that's never felt like a good solution since the user must configure their SMTP connection before sending off the email. Then I read a website posting suggesting a much better solution, post the error to a website, then email the error to the relevant address from the web server. That way, email only needs to be configured in one place, on the web server. Admittedly if the user isn't hooked up to the internet it won't work, but sending an email via SMTP will also generally fail in that scenario.

The exception logger code shows how to do the client-side code, but here it is anyway.

    private void LogToWebsite(string error)
    {
      Uri uri = new Uri("http://www.yourwebsite.com/SubmitBug.aspx");
      HttpWebRequest httpWebRequest    = (HttpWebRequest) WebRequest.Create(uri);
      httpWebRequest.Method    = "POST";
      httpWebRequest.ContentType = "application/x-www-form-urlencoded";

      Encoding encoding = Encoding.Default;

      string parameters = "msg=" HttpUtility.UrlEncode(error);

      // get length of request (may well be a better way to do this)
      MemoryStream memStream = new MemoryStream();
      StreamWriter streamWriter = new StreamWriter(memStream, encoding);
      streamWriter.Write(parameters);
      streamWriter.Flush();
      httpWebRequest.ContentLength = memStream.Length;
      streamWriter.Close();

      Stream stream = httpWebRequest.GetRequestStream();
      streamWriter = new StreamWriter(stream, encoding);
      streamWriter.Write(parameters);
      streamWriter.Close();

      using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
      using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
      {
        streamReader.ReadToEnd();
      }
    }

A couple of points to make here. I'm using POST rather than GET, because GETs have a maximum length of data that can be passed (8K if I remember correctly). The error is URL encoded, so that ampersands don't truncate the posted error message. The code to work out the length of the content may not be required, you may be able to get away without passing it at all and there may be a better way of calculating it. I did it that way when I was fiddling around with the encoding type, since different encoding types would naturally produce different lengths of content. Finally reading back the response data isn't really necessary since we don't do anything with it.

Now onto the server-side code, which is pretty simple.

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!string.IsNullOrEmpty(Request.Params["msg"]))
    {
      SmtpMail.SmtpServer = "localhost";
      SmtpMail.Send("mail@yourwebsite.com", "error@yourwebsite.com", 
        "Bug report", Request.Params["msg"]);
    }
  }

The only thing to note here is that because we are using POST, the data is passed in via the Params property, rather than the QueryString property.

No comments: