Skip to content

Commit 3e20239

Browse files
committed
outgoing request monitoring
1 parent 7e70cc6 commit 3e20239

File tree

1 file changed

+136
-30
lines changed

1 file changed

+136
-30
lines changed

ApiToolKit.cs

Lines changed: 136 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
using Google.Protobuf.WellKnownTypes;
44
using Google.Protobuf;
55
using Microsoft.AspNetCore.Http.Extensions;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Routing;
68
using NUnit.Framework;
79
using Newtonsoft.Json.Linq;
810
using Newtonsoft.Json;
911
using System.Diagnostics;
1012
using System.Text.Json;
11-
using Microsoft.AspNetCore.Http;
12-
using Microsoft.AspNetCore.Routing;
13-
14-
13+
using static System.Web.HttpUtility;
1514

1615
namespace ApiToolkit.Net
1716
{
@@ -35,6 +34,9 @@ public async Task InvokeAsync(HttpContext context)
3534
var responseBodyStream = new MemoryStream();
3635
var originalResponseBodyStream = context.Response.Body;
3736
context.Response.Body = responseBodyStream;
37+
Guid uuid = Guid.NewGuid();
38+
var msg_id = uuid.ToString();
39+
context.Items["APITOOLKIT_MSG_ID"] = msg_id;
3840

3941
try
4042
{
@@ -76,7 +78,7 @@ public async Task InvokeAsync(HttpContext context)
7678
}
7779
var payload = _client.BuildPayload("DotNet", stopwatch, context.Request, context.Response.StatusCode,
7880
System.Text.Encoding.UTF8.GetBytes(requestBody), System.Text.Encoding.UTF8.GetBytes(responseBody),
79-
responseHeaders, pathParams, urlPath, errors);
81+
responseHeaders, pathParams, urlPath, errors, msg_id);
8082

8183
await _client.PublishMessageAsync(payload);
8284
}
@@ -170,9 +172,9 @@ await PubSubClient.PublishAsync(new PubsubMessage
170172
}
171173
}
172174

173-
public ObservingHandler APIToolkitObservingHandler(HttpContext context)
175+
public ObservingHandler APIToolkitObservingHandler(HttpContext context, ATOptions? options = null)
174176
{
175-
return new ObservingHandler(context, PublishMessageAsync, BuildPayload);
177+
return new ObservingHandler(PublishMessageAsync, Metadata.ProjectId, context, options);
176178
}
177179

178180

@@ -191,7 +193,7 @@ public void ReportError(HttpContext context, Exception error)
191193
}
192194
}
193195

