为什么不将错误写入数据库?

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
您好,

我有未处理的异常记录器,它会将每个错误记录到表中。实际上,它可以工作,但是当我对超过1000个用户进行负载测试时,情况很少,错误不会写入此表中。 (成功950次,出现50个错误)为什么会发生这种情况?有任何想法吗? 50位用户甚至由于某种原因而没有开始?

C#:
public class UnhandledExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
            var ex = context.Exception;

            string strLogText = "";
            strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;
            strLogText += Environment.NewLine + "StackTrace ---\n{0}" + ex.StackTrace;
            strLogText += Environment.NewLine + "TargetSite ---\n{0}" + ex.TargetSite;

            if (ex.InnerException != null)
            {
                strLogText += Environment.NewLine + "Inner Exception is {0}" + ex.InnerException;//error prone
            }
            if (ex.Message != null)
            {
                strLogText += Environment.NewLine + "Message ---\n{0}" + ex.Message;//error prone
            }

            var requestedURi = (string)context.Request.RequestUri.AbsoluteUri;
            var requestMethod = context.Request.Method.ToString();
            var timeUtc = DateTime.Now;

            SqlErrorLogging sqlErrorLogging = new SqlErrorLogging();
            ApiError apiError = new ApiError()
            {
                Message = strLogText,
                RequestUri = requestedURi,
                RequestMethod = requestMethod,
                TimeUtc = DateTime.Now
            };
            sqlErrorLogging.InsertErrorLog(apiError);
        }
    }

