0

我使用此博客文章DelegatingHandler中的详细信息构建了一个请求日志记录。

我不想阅读请求或响应正文(在潜在的大容量 Web 服务上加倍这项工作似乎很愚蠢);我要做的就是为每个请求分配一个唯一的 ID,记录 URI 和标头;将请求 ID 写入另一个标头中的响应,然后记录响应(成功/失败)和发生的任何异常。

protected override System.Threading.Tasks.Task<HttpResponseMessage> 
  SendAsync(HttpRequestMessage request, 
  System.Threading.CancellationToken cancellationToken)
{
  DateTime receivedUTC = DateTime.UtcNow;

  //I use the Request properties to persist a Request's ID
  var requestID = request.GetRequestUID();
  if (requestID == null)
    requestID = request.SetRequestUID();

  //grab the base response task
  var baseResult = base.SendAsync(request, cancellationToken);

  return baseResult.ContinueWith(innerTask =>
  {
    var tcs = new TaskCompletionSource<HttpResponseMessage>();

    //note I've got rid of SynchronizationContext code out of this
    //to keep it shorter

    //construct the log packet
    LogData toLog = new LogData()
        {
          ReceivedUTC = receivedUTC,
          Request = request,
          RequestID = requestID,
        };

    //get the response
    try
    {
      tcs.SetResult(innerTask.Result);
      //NOTE - If this request actually fails response serialization,
      //       Then the above result will actually look fine, because
      //       The error hasn't occurred yet!
      toLog.Response = innerTask.Result;
      //this adds a header
      AddRequestIDToResponse(toLog.Response, requestID);
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
      toLog.Ex = ex;
    }

    //fire the logging call asynchronously (code elided, just some DB work)
    Task.Factory.StartNew(() => Log(toLog));
    return tcs.Task;
  }).Unwrap();
}

这对于几乎所有请求都非常有效,除非发生异常,同时ObjectContent从操作方法返回的内容被序列化为响应。

如果发生这种情况,则上面的代码已经触发,并且它在上面的 try/catch 块中看到的响应消息显示为正常200,其中ObjectContent包含最终将无法序列化的对象。这确实是有道理的——因为尚未发生对响应主体的序列化。

如何更改此代码(不强制读取ObjectContent)以确保我看到的响应消息是客户端在内容格式化后收到的实际消息?我应该看一个不同的扩展点吗?

4

1 回答 1

3

作为一个试探性的答案,直到有人(希望)出现并证明我错了;如果不复制和粘贴大量代码,这似乎在 Web API 管道本身内几乎是不可能的。

内容序列化在 Web API 管道的最顶端触发 - 在HttpControllerHandler其中,HttpContent被复制到底层HttpResponseOutputStream内部静态方法中称为HttpControllerHandler.ConvertResponse. 类本身没有足够的扩展点,无法在正确的位置注入任务,以便能够在 Web API 管道中记录实际响应。

所以我面临:

  • 从源代码HttpControllerHandler中提取全部内容并将其导入我的项目(以及它使用的所有内部组件,这不是一个好主意),然后相应地对其进行自定义。

  • 只需尽早强制序列化并捕获异常 - 我可以记录异常但不能记录响应。但是,这有一个小问题
    - 将请求 ID 写入响应非常
    重要,并且当这些格式异常发生时,ID 不会被写入,因为它会将HttpControllerHandler整个
    响应替换为错误响应!

  • 从 Web API 记录更高级别的请求。这
    并不理想,因为我只想记录Web API请求;并且
    日志记录代码依赖于当前请求的
    HttpConfiguration 对象,以便获取特定于请求的 DI
    容器来解析数据库连接(它是一个多租户 Web 服务)。

最终,唯一真正的选择是最后一个 - 在 IIS 或 Asp.Net 级别上完成工作。因此,尽管 Web API 中有所有这些可扩展点——实际上你无法在其中实现“完整”的日志记录解决方案——这真的令人失望。

于 2012-08-31T11:22:16.420 回答