From d162e65d7220d923f23fde27d57e7a0b2d4582f8 Mon Sep 17 00:00:00 2001 From: Ozan Goktan Date: Tue, 7 Nov 2023 08:36:00 +0100 Subject: [PATCH 1/2] add placeholder for logging response header values --- README.md | 1 + src/HttpHandler/Fetch.fs | 2 +- src/HttpHandler/HttpContext.fs | 3 +++ src/HttpHandler/Logging.fs | 12 +++++++++++- test/Fetch.fs | 14 ++++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ad7a1c..082b354 100644 --- a/README.md +++ b/README.md @@ -428,6 +428,7 @@ you may use are: - `Message` - A user-supplied message using `logWithMessage`. - `ResponseContent` - The response content received. Must implement `ToString` to give meaningful output. - `RequestContent` - The request content being sent. Must implement `ToString` to give meaningful output. +- `ResponseHeader[key]` - The response header received, replace `key` with the name of the header field. - `Url` - The URL used for fetching. **Note:** Oryx will not call `.ToString ()` but will hand it over to the `ILogger` for the actual string interpolation, diff --git a/src/HttpHandler/Fetch.fs b/src/HttpHandler/Fetch.fs index aadefbe..fb98f6e 100644 --- a/src/HttpHandler/Fetch.fs +++ b/src/HttpHandler/Fetch.fs @@ -59,7 +59,7 @@ module Fetch = request - /// Fetch content using the given context. Exposes `{Url}`, `{ResponseContent}`, `{RequestContent}` and `{Elapsed}` + /// Fetch content using the given context. Exposes `{Url}`, `{ResponseContent}`, `{ResponseHeader[key]}`, `{RequestContent}` and `{Elapsed}` /// to the log format. let fetch<'TSource> (source: HttpHandler<'TSource>) : HttpHandler = fun next -> diff --git a/src/HttpHandler/HttpContext.fs b/src/HttpHandler/HttpContext.fs index 70b9043..c8e0238 100644 --- a/src/HttpHandler/HttpContext.fs +++ b/src/HttpHandler/HttpContext.fs @@ -47,6 +47,9 @@ module PlaceHolder = [] let ResponseContent = "ResponseContent" + [] + let ResponseHeader = "ResponseHeader" + [] let Message = "Message" diff --git a/src/HttpHandler/Logging.fs b/src/HttpHandler/Logging.fs index cfcbff3..9a74000 100644 --- a/src/HttpHandler/Logging.fs +++ b/src/HttpHandler/Logging.fs @@ -13,7 +13,13 @@ open FSharp.Control.TaskBuilder module Logging = // Pre-compiled let private reqex = - Regex(@"\{(.+?)\}", RegexOptions.Multiline ||| RegexOptions.Compiled) + Regex(@"\{(.+?)(\[(.+?)\])?\}", RegexOptions.Multiline ||| RegexOptions.Compiled) + + let private getHeaderValue (headers: Map>) (key : string): string = + match headers.TryGetValue(key) with + | (true, v) -> + match Seq.tryHead v with | first -> if first.IsSome then first.Value else String.Empty + | (false, _) -> String.Empty let private log' logLevel ctx content = match ctx.Request.Logger with @@ -37,6 +43,10 @@ module Logging = |> Option.toObj :> _ | PlaceHolder.ResponseContent -> content :> _ + | PlaceHolder.ResponseHeader -> + // GroupCollection returns empty string values for indexes beyond what was captured, therefore + // we don't cause an exception here if the optional second group was not captured + getHeaderValue (ctx.Response.Headers) (match'.Groups.[3].Value) :> _ | key -> // Look for the key in the extra info. This also enables custom HTTP handlers to add custom // placeholders to the format string. diff --git a/test/Fetch.fs b/test/Fetch.fs index c376a7a..318a19a 100644 --- a/test/Fetch.fs +++ b/test/Fetch.fs @@ -116,6 +116,9 @@ let ``Get with logging is OK`` () = (task { let responseMessage = new HttpResponseMessage(HttpStatusCode.OK) responseMessage.Content <- new PushStreamContent("42") + responseMessage.Headers.Add("test", value="test-value") + responseMessage.Headers.Add("test2", value="not-included-in-log") + responseMessage.Headers.Add("X-Request-ID", value="test-request-id") return responseMessage })) @@ -129,6 +132,7 @@ let ``Get with logging is OK`` () = |> withMetrics metrics |> withLogger logger |> withLogLevel LogLevel.Debug + |> withLogFormat (HttpContext.defaultLogFormat + "\n← {ResponseHeader[test]}\n← {ResponseHeader[X-Request-ID]}\n← {ResponseHeader}") |> cache // Act @@ -143,6 +147,9 @@ let ``Get with logging is OK`` () = // Assert test <@ logger.Output.Contains "42" @> test <@ logger.Output.Contains "http://test.org" @> + test <@ logger.Output.Contains "test-value" @> + test <@ logger.Output.Contains "test-request-id" @> + test <@ logger.Output.Contains "not-included-in-log" = false @> test <@ Result.isOk result @> test <@ metrics.Retries = 0L @> test <@ metrics.Fetches = 1L @> @@ -164,6 +171,9 @@ let ``Post with logging is OK`` () = retries <- retries + 1 let responseMessage = new HttpResponseMessage(HttpStatusCode.OK) responseMessage.Content <- new PushStreamContent("""{ "pong": 42 }""") + responseMessage.Headers.Add("test", value="test-value") + responseMessage.Headers.Add("test2", value="not-included-in-log") + responseMessage.Headers.Add("X-Request-ID", value="test-request-id") return responseMessage })) @@ -179,6 +189,7 @@ let ``Post with logging is OK`` () = |> withHeader ("api-key", "test-key") |> withLogger (logger) |> withLogLevel LogLevel.Debug + |> withLogFormat (HttpContext.defaultLogFormat + "\n← {ResponseHeader[test]}\n← {ResponseHeader[X-Request-ID]}\n← {ResponseHeader}") |> cache // Act @@ -190,6 +201,9 @@ let ``Post with logging is OK`` () = test <@ logger.Output.Contains json @> test <@ logger.Output.Contains msg @> test <@ logger.Output.Contains "http://testing.org" @> + test <@ logger.Output.Contains "test-value" @> + test <@ logger.Output.Contains "test-request-id" @> + test <@ logger.Output.Contains "not-included-in-log" = false @> test <@ Result.isOk result @> test <@ retries' = 1 @> } From a15c09dd4345254ce4da2d7c0519aa07061e559e Mon Sep 17 00:00:00 2001 From: Ozan Goktan Date: Tue, 7 Nov 2023 08:45:26 +0100 Subject: [PATCH 2/2] formatting --- src/HttpHandler/Logging.fs | 11 ++++++----- test/Fetch.fs | 22 ++++++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/HttpHandler/Logging.fs b/src/HttpHandler/Logging.fs index 9a74000..7f8e94b 100644 --- a/src/HttpHandler/Logging.fs +++ b/src/HttpHandler/Logging.fs @@ -15,11 +15,12 @@ module Logging = let private reqex = Regex(@"\{(.+?)(\[(.+?)\])?\}", RegexOptions.Multiline ||| RegexOptions.Compiled) - let private getHeaderValue (headers: Map>) (key : string): string = + let private getHeaderValue (headers: Map>) (key: string) : string = match headers.TryGetValue(key) with - | (true, v) -> - match Seq.tryHead v with | first -> if first.IsSome then first.Value else String.Empty - | (false, _) -> String.Empty + | (true, v) -> + match Seq.tryHead v with + | first -> if first.IsSome then first.Value else String.Empty + | (false, _) -> String.Empty let private log' logLevel ctx content = match ctx.Request.Logger with @@ -43,7 +44,7 @@ module Logging = |> Option.toObj :> _ | PlaceHolder.ResponseContent -> content :> _ - | PlaceHolder.ResponseHeader -> + | PlaceHolder.ResponseHeader -> // GroupCollection returns empty string values for indexes beyond what was captured, therefore // we don't cause an exception here if the optional second group was not captured getHeaderValue (ctx.Response.Headers) (match'.Groups.[3].Value) :> _ diff --git a/test/Fetch.fs b/test/Fetch.fs index 318a19a..bbbbab0 100644 --- a/test/Fetch.fs +++ b/test/Fetch.fs @@ -116,9 +116,9 @@ let ``Get with logging is OK`` () = (task { let responseMessage = new HttpResponseMessage(HttpStatusCode.OK) responseMessage.Content <- new PushStreamContent("42") - responseMessage.Headers.Add("test", value="test-value") - responseMessage.Headers.Add("test2", value="not-included-in-log") - responseMessage.Headers.Add("X-Request-ID", value="test-request-id") + responseMessage.Headers.Add("test", value = "test-value") + responseMessage.Headers.Add("test2", value = "not-included-in-log") + responseMessage.Headers.Add("X-Request-ID", value = "test-request-id") return responseMessage })) @@ -132,7 +132,10 @@ let ``Get with logging is OK`` () = |> withMetrics metrics |> withLogger logger |> withLogLevel LogLevel.Debug - |> withLogFormat (HttpContext.defaultLogFormat + "\n← {ResponseHeader[test]}\n← {ResponseHeader[X-Request-ID]}\n← {ResponseHeader}") + |> withLogFormat ( + HttpContext.defaultLogFormat + + "\n← {ResponseHeader[test]}\n← {ResponseHeader[X-Request-ID]}\n← {ResponseHeader}" + ) |> cache // Act @@ -171,9 +174,9 @@ let ``Post with logging is OK`` () = retries <- retries + 1 let responseMessage = new HttpResponseMessage(HttpStatusCode.OK) responseMessage.Content <- new PushStreamContent("""{ "pong": 42 }""") - responseMessage.Headers.Add("test", value="test-value") - responseMessage.Headers.Add("test2", value="not-included-in-log") - responseMessage.Headers.Add("X-Request-ID", value="test-request-id") + responseMessage.Headers.Add("test", value = "test-value") + responseMessage.Headers.Add("test2", value = "not-included-in-log") + responseMessage.Headers.Add("X-Request-ID", value = "test-request-id") return responseMessage })) @@ -189,7 +192,10 @@ let ``Post with logging is OK`` () = |> withHeader ("api-key", "test-key") |> withLogger (logger) |> withLogLevel LogLevel.Debug - |> withLogFormat (HttpContext.defaultLogFormat + "\n← {ResponseHeader[test]}\n← {ResponseHeader[X-Request-ID]}\n← {ResponseHeader}") + |> withLogFormat ( + HttpContext.defaultLogFormat + + "\n← {ResponseHeader[test]}\n← {ResponseHeader[X-Request-ID]}\n← {ResponseHeader}" + ) |> cache // Act