Problem/Motivation
This issue is a follow up to #1415860: Add manual instrumentation of RUM?.
Beginning in Drupal 10.2.0, Drupal now sets a content-length header for most responses.
This breaks New Relic's auto-instrumentation:
Auto-instrumentation does not work when the HTTP header field Content-Length is set. To use browser monitoring in this situation, disable auto-instrumentation and manually insert the JavaScript header and footer into your templates.
Per Manually instrument via agent API , newrelic_get_browser_timing_header() and
newrelic_get_browser_timing_footer() need inserted into the header and footer, respectively.
Proposed resolution
- Replace the 'disable_autorum' setting with a 'rum_instrumentation' setting with three options: 'Disable browser monitoring' (disabled), 'Allow auto-instrumentation' (auto), and 'Manual instrumentation' (manual).
- Conditional upon the setting, insert New Relic markup.
Remaining tasks
Merge request- Testing/review
User interface changes
Replace the 'Disable AutoRUM' checkbox with a 'RUM instrumentation' select element on the 'new_relic_rpm.settings' form.
API changes
None.
Data model changes
'disable_autorum' setting is replaced by a 'rum_instrumentation' setting for 'new_relic_rpm.settings'.
Config change is handled by new_relic_rpm_post_update_instrumentation().
| Comment | File | Size | Author |
|---|
Issue fork new_relic_rpm-3444218
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
chris burge commentedComment #4
chris burge commentedNew Relic updated their docs:
For manual instrumentation to be enabled, the
disable_autorumsetting will need to be disabled.I'm thinking we'd be better off to remove the
disable_autorumsetting and replace it with arum_instrumentationsettings with three options:disabled,auto, andmanual.Comment #5
chris burge commentedComment #6
chris burge commentedComment #7
erik_mc commentedInterested in having this fix ready for a client site! Do you think it may be available later this month?
Comment #8
chris burge commented@Erik_MC - If your team would be able to test the merge request and report back, that would be helpful in moving the issue forward. The MR is coded based on New Relic's documentation; however, I don't have a New Relic instance to test against myself.
Comment #9
vipin.mittal18I have verified that Javascript snippets are not injected on Drupal 10.2x version, and after adding Chris MR !5, it is working correctly. Additionally, I have attached a video of the verification process.
Comment #10
chris burge commented@vipin.mittal18 - Thank you for your review.
It's not explicitly stated above, but here's the expected behavior for each of the three settings:
Pre 10.2.0
Disable browser monitoring - No JS inserted
Allow auto-instrumentation - JS inserted (assuming auto-instrumentation is enabled on server)
Manual instrumentation - JS inserted
10.2.0+
Disable browser monitoring - No JS inserted
Allow auto-instrumentation - No JS inserted (because auto-instrumentation is incompatible with D10.2.0+)
Manual instrumentation - JS inserted
Another way of thinking about "Allow auto-instrumentation" is "do nothing".
Comment #11
vipin.mittal18My opinion is same as yours @chris
The maintainers are requested to release these fixes as most of the customers are being impacted and losing monitoring and tracking.
Comment #12
neclimdulMakes sense. I remember browser instrumentation generally not working so I've never run it though so I've got some questions for people that do use it:
1. Are there possibly additional problems with caching with how manual adds adds code? Specifically, is the "header" being added unique to the request? I see the settings tag but should this be a lazy callback or have additional tags to avoid page/render caches?
2. In the post update, should it take the Drupal version into account and migrate to manual instead of migrating to a auto that behaves like manual with a not immediately obvious warning? Or maybe it should warn the user during the update? Unsure.
Comment #13
chris burge commented@neclimdul - Thanks for reviewing this merge request!
I'm not aware of any caching-related issues. The two functions,
newrelic_get_browser_timing_header()andnewrelic_get_browser_timing_footer()are unaware of anything related to the request or the response. In the 7.x branch of this module, they are implemented without any accommodations for caching.All that the post_update function does is preserve the same behavior following the update. The merge request does add a check to
new_relic_rpm_requirements()that alerts site owners about the issue of auto-instrumentation and D10.2.0+.Comment #14
chris burge commentedHere's a screenshot of the requirements error message, which is added in the MR:
We could run the same compatibility check in the post_update function as in
new_relic_rpm_requirements()and return a warning string if the site is configured to allow auto-instrumentation on D10.2.0+.Comment #15
jack.minster commentedThank you for this patch!
We are actively using it in a production environment. It does seem to work, however, there has been question about the placement of the snippets.
Shouldn't the JS timings header code get inserted into the HEAD tag? Would it make more sense to use something like
hook_page_attachments()to move it up as much as possible?Per the New Relic manual instrumentation documentation:
Same with the footer:
Comment #16
chris burge commented@jack.minster - Thanks for the feedback! I'm glad to hear the MR code is working for you all.
Re placement of the header JS, I do see that in their documentation, too; however, when I look at their WordPress and Drupal 7 examples, they're calling the
newrelic_get_browser_timing_header()inside<body>. When I look at rendered page output, I'm seeing it almost after the<body>tag. Is it appearing farther down in your application's markup?The placement of footer JS is trickier. The MR currently uses
hook_page_bottom()to insert the footer JS; however, most JS is loaded after that before the</body>tag. I've tried various methods to address this without success:1. Call
hook_page_attachments()to add a script toscripts_bottom:This isn't allowed:
2. Register a library, pass the return value of
newrelic_get_browser_timing_footer()viadrupalSettingsto a JS file, and then pass that value intoeval():new_relic_rpm.libraries.yml:
new_relic_rpm.module:
js/new-relic-browser-monitoring.js:
This executes the JS code in
scripts_bottom; however, we can't weight the library with a high value to push the JS insertion to the bottom.Exception:
I'm at a bit of a loss. It may not be possible to get that JS code immediately before
</body>using Drupal's API. It may be necessary to customize html.twig.html.That said, if someone could weigh in regarding the placement of the footer JS before/after all of the libraries JS, that would be helpful. It strikes me that we may not get valid browser monitoring results if the footer JS is inserted before the rest of the JS on the page.
Comment #17
jack.minster commented@chris-burge I know that NR also shows examples with the header snippet inside the
bodytag instead of inside theheadtag, which is contradictory. It does render as the first child element of thebodytag currently, though. I'm waiting for some more specific from some of our NR performance experts to see if they recommend a change.As for the footer, I'm not sure about that either. I've asked our lead developer for some feedback as I know he was able to get the header snippet up in the
headtag, perhaps he has ideas for the footer snippet as well.Comment #18
chris burge commented@jack.minster - I'll be interested in feedback you get from your New Relic performance experts.
Re the footer JS, I found that you can control library weights by decorating the
html_response.attachments_processorservice. I need to clean up my local code, and then I'll push it to the MR.Comment #19
chris burge commentedComment #20
chris burge commentedGitLab CI isn't configure for this project yet, so automated tests won't run, but tests are passing locally:
Comment #21
pieterdtt commented@jack.minster @chris-burge
Patch for making head injection configurable.
Comment #22
chris burge commentedWhen I look at a Drupal site where RUM auto-instrumentation is used, I do find the header JS in
<head>. I think that's the way to go.Comment #23
jack.minster commented@chris-burge for some reason the library and settings aren't getting attached in our Acquia environment and therefore the footer snippet isn't getting generated/executed. We're going to localize the patch prior to your last commit and use it as a local patch until we determine the issue.
The patch from @pieterdtt was to demonstrate how he get the header snippet moved up into the
headtag if you'd like to investigate that as an option.Comment #24
chris burge commented@jack.minster - Thanks for that feedback. I'll look into the footer JS insertion issue. I've been doing some work with the test coverage for that piece. One change I've made (but haven't pushed yet) is to execute JS that adds an element to the DOM in the test. The current test results in a false positive.
I do have some work in progress that moves the header JS into
<head>.@jack.minster - Would you mind sending me a DM on my Drupal.org contact form with your contact info and Acquia application docroot?
Comment #25
chris burge commented@jack.minster - Could you try out the latest code push to the MR? The header JS is now rendered in
<HEAD>near the very top. I also abandoned the library hack for the footer. InHtmlResponseAttachmentsProcessorDecorator, the MR is now modifying the::processAssetLibraries()method to insert the JS intoscripts_bottom, bypassing the#attachedtypes restrictions of::processAttachments().Comment #26
chris burge commented@pieterdtt - Thanks for patch #21. I incorporated it into the MR. Based on how New Relic's auto-instrumentation inserts the header JS into
<head>, I think the best thing is to always insert the JS there (i.e. no need for placement configuration).Comment #27
neclimdulI'm not sure I understand why we're not using standard methods for attaching things to the footer.
Comment #28
chris burge commented@neclimdul - Drupal's APIs don't provide a way to insert the New Relic footer JS below Javascript inserted by Drupal's Libraries API. NR's docs for manual instrumentation state:
Below is some sample HTML output from a fresh Drupal site. At the very top is where markup is inserted with
hook_page_bottom(). At the bottom is code inserted withHtmlResponseAttachmentsProcessorDecorator::processAssetLibraries().Using a decorator preserves the original service and also allows other code to decorate the
html_response.attachments_processorservice, as well.@jack.minster may wish to weigh in, as well.
Comment #29
chris burge commentedJust pushed code to properly decorate the service. Unfortunately, seven of the methods on the class are protected, so I had to copy/paste them all over to the decorator. (The decorator can't access protected methods on the inner service.) I also added a test to ensure the decorator doesn't break core functionality.
Comment #30
chris burge commentedLast code push takes a different approach (still using a decorator); however, it uses
str_replace()to insert the rendered footer JS into the rendered markup before returning$this->decorated->processAttachments($response). This approach avoids the code duplication issue.Comment #31
chris burge commentedI received some feedback out of thread that I investigated. Here's a summary of the feedback:
The header JS is inserted using
hook_page_attachments(), complete with a cache dependency. Simply put, it works as designed. Some wizardry is needed to get the footer JS inserted below other JS. What appears to be happening is thatnewrelic_get_browser_timing_footer()is getting called multiple times during the same request. If eithernewrelic_get_browser_timing_header()ornewrelic_get_browser_timing_footer()are called more than once during the same request, they'll return an empty string on subsequent requests. I verified this manually.I'm debating abandoning the service decoration approach and using an event subscriber to alter the DOM directly. I'm also considering modifying
ExtensionAdapter::getBrowserTimingHeader()andExtensionAdapter::getBrowserTimingFooter()to leveragedrupal_static().Comment #32
chris burge commentedWhen big_pipe is enabled,
HtmlResponseAttachmentsProcessor->processAttachments()gets called several times, which meansnewrelic_get_browser_timing_footer()gets called multiple times, which means all calls after the initial call return an empty string. The latest MR code usesdrupal_static()to cache the return value ofnewrelic_get_browser_timing_footer().In manual testing, I was able to reproduce this behavior with Big Pipe enabled; however, enabling Big Pipe in
InstrumentationTestdidn't result innewrelic_get_browser_timing_footer()being called multiple times, so I added an event subscriber to the new_relic_rpm_intstrumentation_test module to simulate the behavior.Comment #33
afireintheattic commentedHello! I am testing this on a site running Drupal 10.2.4, with this patch applied and "RUM Instrumentation" set to "Manual instrumentation"; however, even after clearing caches this does not seem to be adding any scripts to the header or footer. Is there perhaps a step that I am missing?
Comment #34
chris burge commented@afireintheattic - Is the New Relic PHP extension loaded?
A quick way to check is to navigate to the 'Status Report' page and scroll down to 'New Relic PHP Library', which should have a value of 'Exists'.
Comment #35
afireintheattic commentedHey Chris, thanks for following up! It looks like that extension isn't loaded, but I am testing this locally (via Lando). Unfortunately, New Relic is currently only added to the Production server, and my client requires a better way to verify this on lower environments before it will be approved to add to Production. Looking over the codebase and it seems like there is a pretty hard dependency on the New Relic PHP extension being installed, so I assume there is not a way to test whether or not the manual instrumentation option is properly adding the RUM JS unless testing on a server that has the PHP extension installed as well?
Comment #36
jack.minster commented@afireintheattic - I was able to get the NR agent running with ddev using the config files here:
https://github.com/ddev/ddev-contrib/pull/103#issuecomment-1605581669
I know you're running lando (with which I'm not familiar) but perhaps that will help you test locally?
Comment #37
chris burge commentedThere is some interaction between Drupal's internal cache system and
newrelic_get_browser_timing_footer()that I can't explain. I'm logged in as user 1. If I load any admin page, the footer JS is consistently rendered. If I visit non-admin pages, the footer JS is only rendered on the first page load after a cache rebuild. I even tried disabling the decorator and acting on the markup directly in an event subscriber, acting on the SymfonyResponseEvent. Stashing the footer JS in the default cache bin seems to do the trick, however.Comment #38
jack.minster commented@chris-burge - I tested this in my local ddev environment as well as in one of our Acquia environments and so far everything seems to be working as expected. The snippets are rendering in the "correct" place (high up in the
<head>tag and pretty low in the<body>tag).I have also confirmed that browser page view timing, errors and session traces are appearing in New Relic as well. I will try to see if I can get some additional eyes on this as well!
Comment #39
jack.minster commented@chris-burge - one of our performance engineers reviewed the code snippets in our staging environment and confirmed that everything looks good! If your MR doesn't get any traction in the next several days, I'll be applying the lastest version of it as a patch to our code base so it can go to production. Thanks for all your work!
Comment #40
phenaproximaI don't have any domain knowledge here but I gave this a code review and there are some things I think could be tightened up, and one outright bug-creating condition that should be fixed before this is merged.
Comment #41
chris burge commentedThere's too much going on in this MR to not be running tests. I added .gitlab-ci.yml. The maintainers should feel free to remove the file and commit separately in #3446404: Switch from Drupal CI to GitLab CI before merging.
Comment #42
chris burge commentedTests are passing again - now to address the rest of the technical review.
Comment #43
chris burge commented@phenaproxima - Thanks for the code review
Comment #44
chris burge commented@neclimdul - Sorry for flooding your inbox, but I think we're really close on this MR now.
In terms of outstanding items, there's been discussion around how to describe the settings' behaviors:
https://git.drupalcode.org/project/new_relic_rpm/-/merge_requests/5#note...
I'm open to revising documentation and relabeling. It strikes me the machine name 'auto' could be better named 'no-action'.
There was also discussion regarding the UpdateTest test class:
https://git.drupalcode.org/project/new_relic_rpm/-/merge_requests/5#note...
Comment #45
kamleshpatidar commentedAdded patch https://git.drupalcode.org/project/new_relic_rpm/-/merge_requests/5 on Drupal 10.2 website. it is working fine.
Comment #46
chris burge commentedNote: We may be able to get rid of the
HtmlResponseAttachmentsProcessorDecoratorcode when #3448503: Allow modules to use #attached for additional data lands in core.Comment #47
chris burge commentedOn the latest commit, I set the
decoration_priorityto-100so that ournew_relic_rpm.html_response.attachments_processordecorator gets called earlier.There are a handful of contrib modules that decorate core's
html_response.attachments_processorservice. In the case of Acquia's Site Studio module, the module decorates the core service but then breaks the decorator pattern in its implementation. As a result, no decorators with a higher weight are called. (While the currentdecoration_priorityis100, it was defaulted to0in versions earlier than 8.x-8.0.2 of the Site Studio module.)Comment #48
almunningsModules that also implement changes to the AttachmentsResponse can result in the error:
Steps to reproduce
Install the webprofiler module.
Proposed resolution
/src/Render/HtmlResponseAttachmentsProcessorDecorator.php
Change:
private readonly HtmlResponseAttachmentsProcessor $decorated,To:
private readonly AttachmentsResponseProcessorInterface $decorated,Comment #49
chris burge commented@almunnings - That's a good catch. I updated the MR.
Comment #50
niral098Thanks for the work so far!
I'm testing the MR as a patch on Drupal version 10.4.6, but it's not working as expected. The script isn't being applied to the footer at all, although it does appear in the header on a few pages. Additionally, I'm seeing the following JavaScript error in the browser console:
"New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warnin..."
Still investigating what's going on.
Comment #51
programeta commentedThanks for the work so far!
I'm testing the MR as patch on Drupal 11.1.6, but it's not working as expected.
UPDATED: Sorry, I had a bad configuration API Key. It's working properly
Comment #52
chris burge commented@niral098 - The header JS is inserted with
hook_page_attachments(), so I'd recommend looking for other implementations of this hook that may be overwriting the value ofhtml_head. Re the footer JS, that's inserted by decorating thehtml_response.attachments_processorservice, and this has been problematic in some instances. I'd recommend looking for other services decorating thehtml_response.attachments_processorservice.@programeta - "it's not working as expected" - What's not working? Is the header JS not being inserted consistently? Is the footer JS not being inserted consistently? Are you seeing errors in the console? We need to know what's not working in order to consider your feedback.
Comment #53
almunningsHowdy!
This MR is no longer applicable to 2.x-dev, and might need a tweak for Drupal 11 support.
Should we branch for 2.2.x support, or just continue in the same MR?
I'm happy to help out, just don't want to step on toes! :D
Comment #54
joelsteidl commentedIt seems like this functionality was included as part of the 2.2 release?
Comment #55
chris burge commentedThe 2.2.0 release only included two commits, one for GitLab CI/CD and one for D11 support: https://git.drupalcode.org/project/new_relic_rpm/-/commits/2.2.0?ref_typ....
Comment #56
joelsteidl commentedGot it. So this issue is still applicable and a re-roll would be needed based on the commits that made it into 2.2?
Comment #57
chris burge commented@joelsteidl - I'm seeing the MR is mergeable, so a rebase shouldn't be required.
Comment #58
joelsteidl commented@chris burge
Hopefully I don't get in trouble, but I rebased the MR branch with 2.x and that made it so the patch can be applied to 2.2.
I think that's probably want we want since a couple specific commits were made to 2.x.
Comment #61
hfernandes commentedHello,
I identified an issue in the code when using the "Ignore roles" option in the New Relic configuration.
Steps to reproduce
Expected result
Anonymous users should be tracked correctly.
Actual result
Anonymous users are not tracked correctly.
The function
getBrowserTimingFooter()caches the response ofnewrelic_get_browser_timing_footer().When accessed with an Administrator user, this function returns an empty string. That empty string is then cached and reused for anonymous users, causing the tracking issue.
Implemented fix
Update the code to include a method to verify if the transaction is ignored and return NULL on
getBrowserTimingFooter()in that case.Comment #62
edsoncarlos commentedHi @hfernandes , I found this issue related to the cache name ID:
src/ExtensionAdapter/ExtensionAdapter.php
It seems that we need to concatenate the transaction name to the $key. Currently, with the way the $key is formed, all the transaction names on my New Relic dashboard appear the same.
Comment #63
hfernandes commentedHi @edsoncarlos, when you say "transaction name", do you mean that in the Page views section of the Browser dashboard you only see a single page being reported? If so, that's not happening on my end. I think the script returned by
newrelic_get_browser_timing_footeris always the same and that's why it's being cached - but I might be wrong.I'm using the latest version of the patch I created.
Comment #64
rduterteHi all,
I’m adding this comment here as others may encounter the same issue.
I tested the patch
new_relic_rpm-manual-instrumentation-3444218-15.patch
with our current setup: Drupal Core 10.4.8 and PHP 8.3.
I set up my own New Relic PHP agent in my local Drupal project using DDEV and configured Browser Real User Monitoring (RUM) to Manual Instrumentation, as this is the change introduced by the patch.
After doing this, the site breaks with the following error:
Uncaught PHP Exception TypeError: "Unsupported operand types: string * int" at /mnt/www/html/cambunipressnouwptest/docroot/core/lib/Drupal/Core/Render/Element.php line 92 request_id="v-6ca773c4-f03f-11f0-9a0c-1bbf72ef8025"This issue is caused by changes in the hook
hook_new_relic_rpm_page_attachments, specifically this line:$weight = \Drupal::config('new_relic_rpm.settings')->get('head_injection_weight');The value of this variable is a string, as shown by the following command and output:
drush php:eval "var_dump(\Drupal::config('new_relic_rpm.settings')->get('head_injection_weight'));"Output:
string(0) ""In PHP 8+, arithmetic operations such as
floor($weight * 1000)on a string throw a:TypeError: Unsupported operand types: string * intReference:
https://www.php.net/manual/en/migration80.incompatible.php
This behavior was introduced in PHP 8.0+, where arithmetic operators consistently throw a
TypeErrorfor non-numeric operands.However, the MR patch
https://git.drupalcode.org/project/new_relic_rpm/-/merge_requests/5.patch
is working as expected so far, and we have not encountered any issues with it.