Skip to content

Commit

Permalink
Add log placeholder for response header values (#135)
Browse files Browse the repository at this point in the history
* add placeholder for logging response header values

* formatting
  • Loading branch information
ozangoktan authored Nov 8, 2023
1 parent 80c7bcd commit bcae57b
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/HttpHandler/Fetch.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpContent> =
fun next ->
Expand Down
3 changes: 3 additions & 0 deletions src/HttpHandler/HttpContext.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ module PlaceHolder =
[<Literal>]
let ResponseContent = "ResponseContent"

[<Literal>]
let ResponseHeader = "ResponseHeader"

[<Literal>]
let Message = "Message"

Expand Down
13 changes: 12 additions & 1 deletion src/HttpHandler/Logging.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ 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<string, seq<string>>) (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
Expand All @@ -37,6 +44,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.
Expand Down
20 changes: 20 additions & 0 deletions test/Fetch.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}))

Expand All @@ -129,6 +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}"
)
|> cache

// Act
Expand All @@ -143,6 +150,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 @>
Expand All @@ -164,6 +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")
return responseMessage
}))

Expand All @@ -179,6 +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}"
)
|> cache

// Act
Expand All @@ -190,6 +207,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 @>
}
Expand Down

0 comments on commit bcae57b

Please sign in to comment.