So here’s the problem: a project contains two SOLR clients (the legacy of a failed attempt to introduce a newer SOLR client) and I need to explore the query generation to make sure that the queries are identical. I need a lightweight way to generate the queries in a TTD style.
Solution: I write a SOLR client with a decorated connection, that logs the queries before they hit the wire: and house it in an nunit project. A quick search reveals that this is a problem that has been tackled in different parts in different places.
Great so far, but this brings up another issue: the log4net logging code “expects” to run in the context of an application or a website (not unreasonably), and so expects to have access to a config file in either an app.config or a web.config.
So, first we create our LoggingConnection, which supports the ISolrConnection interface, and takes a config file path :
public class LoggingConnection : ISolrConnection { private readonly ISolrConnection _connection; private readonly ILog _logger; public LoggingConnection(ISolrConnection connection, string configFilePath) { _connection = connection; if (LogManager.GetCurrentLoggers().Count() == 0) { LoadConfig(configFilePath); } _logger = LogManager .GetLogger(System.Reflection.MethodBase.GetCurrentMethod() .DeclaringType); } private static void LoadConfig(string configFilePath) { log4net.Config.XmlConfigurator.Configure(new FileInfo(configFilePath)); //get the current logging repository for this application var repository = LogManager.GetRepository(); //get all of the appenders for the repository var appenders = repository.GetAppenders(); //only change the file path on the 'FileAppenders' foreach (var appender in (from iAppender in appenders where iAppender is FileAppender select iAppender)) { var fileAppender = appender as FileAppender; //set the path to your logDirectory using the original file name defined //in configuration if (fileAppender != null && !string.IsNullOrEmpty(fileAppender.File)) { var logfile = Path.GetFileName(fileAppender.File); if(logfile != null) fileAppender.File = Path.Combine(Directory.GetCurrentDirectory(), logfile); //make sure to call fileAppender.ActivateOptions() to notify // the logging sub system that the configuration // for this appender has changed. fileAppender.ActivateOptions(); } } } // logging versions of SOLR calls public string Post(string relativeUrl, string s) { _logger.DebugFormat("POSTing '{0}' to '{1}'", s, relativeUrl); return _connection.Post(relativeUrl, s); } public string PostStream(string relativeUrl, string contentType, Stream content, IEnumerable<KeyValuePair<string, string>> getParameters) { _logger.DebugFormat("POSTing to '{0}'", relativeUrl); return "Not Supported?"; } public string Get(string relativeUrl, IEnumerable<KeyValuePair<string, string>> parameters) { var stringParams = string.Join(", ", parameters .Select(p => string.Format("{0}={1}", p.Key, p.Value)) .ToArray()); _logger.DebugFormat("GETting '{0}' from '{1}'", stringParams, relativeUrl); return _connection.Get(relativeUrl, parameters); } }
Next, we create the SolrService class, whose constructor can create either a normal or a logging connection.
public class SolrService : ISolrService { public SolrService(string solrserver, string filePath) { var connection = !string.IsNullOrEmpty(filePath) ? new LoggingConnection(new SolrConnection(solrserver), filePath) as ISolrConnection : new SolrConnection(solrserver); Startup.Init<SolrTourLite>(connection); } }
if a config file path is supplied, a logging connection is created, otherwise a normal (non-logging) connection is created by default. And thus, the query being passed to the server can be viewed, debugged and tested against the original query.
And so the precise query generated can be easily debugged:
2012-10-02 16:29:30,463 GETting ‘q=(((Continents:Europe OR ContinentCodes:EURO) AND ((TourName:Europe) OR (DestinationText:Europe)) AND SellingCompanyCode:IVUKLS), start=0, rows=100’ from ‘/select’
2012-10-02 16:30:23,568 GETting ‘q=(((Continents:Europe OR ContinentCodes:EURO) AND ((TourName:Europe) OR (DestinationText:Europe))) AND SellingCompanyCode:IVUKLS), start=0, rows=100’ from ‘/select’
2012-10-02 16:31:20,970 GETting ‘q=((Countries:Italy OR CountryCodes:IT) AND ((TourName:Italy) OR (DestinationText:Italy)) AND SellingCompanyCode:IVUKLS), start=0, rows=100’ from ‘/select’
2012-10-02 16:33:30,321 GETting ‘q=((Countries:Italy OR CountryCodes:IT) AND ((TourName:Italy) OR (DestinationText:Italy)) AND SellingCompanyCode:IVUKLS), start=0, rows=100’ from ‘/select’
2012-10-02 16:34:40,478 GETting ‘q=(((Countries:Italy OR CountryCodes:IT) AND ((TourName:Italy) OR (DestinationText:Italy))) AND SellingCompanyCode:IVUKLS), start=0, rows=100’ from ‘/select’
and compared to the original library:
<SearchQuery Date="02-10-2012 13-20-09"> - <![CDATA[(((((Continents:"Asia" OR ContinentCodes:"ASIA") AND ((TourName:Asia) OR (DestinationText:Asia)) AND SellingCompanyCode:IVUKLS)))) ]]> </SearchQuery> - <SearchQuery Date="02-10-2012 13-20-37"> - <![CDATA[(((((Countries:"Egypt" OR CountryCodes:"EG") AND PriceFrom:[3001 TO 4000] AND ((TourName:Egypt) OR (DestinationText:Egypt)) AND SellingCompanyCode:IVUKLS)))) ]]> </SearchQuery> - <SearchQuery Date="02-10-2012 13-20-53"> - <![CDATA[((((SellingCompanyCode:IVUKLS)))) ]]> </SearchQuery> - <SearchQuery Date="02-10-2012 13-21-04"> - <![CDATA[((((CatalogProductCode:"G150" AND SellingCompanyCode:IVUKLS)))) ]]> </SearchQuery>