public void Debug(string logType, string message, IDictionary <string, string> userFields = null)
        {
            if (isInitialized == false)
            {
                GamebaseLog.Error("InstanceLogger not initialized", this);
                return;
            }

            logger.Debug(logType, message, MakeFields(userFields));
        }
        private async Task LogBody(string fmtText, HttpContent content)
        {
            // This buffers the body content within the HttpContent if required.
            var bodyBytes = content != null ? await content.ReadAsByteArrayAsync().ConfigureAwait(false) : new byte[0];

            char[] bodyChars = new char[bodyBytes.Length];
            for (int i = 0; i < bodyBytes.Length; i++)
            {
                var b = bodyBytes[i];
                bodyChars[i] = b >= 32 && b <= 126 ? (char)b : '.';
            }
            InstanceLogger.Debug(fmtText, new string(bodyChars));
        }
        private void LogHeaders(string initialText, HttpHeaders headers1, HttpHeaders headers2)
        {
            var headers = (headers1 ?? Enumerable.Empty <KeyValuePair <string, IEnumerable <string> > >())
                          .Concat(headers2 ?? Enumerable.Empty <KeyValuePair <string, IEnumerable <string> > >()).ToList();
            var args = new object[headers.Count * 2];
            var fmt  = new StringBuilder(headers.Count * 32);

            fmt.Append(initialText);
            var argBuilder = new StringBuilder();

            for (int i = 0; i < headers.Count; i++)
            {
                fmt.Append($"\n  [{{{i * 2}}}] '{{{1 + i * 2}}}'");
                args[i * 2] = headers[i].Key;
                argBuilder.Clear();
                args[1 + i * 2] = string.Join("; ", headers[i].Value);
            }
            InstanceLogger.Debug(fmt.ToString(), args);
        }
        /// <summary>
        /// The main logic of sending a request to the server. This send method adds the User-Agent header to a request
        /// with <see cref="ApplicationName"/> and the library version. It also calls interceptors before each attempt,
        /// and unsuccessful response handler or exception handlers when abnormal response or exception occurred.
        /// </summary>
        protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                                      CancellationToken cancellationToken)
        {
            var    loggable         = IsLoggingEnabled && InstanceLogger.IsDebugEnabled;
            string loggingRequestId = "";

            if (loggable)
            {
                loggingRequestId = Interlocked.Increment(ref _loggingRequestId).ToString("X8");
            }

            int triesRemaining    = NumTries;
            int redirectRemaining = NumRedirects;

            Exception lastException = null;

            // Set User-Agent header.
            var userAgent = (ApplicationName == null ? "" : ApplicationName + " ") + UserAgentSuffix;

            // TODO: setting the User-Agent won't work on Silverlight. We may need to create a special callback here to
            // set it correctly.
            request.Headers.Add("User-Agent", userAgent);

            HttpResponseMessage response = null;

            do // While (triesRemaining > 0)
            {
                response?.Dispose();
                response      = null;
                lastException = null;

                // Check after previous response (if any) has been disposed of.
                cancellationToken.ThrowIfCancellationRequested();

                // We keep a local list of the interceptors, since we can't call await inside lock.
                List <IHttpExecuteInterceptor> interceptors;
                lock (executeInterceptorsLock)
                {
                    interceptors = executeInterceptors.ToList();
                }
                if (request.Properties.TryGetValue(ExecuteInterceptorKey, out var interceptorsValue) &&
                    interceptorsValue is List <IHttpExecuteInterceptor> perCallinterceptors)
                {
                    interceptors.AddRange(perCallinterceptors);
                }

                // Intercept the request.
                foreach (var interceptor in interceptors)
                {
                    await interceptor.InterceptAsync(request, cancellationToken).ConfigureAwait(false);
                }
                if (loggable)
                {
                    if ((LogEvents & LogEventType.RequestUri) != 0)
                    {
                        InstanceLogger.Debug("Request[{0}] (triesRemaining={1}) URI: '{2}'", loggingRequestId, triesRemaining, request.RequestUri);
                    }
                    if ((LogEvents & LogEventType.RequestHeaders) != 0)
                    {
                        LogHeaders($"Request[{loggingRequestId}] Headers:", request.Headers, request.Content?.Headers);
                    }
                    if ((LogEvents & LogEventType.RequestBody) != 0)
                    {
                        await LogBody($"Request[{loggingRequestId}] Body: '{{0}}'", request.Content).ConfigureAwait(false);
                    }
                }
                try
                {
                    // Send the request!
                    response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    lastException = ex;
                }

                // Decrease the number of retries.
                if (response == null || ((int)response.StatusCode >= 400 || (int)response.StatusCode < 200))
                {
                    triesRemaining--;
                }

                // Exception was thrown, try to handle it.
                if (response == null)
                {
                    var exceptionHandled = false;

                    // We keep a local list of the handlers, since we can't call await inside lock.
                    List <IHttpExceptionHandler> handlers;
                    lock (exceptionHandlersLock)
                    {
                        handlers = exceptionHandlers.ToList();
                    }
                    if (request.Properties.TryGetValue(ExceptionHandlerKey, out var handlersValue) &&
                        handlersValue is List <IHttpExceptionHandler> perCallHandlers)
                    {
                        handlers.AddRange(perCallHandlers);
                    }

                    // Try to handle the exception with each handler.
                    foreach (var handler in handlers)
                    {
                        exceptionHandled |= await handler.HandleExceptionAsync(new HandleExceptionArgs
                        {
                            Request           = request,
                            Exception         = lastException,
                            TotalTries        = NumTries,
                            CurrentFailedTry  = NumTries - triesRemaining,
                            CancellationToken = cancellationToken
                        }).ConfigureAwait(false);
                    }

                    if (!exceptionHandled)
                    {
                        InstanceLogger.Error(lastException,
                                             "Response[{0}] Exception was thrown while executing a HTTP request and it wasn't handled", loggingRequestId);
                        throw lastException;
                    }
                    else if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
                    {
                        InstanceLogger.Debug("Response[{0}] Exception {1} was thrown, but it was handled by an exception handler",
                                             loggingRequestId, lastException.Message);
                    }
                }
                else
                {
                    if (loggable)
                    {
                        if ((LogEvents & LogEventType.ResponseStatus) != 0)
                        {
                            InstanceLogger.Debug("Response[{0}] Response status: {1} '{2}'", loggingRequestId, response.StatusCode, response.ReasonPhrase);
                        }
                        if ((LogEvents & LogEventType.ResponseHeaders) != 0)
                        {
                            LogHeaders($"Response[{loggingRequestId}] Headers:", response.Headers, response.Content?.Headers);
                        }
                        if ((LogEvents & LogEventType.ResponseBody) != 0)
                        {
                            await LogBody($"Response[{loggingRequestId}] Body: '{{0}}'", response.Content).ConfigureAwait(false);
                        }
                    }
                    if (response.IsSuccessStatusCode)
                    {
                        // No need to retry, the response was successful.
                        triesRemaining = 0;
                    }
                    else
                    {
                        bool errorHandled = false;

                        // We keep a local list of the handlers, since we can't call await inside lock.
                        List <IHttpUnsuccessfulResponseHandler> handlers;
                        lock (unsuccessfulResponseHandlersLock)
                        {
                            handlers = unsuccessfulResponseHandlers.ToList();
                        }
                        if (request.Properties.TryGetValue(UnsuccessfulResponseHandlerKey, out var handlersValue) &&
                            handlersValue is List <IHttpUnsuccessfulResponseHandler> perCallHandlers)
                        {
                            handlers.AddRange(perCallHandlers);
                        }

                        // Try to handle the abnormal HTTP response with each handler.
                        foreach (var handler in handlers)
                        {
                            try
                            {
                                errorHandled |= await handler.HandleResponseAsync(new HandleUnsuccessfulResponseArgs
                                {
                                    Request           = request,
                                    Response          = response,
                                    TotalTries        = NumTries,
                                    CurrentFailedTry  = NumTries - triesRemaining,
                                    CancellationToken = cancellationToken
                                }).ConfigureAwait(false);
                            }
                            catch when(DisposeAndReturnFalse(response))
                            {
                            }

                            bool DisposeAndReturnFalse(IDisposable disposable)
                            {
                                disposable.Dispose();
                                return(false);
                            }
                        }

                        if (!errorHandled)
                        {
                            if (FollowRedirect && HandleRedirect(response))
                            {
                                if (redirectRemaining-- == 0)
                                {
                                    triesRemaining = 0;
                                }

                                errorHandled = true;
                                if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
                                {
                                    InstanceLogger.Debug("Response[{0}] Redirect response was handled successfully. Redirect to {1}",
                                                         loggingRequestId, response.Headers.Location);
                                }
                            }
                            else
                            {
                                if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
                                {
                                    InstanceLogger.Debug("Response[{0}] An abnormal response wasn't handled. Status code is {1}",
                                                         loggingRequestId, response.StatusCode);
                                }

                                // No need to retry, because no handler handled the abnormal response.
                                triesRemaining = 0;
                            }
                        }
                        else if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
                        {
                            InstanceLogger.Debug("Response[{0}] An abnormal response was handled by an unsuccessful response handler. " +
                                                 "Status Code is {1}", loggingRequestId, response.StatusCode);
                        }
                    }
                }
            } while (triesRemaining > 0); // Not a successful status code but it was handled.

            // If the response is null, we should throw the last exception.
            if (response == null)
            {
                InstanceLogger.Error(lastException, "Request[{0}] Exception was thrown while executing a HTTP request", loggingRequestId);
                throw lastException;
            }
            else if (!response.IsSuccessStatusCode && loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
            {
                InstanceLogger.Debug("Response[{0}] Abnormal response is being returned. Status Code is {1}", loggingRequestId, response.StatusCode);
            }

            return(response);
        }