Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mrc-5048: Add request ID into logs #38

Merged
merged 14 commits into from
Feb 29, 2024
Merged

mrc-5048: Add request ID into logs #38

merged 14 commits into from
Feb 29, 2024

Conversation

r-ash
Copy link
Member

@r-ash r-ash commented Feb 22, 2024

@plietar has added request_id into Rust API in this mrc-ide/outpack_server#58, if a request is received with header x-request-id it will be included in log messages, if one not sent a request ID will be generated and included in the logs.

This PR adds the same pattern so we can use it across all our R APIs. Hopefully with this, if we send a request from Kotlin backend to any of our services we can trace it through with this ID.

This PR will

  • Update github actions workflows (these were a fair bit out of date)
  • If request includes a header x-request-id it will return it in the response header. If one not sent, it will generate a uuid.
  • If logger is being used, it will add add request ID into all log messages
  • If API author uses the logger which porcelain configures (i.e. the logger with name equal to the package name), it will also add the request ID into all logs from this

@@ -1,5 +1,6 @@
porcelain_filters <- function(req, res) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These req, res args were not being used

@@ -0,0 +1,19 @@
add <- function(a, b) {
logger <- lgr::get_logger("add.chatty")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note how users will have to get the logger if they want to use the request ID (and all other things configured by porcelain). They could also pass this through from when the logger is created in the api. We could add a function to make this easier.

@@ -99,7 +99,7 @@ test_that("Nice error on parse failure", {
err <- expect_error(
roxygen2::parse_text(text))
expect_match(
err$message,
err$parent$message,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this has changed due to porcelain update

So that the request ID is available to logs whilst endpoint is being run
Copy link

codecov bot commented Feb 22, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (18511c4) to head (6c35bd6).

Additional details and impacted files
@@            Coverage Diff            @@
##            master       #38   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           19        19           
  Lines          975       992   +17     
=========================================
+ Hits           975       992   +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@plietar plietar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. Just some minor points

R/filter.R Outdated Show resolved Hide resolved
tests/testthat/test-input.R Outdated Show resolved Hide resolved
tests/testthat/test-input.R Outdated Show resolved Hide resolved
Copy link
Contributor

@M-Kusumgar M-Kusumgar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, tiny optional comment

R/filter.R Outdated Show resolved Hide resolved
R/logging.R Outdated
@@ -1,3 +1,5 @@
LOG_FILTER_REQUEST_ID_NAME <- "request_id" # nolint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The advantage of this constant seem reduced by (I'm guessing) it needing to match the name used in FilterInject. You could use the .list argument there and do

FilterInject$new(.list = set_names(request_id, LOG_FILTER_REQUEST_ID_NAME))

which would reuse this constant at least

R/filter.R Outdated
req$REQUEST_ID <- request_id
res$setHeader("x-request-id", request_id)
if (!is.null(logger)) {
logger$add_filter(lgr::FilterInject$new(request_id = request_id),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interaction with lgr feels like it's going to have performance issues each time (based on my previous experience with the package). Each request we'll be doing

  • add_filter
  • FilterInject creation
  • remove_filter

I presume this means that when the request comes through we're checking to see "do we have the appropriate header" then if so adding additional data to the log output? Any reason this can't be done directly in porcelain_log_postserialize where we have req already? It's possible I'm missing something here though.

OTOH, it might be worth looking to see how bad this is performance wise - see if it's actually an issue with a quick benchmark with and without the header being present and see how long a roundtrip takes?

Copy link
Member Author

@r-ash r-ash Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted about this in person but just putting this down here for future reference.

We can't just use porcelain_log_postserialize as we want to add the request ID into the logger so if a user writes a log call in their API impl it will pick up the request ID.

With plumber we have 4 hooks we can use preroute, postroute, preserialize and posterialize. In preroute we don't have a handle on the "HTTP_X_REQUEST_ID" so we need to do this after the preroute, but by actual code endpoint is run before we get to postroute. So we have to handle this in a filter.

I did some benchmarking on this, by running up the API in a background process with the add.chatty package I added in this PR. There isn't a huge amount of variation in timings

With no filter

rashton@wpia-dide270:~/projects/porcelain$ ./timeit curl -w "@curl_format.txt" -o /dev/null -s  "http://localhost:8552/?a=3&b=3"
Average: 0.00569371 N:  1000

With the filter which creates and removes the object every time

rashton@wpia-dide270:~/projects/porcelain$ ./timeit curl -w "@curl_format.txt" -o /dev/null -s  "http://localhost:9120/?a=3&b=3"
Average: 0.0055903 N:  1000

With updated filter which just changes the request ID on an the filter object which exists in cache

rashton@wpia-dide270:~/projects/porcelain$ ./timeit curl -w "@curl_format.txt" -o /dev/null -s  "http://localhost:9343/?a=3&b=3"
Average: 0.00546013 N:  1000

So very similar average times, less different than I was expecting. Feels nice to not have to create the object every time so have gone with that but previous impl I think read a little more simply so we can always revert back.

@r-ash r-ash requested a review from richfitz February 29, 2024 10:43
@r-ash r-ash merged commit 655da35 into master Feb 29, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants