Integrating newrelic on angular universal application (nodejs)
Asked Answered
I

2

6

I'm integrating newrelic on nodejs app, made with angular universal. I am using webpack for bundling

first line in main.server.aot.ts

const newrelic = require('newrelic');

and have added newrelic.js in root directory as well.

running build ejects following error :-

ERROR in ./node_modules/@newrelic/native-metrics/lib/pre-build.js
Module not found: Error: Can't resolve '../package' in 'D:\repos\ib-mobile\node_modules\@newrelic\native-metrics\lib'
 @ ./node_modules/@newrelic/native-metrics/lib/pre-build.js 40:12-33
 @ ./node_modules/@newrelic/native-metrics/index.js
 @ ./node_modules/newrelic/lib/sampler.js
 @ ./node_modules/newrelic/lib/agent.js
 @ ./node_modules/newrelic/index.js
 @ ./src/main.server.aot.ts

ERROR in ./node_modules/node-gyp/lib/node-gyp.js
Module not found: Error: Can't resolve '../package' in 'D:\repos\ib-mobile\node_modules\node-gyp\lib'
 @ ./node_modules/node-gyp/lib/node-gyp.js 67:16-37
 @ ./node_modules/@newrelic/native-metrics/lib/pre-build.js
 @ ./node_modules/@newrelic/native-metrics/index.js
 @ ./node_modules/newrelic/lib/sampler.js
 @ ./node_modules/newrelic/lib/agent.js
 @ ./node_modules/newrelic/index.js
 @ ./src/main.server.aot.ts

ERROR in ./node_modules/newrelic/index.js
Module not found: Error: Can't resolve './package' in 'D:\repos\ib-mobile\node_modules\newrelic'
 @ ./node_modules/newrelic/index.js 13:19-39
 @ ./src/main.server.aot.ts

and if I add newrelic as externals in webpack config

module.exports = {
  entry: root('./src/main.server.aot.ts'),
  output: {
    path: root('dist_server'),
    filename: 'server.js'
  },
  target: 'node',
  externals: {
    newrelic: true
  }
};

then I get another error as

/home/ubuntu/ib-mobile/dist_server/server.js:79752

module.exports = newrelic;
                 ^

ReferenceError: newrelic is not defined
    at Object.<anonymous> (/home/ubuntu/ib-mobile/dist_server/server.js:79752:18)
    at __webpack_require__ (/home/ubuntu/ib-mobile/dist_server/server.js:26:30)
    at Object.module.exports (/home/ubuntu/ib-mobile/dist_server/server.js:79667:16)
    at __webpack_require__ (/home/ubuntu/ib-mobile/dist_server/server.js:26:30)
    at /home/ubuntu/ib-mobile/dist_server/server.js:91:18
    at Object.<anonymous> (/home/ubuntu/ib-mobile/dist_server/server.js:94:10)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)

Angular Version (ng -v)

Angular CLI: 1.6.3
Node: 6.11.0
OS: win32 x64
Angular: 4.4.6
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, platform-server, router
... tsc-wrapped

@angular/cli: 1.6.3
@angular-devkit/build-optimizer: 0.0.36
@angular-devkit/core: 0.0.22
@angular-devkit/schematics: 0.0.42
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.9.3
@schematics/angular: 0.1.11
@schematics/schematics: 0.0.11
typescript: 2.3.4
webpack: 3.10.0
Identity answered 28/12, 2017 at 8:23 Comment(4)
Hello I'm facing the same problems. Had you any luck and resolved this by yourself? Thx.Stearn
@Stearn you have to inject yourself after server bundle completes use WebpackBannerPlugin to do that, const newrelic = require('newrelic');Identity
@UbaidAzad can you post whole config. as answer? I am not getting what are you trying to sayLyonnaise
the suggestion you recommend add line in the commented format which doesn't make any senseLyonnaise
S
1

Found a hacky way to get around this issuse!

TLDR

Essentially all this does is configure webpack to add the import at the top of the server.js file, and BAM it works.

Problem

So everything that you have done is correct, however by telling webpack that you want newrelic module to be external, you are telling it that the newrelic instance will be available in memory when running the application. We obviously have not done this and that is why we are getting the error you mentioned above. I think the use case for this functionality is for the browser, where you are including scripts with the <script src="{http://example.com/script.js}" /> and so you dont want webpack to concern itself with trying to resolve the dependency from node_modules.

Solution

Instead of using the "externals" property, we are going to simply add our standard nodejs import var newrelic = require('newrelic'); into the file that runs the expressjs node application (exactly like the new relic people are expecting).

In order to do this, we need the following:

  • BannerWebpackPlugin
  • Knowledge of which output chunk/file our nodejs express application is running.

In order to install BannerWebpackPlugin simply do the following: npm install --save-dev banner-webpack-plugin

As for the chunk/file you can either have a look at the error log (you can see from this error snippet that its the server.js chunk/file):

ReferenceError: newrelic is not defined at Object.<anonymous> (/home/ubuntu/ib-mobile/dist_server/server.js:79752:18)

Or you could look the webpack files "entry" property and find the chunk that is being generated by webpack (In my case this is also a "sever" which outputs to server.js.)

Now that we have this information we simply need to add a banner configuration to our chunk through the webpack config plugins section.

This is what my one looked like:

plugins: [
...,
    new BannerPlugin({
        chunks: {
            server:{
                beforeContent: 'var newrelic = require("newrelic");'
            }
       }
    }),
...]

At this point you should be able to run your webpack build and have your new relic stuff working!

If you had previously been including a require in your application code you can now remove it and the webpack import should be enough to get the newrelic agent running.

Shouldst answered 19/12, 2018 at 2:24 Comment(3)
Is there anyway to use newrelic in the code base itself with this solution? E.g. -newrelic.noticeError('things went wrong'); I've been getting errors of "Cannot fine name 'newrelic'" even after declaring it as global var in a typings.d.ts file.Herbertherbicide
Update - fixed some path issues, realized this solution probably won't work for a webpack project targeted for web, since "require" isn't available.Herbertherbicide
I had to modify the above a bit (seems like the BannerPlugin api may have changed)? For my Angular Universal app, I used new webpack.BannerPlugin({ banner: 'const nr = process.env.NR_LICENSE ? require(\'newrelic\') : false;', raw: true }),Yusem
I
2

There is more elegant solution to described problem. You can set webpack externals field like function:

(context, request, callback) => {
    var regex = new RegExp(`^newrelic$`);

    if (regex.test(request)) {
        return callback(null, `commonjs ${request}`);
    }
    callback();
 }

This will tell webpack to leave const newrelic = require('newrelic'); as is in bundled file. All you need is to place node_modules with newrelic near your running server. The second solution is to set libraryType property of outputto commonjs then the following externals syntax will work:

externals : {
    newrelic : {
        commonjs: 'newrelic'
    }
}
Inefficacious answered 30/1, 2019 at 11:30 Comment(0)
S
1

Found a hacky way to get around this issuse!

TLDR

Essentially all this does is configure webpack to add the import at the top of the server.js file, and BAM it works.

Problem

So everything that you have done is correct, however by telling webpack that you want newrelic module to be external, you are telling it that the newrelic instance will be available in memory when running the application. We obviously have not done this and that is why we are getting the error you mentioned above. I think the use case for this functionality is for the browser, where you are including scripts with the <script src="{http://example.com/script.js}" /> and so you dont want webpack to concern itself with trying to resolve the dependency from node_modules.

Solution

Instead of using the "externals" property, we are going to simply add our standard nodejs import var newrelic = require('newrelic'); into the file that runs the expressjs node application (exactly like the new relic people are expecting).

In order to do this, we need the following:

  • BannerWebpackPlugin
  • Knowledge of which output chunk/file our nodejs express application is running.

In order to install BannerWebpackPlugin simply do the following: npm install --save-dev banner-webpack-plugin

As for the chunk/file you can either have a look at the error log (you can see from this error snippet that its the server.js chunk/file):

ReferenceError: newrelic is not defined at Object.<anonymous> (/home/ubuntu/ib-mobile/dist_server/server.js:79752:18)

Or you could look the webpack files "entry" property and find the chunk that is being generated by webpack (In my case this is also a "sever" which outputs to server.js.)

Now that we have this information we simply need to add a banner configuration to our chunk through the webpack config plugins section.

This is what my one looked like:

plugins: [
...,
    new BannerPlugin({
        chunks: {
            server:{
                beforeContent: 'var newrelic = require("newrelic");'
            }
       }
    }),
...]

At this point you should be able to run your webpack build and have your new relic stuff working!

If you had previously been including a require in your application code you can now remove it and the webpack import should be enough to get the newrelic agent running.

Shouldst answered 19/12, 2018 at 2:24 Comment(3)
Is there anyway to use newrelic in the code base itself with this solution? E.g. -newrelic.noticeError('things went wrong'); I've been getting errors of "Cannot fine name 'newrelic'" even after declaring it as global var in a typings.d.ts file.Herbertherbicide
Update - fixed some path issues, realized this solution probably won't work for a webpack project targeted for web, since "require" isn't available.Herbertherbicide
I had to modify the above a bit (seems like the BannerPlugin api may have changed)? For my Angular Universal app, I used new webpack.BannerPlugin({ banner: 'const nr = process.env.NR_LICENSE ? require(\'newrelic\') : false;', raw: true }),Yusem

© 2022 - 2024 — McMap. All rights reserved.