C#:
public void InsertErrorLog(ApiError apiError)
        {
            using (var sqlConnection =
                new SqlConnection(ConfigurationManager.ConnectionStrings["GameContext"].ConnectionString))
            {
                try
                {
                    sqlConnection.Open();
                    using (SqlCommand cmd =
                        new SqlCommand(
                            "INSERT INTO [dbo].[API_Error] ([Message],[RequestMethod],[RequestUri],[TimeUtc]) VALUES (@Message, @RequestMethod, @RequestUri, @TimeUtc)",
                            sqlConnection))
                    {
                        cmd.Parameters.AddWithValue("@TimeUtc", apiError.TimeUtc);
                        cmd.Parameters.AddWithValue("@RequestUri", apiError.RequestUri);
                        cmd.Parameters.AddWithValue("@Message", apiError.Message);
                        cmd.Parameters.AddWithValue("@RequestMethod", apiError.RequestMethod);

                        cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }
                finally
                {
                    sqlConnection.Close();
                }
            }
        }
 

金西尼

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
3,501
地点
悉尼,澳大利亚
编程经验
10+
If you want to rethrow an exception in a catch block then you should just use throw rather than throw e because the latter will truncate the stack trace to the current method. Also, if you have a finally block then you aren't required to have a catch block, so having one 那 simply rethrows an exception is pointless.

Also, there's no need to nest multiple using blocks unless you specifically require code between them, which you don't in this case. You could do this:
C#:
using (var connection = new SqlConnection("..."))
using (var command = new SqlCommand("...", connection))
{
    // ...
   
    connection.Open();
    command.ExecuteNonQuery();
}
The fact 那 you are creating the SqlConnection with a using statement means 那 closing the connection is pointless. It will be closed implicitly when it is disposed at the end of the block.

As for the issue, you should add some Debug.WriteLine calls to your trycatch blocks. You can log the result of ExecuteNonQuery和whether any exception was thrown, which will tell you exactly how many records should have been inserted.
 
Last edited:

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
我怀疑这是您真正想要做的事情:
C#:
strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;

If ex.Source were "The End Of The World",结果字符串如下所示:"\ r \ n来源--- \ n {0}世界的尽头".

我认为您想要的结果是这样的:"\ r \ n资料来源--- \ n世界尽头" without the "{0}"卡在里面。您可以使用字符串插值来获得:
C#:
strLogText += Environment.NewLine + $"Source ---\n{ex.Source}" + ex.Source;

I also don't think you want the leading line break there as well as the embedded newline there. I recommend using a StringBuilder as well as some string interpolation So all of 那 above could be something like:
C#:
string FormatException(Exception ex, int depth = 0)
{
    string indent = ""
    if (depth > 0)
        indent.PadRight(depth * 4);

    var sb = new StringBuilder();
    IndentLine($"Source --- {ex.Source}");
    IndentLine($"StackTrace --- {ex.StackTrace}");
    IndentLine($"TargetSite --- {ex.TargetSite}");

    if (ex.InnerException != null)
    {
        IndentLine("--- Inner Exception ---");
        sb.Append(FormatException(ex.InnerException, depth + 1));
        IndentLine("--- End Inner Exception ---");
    }
    if (ex.Message != null)
    {
        IndentLine($"Message --- {ex.Message}");
    }
    return sb.ToString();

    void IndentLine(string s)
    {
        sb.Append(indent);
        sb.AppendLine(s);
    }
}

:

string strLogText = FormatException(ex);
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
谢谢 @jmcilhinney@跳伞,我将进行更改,但我想我无法清楚地说明我的问题。我要说的是,只有在尝试进行负载测试时才会发生这种情况。我在Jmeter上看到错误,但DB表中没有错误。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
JMeter报告的确切错误是什么?他们是500错误吗?他们是错误503吗?

如果您在IIS上运行,则可以尝试查看Windows事件日志以查看是否拒绝任何连接尝试。您也可以在IIS的Logs目录中查找以查看连接是否成功。

如果连接尝试未成功进入您的应用程序,那么您的应用程序内就没有日志可记录,因为您上面拥有的该记录器可能仅用于在代码中捕获异常时使用。

这与您过去几周一直用尽的SQL连接池所发布的代码库相同吗?也许您甚至无法打开连接以写入数据库?

我有一个运行在IIS上的Web服务,该服务可以轻松地每秒处理5000个会话/事务,因此就我个人而言,我认为您仅将1000个用户用于IIS就不会达到极限。也许您正在使所有IIS线程忙,因此它不能接受更多会话?
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
这与您过去几周一直用尽的SQL连接池所发布的代码库相同吗?也许您甚至无法打开连接以写入数据库?

我想你是对的。我的应用程序可以处理600个用户,但是600个之后会出现一些错误,这是由于我猜想是由于SQL连接耗尽所致。
 

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,923
地点
英国
编程经验
10+
使用块进行嵌套不是必需的,但是在可能的情况下使用块进行嵌套并不是一个坏习惯。

由于连接将是使用的第一个和最后一个对象,因此您可以将其包装在using块中,然后将命令放入using语句中,以与它一起使用。由于您的命令将在使用连接后执行,因此也无需将其包装在using语句中。由于您使用的是try块,因此您还应该阅读 使用带有try块的语句 -这为结构正确的代码带来了全新的辩论。我建议您在继续操作之前仔细阅读该链接,包括所有评论。

一旦理解了这些语句的用法,就应该稍微考虑一下代码并重新组织代码,以遵循考虑同时使用两个块的最佳实践。基本阅读 终于封锁 也很重要,因为该模块的功能类似于 一次性 使用语句。注意:如果CLR在进一步执行将导致更多异常的幻想下,它将不会要求执行dispose方法,因为它将不会执行 try-finally块将编译并强制执行哪种dispose方法,在我链接到您的MSDN文档中也对此进行了说明。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
谢谢 @谢平@跳伞。我检查了inetpub下的IIS日志。我在那里可以看到错误日志。而且我还检查了C:\ Windows \ System32 \ LogFiles \ HTTPERR日志。也有错误。

inetpub示例日志:
C#:
2019-08-09 20:19:48 11.1.11.1 POST /api/v2/game/purchase - 1907 - 11.11.111.111 Apache-HttpClient/4.5.7+(Java/1.8.0_191) - 500 0 0 155468

HTTPERR日志:
C#:
2019-08-08 18:05:06 99.99.999.999 59334 11.1.11.1 1907 HTTP/1.1 POST /api/v2/game/purchase - 3 Connection_Dropped_List_Full EPIN
2019-08-08 18:05:46 99.99.999.999 59801 11.1.11.1 1907 HTTP/1.1 POST /api/v2/game/purchase - 3 Request_Cancelled EPIN
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
"Connection_Dropped_List_Full" 和 "Request_Cancelled"会向我建议JMeter甚至无法连接到您的应用程序,并且不是您的应用程序在处理请求时抛出异常。 HTTP错误500也向我表明应用程序崩溃或未运行,这可能与断开的连接一致。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
我怀疑这是您真正想要做的事情:
C#:
strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;

If ex.Source were "The End Of The World",结果字符串如下所示:"\ r \ n来源--- \ n {0}世界的尽头".

我认为您想要的结果是这样的:"\ r \ n资料来源--- \ n世界尽头" without the "{0}"卡在里面。您可以使用字符串插值来获得:
C#:
strLogText += Environment.NewLine + $"Source ---\n{ex.Source}" + ex.Source;

I also don't think you want the leading line break there as well as the embedded newline there. I recommend using a StringBuilder as well as some string interpolation So all of 那 above could be something like:
C#:
string FormatException(Exception ex, int depth = 0)
{
    string indent = ""
    if (depth > 0)
        indent.PadRight(depth * 4);

    var sb = new StringBuilder();
    IndentLine($"Source --- {ex.Source}");
    IndentLine($"StackTrace --- {ex.StackTrace}");
    IndentLine($"TargetSite --- {ex.TargetSite}");

    if (ex.InnerException != null)
    {
        IndentLine("--- Inner Exception ---");
        sb.Append(FormatException(ex.InnerException, depth + 1));
        IndentLine("--- End Inner Exception ---");
    }
    if (ex.Message != null)
    {
        IndentLine($"Message --- {ex.Message}");
    }
    return sb.ToString();

    void IndentLine(string s)
    {
        sb.Append(indent);
        sb.AppendLine(s);
    }
}

:

string strLogText = FormatException(ex);
@跳伞 VS 2015社区版说不能在声明之前使用局部变量“ IndentLine”。是否缺少任何图书馆?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
抱歉,我正在使用C#7.0功能。这应该适用于C#6.0:
C#:
string FormatException(Exception ex, int depth = 0)
{
    string indent = ""
    if (depth > 0)
        indent.PadRight(depth * 4);

    var sb = new StringBuilder();
    sb.AppendLine($"{indent}Source --- {ex.Source}");
    sb.AppendLine($"{indent}StackTrace --- {ex.StackTrace}");
    sb.AppendLine($"{indent}TargetSite --- {ex.TargetSite}");

    if (ex.InnerException != null)
    {
        sb.AppendLine("{indent}--- Inner Exception ---");
        sb.Append(FormatException(ex.InnerException, depth + 1));
        sb.AppendLine("{indent}--- End Inner Exception ---");
    }
    if (ex.Message != null)
    {
        sb.AppendLine($"{indent}Message --- {ex.Message}");
    }
    return sb.ToString();
}
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
抱歉,这不在主题范围内,但我想知道这会很快吞噬连接池吗?我应该将其更改为Task(而不是void)并添加一个wait吗?

C#:
 public void IncomingMessageAsync(ApiLog apiLog)
        {
            apiLog.RequestType = "Request";
            var sqlErrorLogging = new ApiLogging();
            sqlErrorLogging.InsertLog(apiLog);
        }

        public void OutgoingMessageAsync(ApiLog apiLog)
        {
            apiLog.RequestType = "Response";
            var sqlErrorLogging = new ApiLogging();
            sqlErrorLogging.InsertLog(apiLog);
        }
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
什么 does the class ApiLogging do with regards to connections? Is the implementation of ApiLogging.InsertLog() exact to the implementation of your InsertErrorLog() in your post #1 above? Please share the code for ApiLogging.
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
这是ApiLogging。

C#:
public class ApiLogging
    {
        public void InsertLog(ApiLog apiLog)
        {
            try
            {
                using (var sqlConnection =
                    new SqlConnection(ConfigurationManager.ConnectionStrings["GameContext"].ConnectionString))
                {
                    using (SqlCommand cmd = new SqlCommand(
                        "INSERT INTO [dbo].[API_Log] ([Host],[Headers],[StatusCode],[TimeUtc],[RequestBody],[RequestedMethod],[UserHostAddress],[Useragent],[AbsoluteUri],[RequestType])    VALUES (@Host,@Headers,@StatusCode,getdate(),@RequestBody,@RequestedMethod,@UserHostAddress,@Useragent,@AbsoluteUri,@RequestType)",
                        sqlConnection))
                    {
                        sqlConnection.Open();
                        cmd.Parameters.AddWithValue("@Host", apiLog.Host);
                        cmd.Parameters.AddWithValue("@Headers", apiLog.Headers);
                        cmd.Parameters.AddWithValue("@StatusCode", apiLog.StatusCode);
                        cmd.Parameters.AddWithValue("@RequestBody", apiLog.RequestBody);
                        cmd.Parameters.AddWithValue("@RequestedMethod", apiLog.RequestedMethod);
                        cmd.Parameters.AddWithValue("@UserHostAddress", apiLog.UserHostAddress);
                        cmd.Parameters.AddWithValue("@Useragent", apiLog.Useragent);
                        cmd.Parameters.AddWithValue("@AbsoluteUri", apiLog.AbsoluteUri);
                        cmd.Parameters.AddWithValue("@RequestType", apiLog.RequestType);
                        cmd.ExecuteNonQuery();
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
那里的代码看起来像是干净地释放了连接。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
192
编程经验
10+
那里的代码看起来像是干净地释放了连接。

但是这种方法是异步的。是否应该一直保持异步状态?
C#:
namespace Game.Handlers
{
    public class RequestResponseHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            var requestedMethod = request.Method;
            var userHostAddress = HttpContext.Current != null ? HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
            var useragent = request.Headers.UserAgent.ToString();
            var requestMessage = await request.Content.ReadAsByteArrayAsync();
            var uriAccessed = request.RequestUri.AbsoluteUri;

            var responseHeadersString = new StringBuilder();
            foreach (var header in request.Headers)
            {
                responseHeadersString.Append($"{header.Key}: {String.Join(", ", header.Value)}{Environment.NewLine}");
            }

            var messageLoggingHandler = new MessageLogging();

            var requestLog = new ApiLog()
            {
                Headers = responseHeadersString.ToString(),
                AbsoluteUri = uriAccessed,
                Host = userHostAddress,
                RequestBody = Encoding.UTF8.GetString(requestMessage),
                UserHostAddress = userHostAddress,
                Useragent = useragent,
                RequestedMethod = requestedMethod.ToString(),
                StatusCode = string.Empty
            };

            messageLoggingHandler.IncomingMessageAsync(requestLog);

            var response = await base.SendAsync(request, cancellationToken);

            byte[] responseMessage = new byte[] { 0x1 };
            if (response.IsSuccessStatusCode)
            {
                if (response.Content != null)
                {
                    responseMessage = await response.Content.ReadAsByteArrayAsync();
                }
            }
            else
                responseMessage = Encoding.UTF8.GetBytes(response.ReasonPhrase);

            var responseLog = new ApiLog()
            {
                Headers = responseHeadersString.ToString(),
                AbsoluteUri = uriAccessed,
                Host = userHostAddress,
                RequestBody = Encoding.UTF8.GetString(responseMessage),
                UserHostAddress = userHostAddress,
                Useragent = useragent,
                RequestedMethod = requestedMethod.ToString(),
                StatusCode = response.StatusCode.ToString()
            };

            messageLoggingHandler.OutgoingMessageAsync(responseLog);
            return response;
        }
    }
}
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
什么 does code in post #18 got to do with the code from post #14 on which you asked for comments regarding SQL connections got to do with each other? I'm not seeing any calling to ApiLogging.InsertLog() anywhere in post #18.

此外,本身使用async / await不会创建线程。请阅读 没有线程

是否应该一直保持异步状态?
我假设您是在问这个问题,因为您从这里看到了最佳实践2: 异步/等待-异步编程最佳实践
我将引用史蒂芬·克莱伊的话:
解决此问题的最佳方法是允许异步代码通过代码库自然增长。如果您遵循此解决方案,则会看到异步代码扩展到其入口点,通常是事件处理程序或控制器操作。
 

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,923
地点
英国
编程经验
10+
是的,我记得几年前当我研究类似的文章时,这篇文章对我有所帮助。 Stephen Cleary,当然是个知识渊博的人。

OP:我有一个问题,没人问过您。
  • 您是如何结束一个项目的,而您对内部和代码问题的了解却很少?
  • 当然,如果您编写了它,则应该知道它在做什么。你写了吗?
  • 当然,如果您没有编写它,并且从另一个开发人员那里接手了这个项目,那么您也将能够理解允许执行的代码中正在发生的事情的概念?
你不知道我认为,如果我们了解您实际知道多少信息,对这里的任何人提供帮助都会更有帮助。和 不尝试 在这里听起来是居高临下的还是优越的,但是我已经在编码论坛上呆了很长时间了,我几乎可以总是告诉别人什么时候他们正在对自己很少了解的东西寻求答案。为了我自己的理智,我在向用户提供复杂的答案或提供他们不知道如何解释的代码之前研究用户的答复;这总是使我们教给您有关您以及我们的代码如何工作的每一个细节。这真的不是我们的工作。自我教育完全取决于您,因为到目前为止我们只能激励您。

如果您对没有经验的线程流的基本原理表示满意,那么我认为这没有帮助。而且,如果您回想起一两个主题,您是在问我关于线程的问题,因为您想引入多线程作为解决连接池问题的方法,而这在这方面并不能解决任何问题。尽管(有时)在另一个线程上执行长时间运行的进程非常有用,但这并不总是必需的,特别是在您不等待结果或进程不影响性能的情况下。相反,您应该专注于适用于所使用代码的基础知识,然后专注于吞噬连接池的内容,并关闭尚待解决的漏洞。

抱歉,这不在主题范围内,但我想知道这会很快吞噬连接池吗?我应该将其更改为Task(而不是void)并添加一个wait吗?
从更改为任务中您将获得什么,以及如何运行此任务?后面的API类会执行很长时间吗?我们不知道只有您知道这些方法的调用频率。
C#:
 public void IncomingMessageAsync(ApiLog apiLog)
        {
            apiLog.RequestType = "Request";
            var sqlErrorLogging = new ApiLogging();
            sqlErrorLogging.InsertLog(apiLog);
        }

        public void OutgoingMessageAsync(ApiLog apiLog)
        {
            apiLog.RequestType = "Response";
            var sqlErrorLogging = new ApiLogging();
            sqlErrorLogging.InsertLog(apiLog);
        }
我发现这很有趣,几乎很有趣,因为您遇到了连接池问题,但是您正在尝试将错误记录到数据库中? :不确定:
 
最佳 底部