194-
public Payload BuildPayload(string SDKType, Stopwatch stopwatch, HttpRequest req, int statusCode, byte[] reqBody, byte[] respBody, Dictionary<string, List<string>> respHeader, Dictionary<string, string> pathParams, string urlPath, List<ATError> errors)
196+
public Payload BuildPayload(string SDKType, Stopwatch stopwatch, HttpRequest req, int statusCode, byte[] reqBody, byte[] respBody, Dictionary<string, List<string>> respHeader, Dictionary<string, string> pathParams, string urlPath, List<ATError> errors, string msg_id)
195197
{
196198
if (req == null || Metadata is null)
197199
{
@@ -218,9 +220,9 @@ public Payload BuildPayload(string SDKType, Stopwatch stopwatch, HttpRequest req
218220
ProjectId = projectId,
219221
ProtoMajor = majorVersion,
220222
ProtoMinor = minorVersion,
221-
QueryParams = req.Query.ToDictionary(kv => kv.Key, kv => kv.Value.ToList()),
223+
QueryParams = req.Query.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()),
222224
RawUrl = req.GetEncodedPathAndQuery(),
223-
Referer = req.Headers["Referer"].ToString(),
225+
Referer = req.Headers.Referer.ToString(),
224226
RequestBody = RedactJSON(reqBody, Config.RedactRequestBody),
225227
RequestHeaders = RedactHeaders(reqHeaders, Config.RedactHeaders),
226228
ResponseBody = RedactJSON(respBody, Config.RedactResponseBody),
@@ -229,14 +231,18 @@ public Payload BuildPayload(string SDKType, Stopwatch stopwatch, HttpRequest req
229231
StatusCode = statusCode,
230232
Timestamp = DateTime.UtcNow,
231233
UrlPath = urlPath,
232-
Errors = errors
234+
Errors = errors,
235+
ServiceVersion = Config.ServiceVersion,
236+
Tags = Config.Tags ?? new List<string> { },
237+
MsgId = msg_id
238+
233239
};
234240
}
235241

236242

237243

238244

239-
private ATError BuildError(Exception error)
245+
private static ATError BuildError(Exception error)
240246
{
241247
// Create an instance of ATError
242248
var atError = new ATError
@@ -309,6 +315,9 @@ public class Config
309315
public bool VerboseDebug { get; set; }
310316
public string RootUrl { get; set; }
311317
public string ApiKey { get; set; }
318+
public string ServiceVersion { get; set; }
319+
public List<string> Tags { get; set; }
320+
312321
public List<string> RedactHeaders { get; set; }
313322
public List<string> RedactRequestBody { get; set; }
314323
public List<string> RedactResponseBody { get; set; }
@@ -323,7 +332,7 @@ public class Payload
323332
public Dictionary<string, List<string>> RequestHeaders { get; set; }
324333

325334
[JsonProperty("query_params")]
326-
public Dictionary<string, List<string>> QueryParams { get; set; }
335+
public Dictionary<string, string> QueryParams { get; set; }
327336

328337
[JsonProperty("path_params")]
329338
public Dictionary<string, string> PathParams { get; set; }
@@ -372,6 +381,16 @@ public class Payload
372381
public long Duration { get; set; }
373382
[JsonProperty("errors")]
374383
public List<ATError>? Errors { get; set; }
384+
[JsonProperty("tags")]
385+
public List<string> Tags { get; set; }
386+
[JsonProperty("service_version")]
387+
public string ServiceVersion { get; set; }
388+
[JsonProperty("parent_id")]
389+
public string? ParentId { get; set; }
390+
391+
[JsonProperty("msg_id")]
392+
public string? MsgId { get; set; }
393+
375394
}
376395

377396

@@ -393,41 +412,128 @@ public class ATError
393412
public string StackTrace { get; set; }
394413
}
395414

