This is the fourth part of the Logging series. For your convenience you can find other parts in the table of contents in Part 1 – Correlations

We can generate correlation data on each request but we need to pass them throughout the system. Let’s start with simple REST requests.

Rest client

We want to have the following factory:

namespace DomainServices.RestClient
{
    public interface IRestClientFactory
    {
        IRestClient CreateRestClientForExternalService(string baseUrl);
        IRestClient CreateRestClient(string baseUrl);
    }
}

We will use it to create REST clients for communicating with our components and external services. Actual implementation could go as follows:

using System;
using DomainCommons.Correlations;

namespace DomainServices.RestClient
{
	public class RestClientFactory : IRestClientFactory
	{
		private readonly ICorrelator _correlator;

		public RestClientFactory(ICorrelator correlator)
		{
			_correlator = correlator;
		}

		public IRestClient CreateRestClientForExternalService(string baseUrl)
		{
			if (baseUrl == null)
			{
				throw new ArgumentNullException(nameof(baseUrl));
			}

			return new RestSharpRestClient(new RestSharp.RestClient(baseUrl));
		}

		public IRestClient CreateRestClient(string baseUrl)
		{
			if (baseUrl == null)
			{
				throw new ArgumentNullException(nameof(baseUrl));
			}

			return new CorrelationRestClient(new RestSharp.RestClient(baseUrl), _correlator);
		}
	}
}

We use RestSharp library and we wrap it with our custom logic for client to use internally. Since we do not want to expose all the details, we adapt RestSharp with custom interface:

using System.Threading.Tasks;
using RestSharp;

namespace DomainServices.RestClient
{
	public interface IRestClient
	{
		Task> ExecuteTaskAsync(IRestRequest request);
	}
}

We expose just one function. In fact you should reimplement IRestRequest interface on your own but for the simplest implementation this is not required.

Now the basic client:

using System.Threading.Tasks;
using RestSharp;

namespace DomainServices.RestClient
{
	internal class RestSharpRestClient : IRestClient
	{
		private readonly RestSharp.RestClient _restClient;

		public RestSharpRestClient(RestSharp.RestClient restClient)
		{
			_restClient = restClient;
		}

		public Task> ExecuteTaskAsync(IRestRequest request)
		{
			return _restClient.ExecuteTaskAsync(request);
		}
	}
}

This only delegates to RestSharp client and so it is obvious.

The latter case is a bit harder. We need to pass headers to the request and parse the response:

using System.Linq;
using System.Threading.Tasks;
using DomainCommons.Correlations;
using DomainCommons.Loggers;
using RestSharp;

namespace DomainServices.RestClient
{
	internal class CorrelationRestClient : IRestClient
	{
		private readonly RestSharp.RestClient _restClient;
		private readonly ICorrelator _correlator;

		public CorrelationRestClient(RestSharp.RestClient restClient, ICorrelator correlator)
		{
			_restClient = restClient;
			_correlator = correlator;
		}

		public async Task> ExecuteTaskAsync(IRestRequest request)
		{
			request.AddHeader(Constants.CorrelationIdHeader, _correlator.GetCorrelationId());
			request.AddHeader(Constants.CorrelationCounterHeader, _correlator.GetLogicalTime().ToString());
			
			var result = await _restClient.ExecuteTaskAsync(request);

			var correlationHeader = result.Headers.FirstOrDefault(h => h.Name == Constants.CorrelationCounterHeader);
			if (correlationHeader != null)
			{
				_correlator.UpdateLogicalTime(int.Parse(correlationHeader.Value.ToString()));
			}
			
			return result;
		}
	}
}

So we send correlation ID and logical time to the service and on return we parse logical time and update it in correlator. Simple as that.

Service bus

We also need to pass correlation data to service bus messages. We can store them in metadata:

var client = QueueClient.CreateFromConnectionString(_config.ServiceBusConnectionString, notification.GetType().Name);
var brokeredMessage = new BrokeredMessage(notification);
brokeredMessage.Properties[_messageTypeMetadataField] = notification.GetType().AssemblyQualifiedName;
brokeredMessage.Properties[_correlationIdMetadataField] = _correlator.GetCorrelationId();
brokeredMessage.Properties[_correlationCounterMetadataField] = _correlator.GetLogicalTime();

_logger.Log(LogLevel.Information, $"Sending message: {notification}");
client.Send(brokeredMessage);

Summary

We now pass correlation data throughout the system. The only missing piece is parsing logs. We will handle this next time.