/// <summary>
        /// Processes the incoming HTTP call and capture details about
        /// the request, the response, the identity of the caller and the
        /// call duration to persistent storage.
        /// </summary>
        /// <param name="context">A reference to the Owin context.</param>
        /// <returns />
        public override async Task Invoke(IOwinContext context)
        {
            var request  = context.Request;
            var response = context.Response;

            // if it's an options call or tracing is not enabled, don't do anything
            if (IsOptionsRequest(context) || !_options.Trace || !_options.UrlPrefixes.Any(context.Request.Uri.AbsolutePath.StartsWith))
            {
                await Next.Invoke(context);

                return;
            }

            // start stopwatch to record call duration
            var sw = new Stopwatch();

            sw.Start();

            // record call time
            _options.HttpTrackingEntry.CallDateTime = DateTime.UtcNow;


            // replace the request stream in order to intercept downstream reads

            // Buffering mvc reponse
            HttpResponse httpResponse  = HttpContext.Current.Response;
            StreamHelper outputCapture = new StreamHelper(httpResponse.Filter);

            httpResponse.Filter = outputCapture;

            // Buffering Owin response if any
            IOwinResponse owinResponse       = context.Response;
            Stream        owinResponseStream = owinResponse.Body;

            owinResponse.Body = new MemoryStream();

            string input           = string.Empty;
            var    httpInputStream = HttpContext.Current?.Request?.InputStream;

            if (httpInputStream.Length > 0) // can access http call
            {
                SetEventRequestHeaders(HttpContext.Current?.Request, _options.HttpTrackingEntry);
                input = HttpUtility.UrlDecode(await new StreamReader(httpInputStream).ReadToEndAsync());
                HttpContext.Current.Request.InputStream.Position = 0;
            }
            else
            {
                // owin
                var requestBuffer = new MemoryStream();
                var requestStream = new ContentStream(requestBuffer, request.Body);
                request.Body = requestStream;
                SetEventRequestHeaders(request, _options.HttpTrackingEntry);
                input = HttpUtility.UrlDecode(await WriteContentAsync(requestStream, _options.HttpTrackingEntry.RequestHeaders, _maxRecordedRequestLength));
            }

            var anonymizedInput = _options.Anonymizer.Anonymize(input, request.Uri.ToString(), Direction.In);

            // record request/input
            _options.HttpTrackingEntry.Request = anonymizedInput;

            // invoke next middleware
            await Next.Invoke(context);

            // in case owin contains response
            if (outputCapture.CapturedData.Length == 0)
            {
                owinResponse.Body.Position = 0;
                await owinResponse.Body.CopyToAsync(owinResponseStream);
            }
            else
            {
                // in case we have  captured data from  mvc response copy it into owinResponse
                outputCapture.CapturedData.Position = 0;
                outputCapture.CapturedData.CopyTo(owinResponse.Body);
            }

            // set response headers.
            SetEventResponseHeaders(owinResponse, _options.HttpTrackingEntry);

            // record call duration
            sw.Stop();
            var elapsed = HumanizeTimespan(sw.Elapsed);

            _options.HttpTrackingEntry.CallDuration = elapsed;

            // finally  read final reponse  body
            owinResponse.Body.Seek(0, SeekOrigin.Begin);
            var apiResponse = _options.Anonymizer.Anonymize(new StreamReader(owinResponse.Body).ReadToEnd(), request.Uri.ToString(), Direction.Out);

            _options.HttpTrackingEntry.Response = apiResponse;
            await SaveTraceAsync(_options.Context, _options.HttpTrackingEntry);
        }
        /// <summary>
        /// Write content / request
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="headers"></param>
        /// <param name="maxLength"></param>
        /// <returns></returns>
        private static async Task <string> WriteContentAsync(ContentStream stream, IDictionary <string, string[]> headers, long maxLength)
        {
            var contentType = headers.ContainsKey(ContentTypeHeader) ? headers[ContentTypeHeader][0] : null;

            return(await stream.ReadContentAsync(contentType, maxLength));
        }