Skip to content

Instantly share code, notes, and snippets.

@ThomasArdal
Last active December 17, 2015 22:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ThomasArdal/5680420 to your computer and use it in GitHub Desktop.
Save ThomasArdal/5680420 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Configuration;
using System.Globalization;
using Nest;
namespace Elmah.ElasticSearch
{
public class ElasticSearchLog : ErrorLog
{
ElasticClient _elasticClient;
public ElasticSearchLog(IDictionary config)
{
if (config == null)
{
throw new ArgumentNullException("config");
}
InitElasticSearch(config);
}
public override string Log(Error error)
{
var indexResponse = _elasticClient.Index(new ErrorDocument
{
ApplicationName = ApplicationName,
Error = error,
ErrorXml = ErrorXml.EncodeString(error)
});
return indexResponse.Id;
}
public override ErrorLogEntry GetError(string id)
{
var document = _elasticClient.Get<ErrorDocument>(id);
var result = !string.IsNullOrEmpty(document.ErrorXml)
? new ErrorLogEntry(this, id, ErrorXml.DecodeString(document.ErrorXml))
: new ErrorLogEntry(this, id, document.Error);
return result;
}
public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList)
{
var result = _elasticClient.Search<ErrorDocument>(x =>
{
if (!string.IsNullOrWhiteSpace(ApplicationName))
{
x.Filter(f => f.Term(t => t.ApplicationName, ApplicationName));
}
x.Skip(pageSize*pageIndex).Take(pageSize).Sort(s => s.OnField(e => e.Error.Time).Descending());
return x;
});
foreach (var errorDocument in result.Documents)
{
errorEntryList.Add(new ErrorLogEntry(this, errorDocument.Id.ToString(CultureInfo.InvariantCulture), errorDocument.Error));
}
return result.Total;
}
private string LoadConnectionString(IDictionary config)
{
// From ELMAH source
// First look for a connection string name that can be
// subsequently indexed into the <connectionStrings> section of
// the configuration to get the actual connection string.
var connectionStringName = (string)config["connectionStringName"];
if (!string.IsNullOrEmpty(connectionStringName))
{
var settings = ConfigurationManager.ConnectionStrings[connectionStringName];
if (settings != null)
return settings.ConnectionString;
throw new ApplicationException(string.Format("Could not find a ConnectionString with the name '{0}'.", connectionStringName));
}
throw new ApplicationException("You must specifiy the 'connectionStringName' attribute on the <errorLog /> element.");
}
private void InitElasticSearch(IDictionary config)
{
var defaultIndex = "elmah";
var url = LoadConnectionString(config);
var connectionSettings = new ConnectionSettings(new Uri(url));
connectionSettings.SetDefaultIndex(defaultIndex);
_elasticClient = new ElasticClient(connectionSettings);
ConnectionStatus status;
_elasticClient.TryConnect(out status);
if (!status.Success)
{
throw new ApplicationException(
string.Format("Could not connect to ElasticSearch: " +
(status.Error != null ? status.Error.ExceptionMessage : "Unknown reason")));
}
}
}
[ElasticType]
public class ErrorDocument
{
public int Id { get; set; }
public Error Error { get; set; }
public string ErrorXml { get; set; }
public string ApplicationName { get; set; }
}
}
@Mpdreamz
Copy link

Line 97 and 98 can be merged into if !(_elasticClient.TryConnect(out status))

@Mpdreamz
Copy link

status.ToString() gives a good summary of the request and response and makes a great exception message (Line 100)

@Mpdreamz
Copy link

The term filter ApplicationName might not work if ApplicationName is not set to not_analyzed in the index.

i.e an ApplicationName "MyCool.Application" might be broken into multiple terms "my" "cool" "application" and so it won't be found using a term filter on "MyCool.Application",

@Mpdreamz
Copy link

You can remove the if around the Filter statement, if ApplicationName is null or empty the filter will be marked as conditionless and not sent at all to Elasticsearch.

Removes the need for the multiline lambda handling the search descriptor creation :)

@Mpdreamz
Copy link

@ThomasArdal does ELMAH have a hook for table/index creation? I don't use elmah much anymore but I remember it came with seperate sql scripts?

You will have to set [ElasticProperty(Index = FieldIndexOption.not_analyzed)] on the ApplicationName and call

client.MapFluent<ErrorDocument>(m=>m.MapFromAttributes());

@ThomasArdal
Copy link
Author

@Mpdreams I don't know if there's a hook. Alternative would be to check if index exists on connect. I think I'll keep a connection open somehow, rather that connecting each time like in the current implementation. What are your thoughts on this?

It's weird, I get a StackOverflowException when adding the MapFluent call?

@Mpdreamz
Copy link

or better through an explicit create index call

client.CreateIndex("myindexname", c => c
    .NumberOfReplicas(0)
    .NumberOfShards(1)
    .Settings(s=>s
        .Add("merge.policy.merge_factor","10")
        .Add("search.slowlog.threshold.fetch.warn", "1s")
    )   
    .AddMapping<ElasticSearchProject>(m => m.MapFromAttributes())
    .AddMapping<Person>(m => m.MapFromAttributes())
);

@Mpdreamz
Copy link

Yeah an IndexExists instead of tryconnect each time is a good idea. The indexExist is a HEAD and pretty light.

There is not really a connection to keep open, newing the client is just fine. The whole client is stateless.

The SO is interesting can you reproduce it and create an issue?

Laptop battery died and left the charger at work :( replying on my phone so excuse the short sentences/wording/tone :)

@ThomasArdal
Copy link
Author

The error is happening when mapping the Error object from Elmah. Must be a circular reference in there some place. I'm still not sure if I would like to continue adding the Elmah Error object to ES or converting it to properties on the ErrorDocument in my sample.

I would recommend you to steal borrow an extra from work. Saved me numerous times :)

@ThomasArdal
Copy link
Author

After some debugging I found out, that it's a property of type Exception, that causes the problem in MapFluent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment