1

We have been using elmah for some time now and we have a new requirement for the ui dont saving information directly to he database and the manager of the team asked if we can tell elmah to send the exception information to a web service. Does anyone have done this and or have any information to share, thanks.

Juan
  • 1,352
  • 13
  • 20
  • Juans answer probably works fine. An alternative would be to move the entire ELMAH log into the cloud using elmah.io (I founded that), Raygun, Loggly, Airbrake or similar. – ThomasArdal Jan 14 '16 at 09:32
  • I completely agree and I made some test with elmah.io and raygun even Microsoft Insights and they offer excellent alternatives out of the box....But some times people in management don't want this better options for what ever reason they have..... I haven't tested airbrake and loggly I will take a look, thanks – Juan Jan 14 '16 at 13:15
  • And this options does not work with the new asp.net 5 – Juan Jan 14 '16 at 13:17

1 Answers1

0

I implemented this hope it helps someone:

The web service:

   using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mail;
using System.Web.Http;
using System.Xml;
using System.Xml.Linq;
using Elmah;
using Newtonsoft.Json;

namespace ElmahExt.Service.Controllers
{
    public class ElmahCommandsController : ApiController
    {
       private readonly string _connection = ConfigurationManager.ConnectionStrings["conSQL"].ConnectionString ;

        [HttpPost]
        public HttpResponseMessage LogError(Error elmahErrorInformation)
        {
            try
            {

                var id = Guid.NewGuid();

                using (SqlConnection cnn = new SqlConnection(_connection))
                using (SqlCommand cmd = SqlCommands.LogError(elmahErrorInformation,id.ToString()))
                {
                    cmd.Connection = cnn;
                    cnn.Open();
                    cmd.ExecuteNonQuery();
                    return Request.CreateResponse(HttpStatusCode.OK, id.ToString());
                }

        }
        catch
        {
            return Request.CreateResponse(HttpStatusCode.ExpectationFailed,string.Empty);
        }

    }

    [HttpGet]
    public HttpResponseMessage GetErrors(string appName, int pageIndex, int pageSize)
    {
        using (SqlConnection cnn = new SqlConnection(_connection))
        using (SqlCommand cmd = SqlCommands.GetErrorsXml(appName, pageIndex, pageSize))
        {
            cmd.Connection = cnn;
            cnn.Open();



            XmlReader reader = cmd.ExecuteXmlReader();

            try
            {
                int? total =(int?)cmd.Parameters["@TotalCount"].Value ?? 0;

                List<string> errors = new List<string>();

                while (reader.IsStartElement("error"))
                    errors.Add(reader.ReadOuterXml());

                XDocument doc = new XDocument(
                    new XElement("GetErrorsResult",
                        new XAttribute("total", total),
                        from string error in errors
                        select new XElement("Error", error)
                        )
                    );

                return Request.CreateResponse(HttpStatusCode.OK, doc.ToString());

            }
            finally
            {
                reader.Close();
            }
        }
    }

    [HttpGet]
    public HttpResponseMessage GetError(string appName, string id)
    {
        Guid errorGuid = new Guid(id);

        string errorXml;

        using (SqlConnection cnn = new SqlConnection(_connection))
        using (SqlCommand cmd = SqlCommands.GetErrorXml(appName, errorGuid))
        {
            cmd.Connection = cnn;
            cnn.Open();
            errorXml = (string)cmd.ExecuteScalar();
        }

        XElement errorEl = (errorXml == null) ? null : new XElement("Error", errorXml);

        XDocument doc = new XDocument(
            new XElement("GetErrorResult", errorEl)
            );
        return Request.CreateResponse(HttpStatusCode.OK, doc.ToString());

    }

}

}

Elmah extension

   using System;
using System.Collections;
using System.Linq;
using System.Net.Http;
using System.Net.Mail;
using System.Threading.Tasks;
using System.Xml.Linq;
using Elmah;


namespace ElmahExt
{


    public class ServiceErrorLog : ErrorLog
    {



           private string _url;
            public ServiceErrorLog(IDictionary config)
            {
    \\Here config have the information from the configuration in the web config
                ApplicationName = config["applicationName"] as string ?? "";
                _url = config["WebServiceUrl"] as string ?? "";

            }

      static ServiceErrorLog()
        {
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        }
    private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
    {
       e.SetObserved();
    }

    public override string Name => "Custom Elmah provider using web services.";


    /// <summary>
    /// This method log the error information coming from an Elmah call. The result of the web service is received with the guid id of the error but is not waited or returned. 
    /// This implementation was on purpose to continue the processing.
    /// </summary>
    /// <param name="error">Elmah object containing all the information of the exception</param>
    /// <returns></returns>
    public override string Log(Error error)
    {

        Task T = Task.Factory.StartNew(() =>
        {
            using (var client = new HttpClient())
            {
              var result = client.PostAsJsonAsync(_url, error).Result;
            }
        });

        return string.Empty;

    }



    /// <summary>
    /// Return a list of errors to display in the Elmah viewer.
    /// </summary>
    /// <param name="pageIndex">The index of the page to display</param>
    /// <param name="pageSize">The amount of records to display per page</param>
    /// <param name="errorEntryList">The list of errors</param>
    /// <returns>A list of errors in XML format.</returns>
    public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList)
    {
        int total = 0;

        using (var client = new HttpClient())
        {
            _url += $"?pageIndex={pageIndex}&pageSize={pageSize}&appName={ApplicationName}";

            var response = client.GetAsync(_url).Result;

            if (response.IsSuccessStatusCode)
            {

                var result = response.Content.ReadAsAsync<string>().Result;

                XDocument doc = XDocument.Parse(result);
                XElement root = doc.Element("GetErrorsResult");


                if (root != null)
                {
                    var errors = from XElement el in root.Elements("Error")
                        select new
                        {
                            value = el.Value,
                            element = XElement.Parse(el.Value)
                        };

                    foreach (var err in errors)
                    {
                        string errorId = err.element.Attribute("errorId").Value;
                        Error error = ErrorXml.DecodeString(err.value);
                        errorEntryList.Add(new ErrorLogEntry(this, errorId, error));
                    }

                    total = int.Parse(root.Attribute("total").Value);


                }



            }
        }

        return total;
    }


    /// <summary>
    /// Returns an specific error
    /// </summary>
    /// <param name="id">The id of the error.(Guid)</param>
    /// <returns>Returns an XML with the error information.</returns>
    public override ErrorLogEntry GetError(string id)
    {
        ErrorLogEntry errorLogEntry = null;

        using (var client = new HttpClient())
        {
            _url += $"?id={id}&appName={ApplicationName}";

            var response = client.GetAsync(_url).Result;

            if (response.IsSuccessStatusCode)
            {

                var result = response.Content.ReadAsAsync<string>().Result;

                XDocument doc = XDocument.Parse(result);
                XElement root = doc.Element("GetErrorResult");


                XElement errorEl = root?.Element("Error");

                if (errorEl != null)
                {
                    Error error = ErrorXml.DecodeString(errorEl.Value);
                    errorLogEntry = new ErrorLogEntry(this, id, error);
                }
            }


        }

        return errorLogEntry;
    }
}

}

In your web config to have to add the custom provider:

  <errorLog type="ElmahExt.ServiceErrorLog, ElmahExt" applicationName="Elmah Evaluation App" 
                  WebServiceUrl="http://localhost:28382/api/ElmahCommands/LogError" />

Note in this configuration section the variables of the web service url and the name of the application used in the constructor of the custom provider.

Juan
  • 1,352
  • 13
  • 20
  • If you implement this code, you have to make some refactor in the exception handling area. And try to add an alternate way to capture errors to identify if the web server is down or the database. This code does not work in the new version of the asp.net 5 – Juan Jan 14 '16 at 13:23