415+
416+
public class ATOptions
417+
{
418+
public string? PathWildCard { get; set; }
419+
public List<string> RedactHeaders { get; set; }
420+
public List<string> RedactRequestBody { get; set; }
421+
public List<string> RedactResponseBody { get; set; }
422+
}
396423
public class ObservingHandler : DelegatingHandler
397424
{
398425
private readonly HttpContext _context;
399426
private readonly Func<Payload, Task> _publishMessageAsync;
400-
private readonly Func<string, Stopwatch, HttpRequest, int, byte[], byte[], Dictionary<string, List<string>>, Dictionary<string, string>, string, List<ATError>, Payload> _buildPayload;
401-
public ObservingHandler(HttpContext httpContext, Func<Payload, Task> publishMessage, Func<string, Stopwatch, HttpRequest, int, byte[], byte[], Dictionary<string, List<string>>, Dictionary<string, string>, string, List<ATError>, Payload> buildPayload)
427+
private readonly ATOptions _options;
428+
private readonly string _project_id;
429+
private readonly string? _msg_id;
430+
public ObservingHandler(Func<Payload, Task> publishMessage, string project_id, HttpContext? httpContext = null, ATOptions? options = null) : base(new HttpClientHandler())
402431
{
403432
_context = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
404433
_publishMessageAsync = publishMessage;
405-
_buildPayload = buildPayload;
434+
_options = options ?? new ATOptions { RedactHeaders = new List<string> { }, RedactRequestBody = new List<string> { }, RedactResponseBody = new List<string> { } };
435+
_project_id = project_id;
436+
if (httpContext != null)
437+
{
438+
if (httpContext.Items.TryGetValue("APITOOLKIT_MSG_ID", out var msg_id) && msg_id != null)
439+
{
440+
_msg_id = msg_id.ToString();
441+
}
442+
}
406443
}
407444

445+
408446
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
409447
{
410-
var requestInfo = new
448+
449+
var StartTime = DateTimeOffset.UtcNow;
450+
var Method = request.Method;
451+
var RequestUri = request.RequestUri;
452+
var reqHeaders = request.Headers.ToDictionary(h => h.Key, h => h.Value.ToList());
453+
var reqBody = "";
454+
if (request != null && request.Content != null)
411455
{
412-
StartTime = DateTimeOffset.UtcNow,
413-
Method = request.Method,
414-
RequestUri = request.RequestUri,
415-
Headers = request.Headers,
416-
Body = await request.Content.ReadAsStringAsync(),
417-
};
456+
reqBody = await request.Content.ReadAsStringAsync(cancellationToken);
457+
}
458+
459+
Stopwatch stopwatch = new Stopwatch();
460+
stopwatch.Start();
418461

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

421-
var responseInfo = new
464+
try
422465
{
423-
Duration = DateTimeOffset.UtcNow - requestInfo.StartTime,
424-
StatusCode = response.StatusCode,
425-
Headers = response.Headers,
426-
Body = await response.Content.ReadAsStringAsync(),
427-
};
466+
var Duration = DateTimeOffset.UtcNow;
467+
var StatusCode = response.StatusCode;
468+
var respHeaders = response.Headers.ToDictionary(h => h.Key, h => h.Value.ToList());
469+
var respBody = await response.Content.ReadAsStringAsync();
470+
var queryDictCol = ParseQueryString(RequestUri?.Query ?? "");
471+
472+
var queryDict = new Dictionary<string, string>();
473+
foreach (string key in queryDictCol)
474+
{
475+
queryDict[key] = queryDictCol[key] ?? "";
476+
}
477+
478+
stopwatch.Stop();
479+
480+
var payload = new Payload
481+
{
482+
Duration = stopwatch.ElapsedTicks * 100,
483+
Host = RequestUri?.Host.ToString() ?? "",
484+
Method = Method.ToString(),
485+
PathParams = ParsePathPattern(_options.PathWildCard ?? RequestUri?.AbsolutePath ?? "", RequestUri?.AbsolutePath ?? ""),
486+
ProjectId = _project_id,
487+
ProtoMajor = 1,
488+
ProtoMinor = 1,
489+
QueryParams = queryDict,
490+
RawUrl = RequestUri?.PathAndQuery ?? "",
491+
Referer = "",
492+
RequestBody = Client.RedactJSON(System.Text.Encoding.UTF8.GetBytes(reqBody), _options.RedactRequestBody ?? new List<string> { }),
493+
RequestHeaders = Client.RedactHeaders(reqHeaders, _options.RedactHeaders ?? new List<string> { }),
494+
ResponseBody = Client.RedactJSON(System.Text.Encoding.UTF8.GetBytes(respBody), _options.RedactResponseBody ?? new List<string> { }),
495+
ResponseHeaders = Client.RedactHeaders(respHeaders, _options.RedactHeaders ?? new List<string> { }),
496+
SdkType = "DotNetOutgoing",
497+
StatusCode = (int)StatusCode,
498+
ParentId = _msg_id,
499+
Timestamp = DateTime.UtcNow,
500+
UrlPath = _options.PathWildCard ?? RequestUri?.AbsolutePath ?? "",
501+
502+
};
503+
504+
await _publishMessageAsync(payload);
505+
return response;
506+
}
507+
catch (Exception err)
508+
{
509+
return response;
510+
}
511+
512+
}
513+
static Dictionary<string, string> ParsePathPattern(string pattern, string url)
514+
{
515+
var result = new Dictionary<string, string>();
516+
517+
var patternParts = pattern.Split('/');
518+
var urlParts = url.Split('/');
519+
520+
for (int i = 0; i < patternParts.Length; i++)
521+
{
522+
string patternPart = patternParts[i];
428523

524+
if (patternPart.StartsWith('{') && patternPart.EndsWith('}'))
525+
{
526+
string variableName = patternPart.Trim('{', '}');
527+
string urlPart = "";
528+
if (i < urlParts.Length)
529+
{
530+
urlPart = urlParts[i];
531+
}
532+
result[variableName] = urlPart;
533+
}
534+
}
429535

430-
return response;
536+
return result;
431537
}
432538
}
433539

0 commit comments

Comments
 (0)