@gasket/plugin-elastic-apm 6.46.8 → 7.0.0--canary-1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,21 +34,36 @@ Add a `--require` flag to a `package.json` start script:
34
34
  "scripts": {
35
35
  "build": "gasket build",
36
36
  - "start": "gasket start",
37
- + "start": "gasket start --require elastic-apm-node/start",
37
+ + "start": "gasket start --require ./setup.js",
38
38
  "local": "gasket local"
39
39
  }
40
40
  ```
41
41
 
42
+ Add a `setup.js` script to the root of your app
43
+
44
+ ```
45
+ // setup.js
46
+ require('dotenv').config();
47
+
48
+ require('elastic-apm-node').start({
49
+ serviceName: 'my-service-name',
50
+ secretToken: process.env.ELASTIC_APM_SECRET_TOKEN,
51
+ serverUrl: process.env.ELASTIC_APM_SERVER_URL
52
+ // any additional configurations options
53
+ });
54
+ ```
55
+
42
56
  ## Configuration
43
57
 
44
58
  The [start recommendations] for the APM agent are to require it as early as
45
- possible in your app. For Gasket apps, using `--require elastic-apm-node/start`
59
+ possible in your app. For Gasket apps, using `--require ./setup.js`
46
60
  will accomplish this. To configure the APM agent, set the environment variables
47
61
  described in the [configuration options documentation].
48
62
 
49
63
  In particular, the APM server URL (`ELASTIC_APM_SERVER_URL`) and secret token
50
- (`ELASTIC_APM_SECRET_TOKEN`) are both required configuration. If either
51
- of these are not present, the APM agent will be disabled.
64
+ (`ELASTIC_APM_SECRET_TOKEN`) are both required configuration. If either of these
65
+ are not present, the APM agent will be disabled.
66
+
52
67
 
53
68
  ### Plugin Configurations
54
69
 
@@ -72,26 +87,29 @@ module.exports = {
72
87
  };
73
88
  ```
74
89
 
75
- ### Custom Start Configurations
90
+ #### Custom Filtering Sensitive Fields
76
91
 
77
- For scenarios where you need to configure the start options for the APM agent,
78
- you can do so in a custom setup script and require it instead.
79
-
80
- For example, add a `setup.js` script to the root of your app:
92
+ If your application’s users send session credentials or any other sensitive
93
+ information in their cookies, you may wish to filter them out before they are
94
+ stored in Elasticsearch. Specify a list of cookie names to redact in
95
+ `setup.js` using the [sanitizeFieldNames] configuration option:
81
96
 
82
97
  ```
83
98
  // setup.js
99
+ require('dotenv').config();
100
+
84
101
  require('elastic-apm-node').start({
85
- // any configuration options
86
- })
102
+ ...,
103
+ sanitizeFieldNames: ['foo', 'bar', '*token*']
104
+ });
87
105
  ```
88
106
 
89
- Then adjust your start script to require it instead:
107
+ The `sanitizeFieldNames` config option can be used for:
108
+ - request and response HTTP headers
109
+ - HTTP request cookies
110
+ - any form field captured during an `application/x-www-form-urlencoded` data request
90
111
 
91
- ```diff
92
- - "start": "gasket start --require elastic-apm-node/start",
93
- + "start": "gasket start --require ./setup.js",
94
- ```
112
+ To filter out other data, use the [APM Add Filter API].
95
113
 
96
114
  ### Custom Filters
97
115
 
@@ -103,7 +121,9 @@ hooks of your Gasket app, such as with the [init] or [middleware] lifecycles.
103
121
 
104
122
  ### apmTransaction
105
123
 
106
- Enables customizing an APM transaction. Hooks receive the current APM [Transaction](https://www.elastic.co/guide/en/apm/agent/nodejs/current/transaction-api.html) and details about the request. Hooks may be asynchronous. The request details are as follows:
124
+ Enables customizing an APM transaction. Hooks receive the current APM
125
+ [Transaction] and details about the request. Hooks may be asynchronous. The
126
+ request details are as follows:
107
127
 
108
128
  | Property | Description |
109
129
  |----------|-------------|
@@ -120,11 +140,11 @@ module.exports = (gasket, transaction, { req, res }) => {
120
140
 
121
141
  ## How it works
122
142
 
123
- This plugin hooks the Gasket [preboot] lifecycle from [@gasket/plugin-start]
124
- and will set up additional filtering, such as for sensitive cookies. If the
143
+ This plugin hooks the Gasket [preboot] lifecycle from [@gasket/plugin-start] and
144
+ will set up additional filtering, such as for sensitive cookies. If the
125
145
  `preboot` hook finds that the APM agent has not yet been started using the
126
- recommended `--require elastic-apm-node/start`, it will start it here.
127
- However, you risk not bootstrapping necessary modules with a late start.
146
+ recommended `--require elastic-apm-node/start`, it will start it here. However,
147
+ you risk not bootstrapping necessary modules with a late start.
128
148
 
129
149
  ## License
130
150
 
@@ -138,3 +158,6 @@ However, you risk not bootstrapping necessary modules with a late start.
138
158
  [configuration options documentation]:https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html
139
159
  [start recommendations]:https://www.elastic.co/guide/en/apm/agent/nodejs/master/agent-api.html#apm-start
140
160
  [Elastic APM docs]:https://www.elastic.co/guide/en/apm/agent/nodejs/master/agent-api.html
161
+ [sanitizeFieldNames]:https://www.elastic.co/guide/en/apm/agent/nodejs/4.x/configuration.html#sanitize-field-names
162
+ [APM Add Filter API]:https://www.elastic.co/guide/en/apm/agent/nodejs/4.x/agent-api.html#apm-add-filter
163
+ [Transaction]:(https://www.elastic.co/guide/en/apm/agent/nodejs/current/transaction-api.html)
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Determines if the Elastic APM agent has sufficient config to be active
3
+ * @param {import('./index').ExtendedAgentConfigOptions} config Apm agent config
4
+ * @param {NodeJS.ProcessEnv} env Environment variables
5
+ * @returns {boolean} A combined config object
6
+ */
7
+ const isActive = (config, env) => {
8
+ const { active, serverUrl, secretToken } = config;
9
+
10
+ if (active || env.ELASTIC_APM_ACTIVE) {
11
+ return true;
12
+ }
13
+
14
+ const combined = {
15
+ serverUrl: serverUrl || env.ELASTIC_APM_SERVER_URL,
16
+ secretToken: secretToken || env.ELASTIC_APM_SECRET_TOKEN
17
+ };
18
+
19
+ if (combined.serverUrl && combined.secretToken) {
20
+ return true;
21
+ }
22
+
23
+ return false;
24
+ };
25
+
26
+ /** @type {import('@gasket/core').HookHandler<'configure'>} */
27
+ module.exports = async function configure(gasket, config) {
28
+ config.elasticAPM = config.elasticAPM || {};
29
+
30
+ // eslint-disable-next-line no-process-env
31
+ config.elasticAPM.active = isActive(config.elasticAPM, process.env);
32
+
33
+ return { ...config };
34
+ };
package/lib/cookies.js CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Returns an array of cookie names which are considered sensitive because they
3
3
  * may contain session credential or PII
4
- *
5
- * @param {*} config the Gasket config object
4
+ * @param {import('@gasket/core').GasketConfig} config the Gasket config
6
5
  * @returns {string[]} an array of cookie names
7
6
  */
8
7
  const sensitiveCookies = (config) => {
@@ -18,28 +17,42 @@ const sensitiveCookies = (config) => {
18
17
 
19
18
  /**
20
19
  * Redacts the contents of user-specified sensitive cookies
21
- *
22
- * @param {object} config The Gasket config
23
- * @param {object} payload The APM payload
24
- * @returns {object} a modified version of the incoming APM payload
20
+ * @type {import('./index').filterSensitiveCookies}
25
21
  */
26
- const filterSensitiveCookies = (config) => (payload) => {
27
- if (
28
- payload.context &&
22
+ const filterSensitiveCookies = function (config) {
23
+ return function (payload) {
24
+ const cookiesToRedact = sensitiveCookies(config);
25
+
26
+ if (
27
+ payload.context &&
29
28
  payload.context.request &&
30
29
  payload.context.request.headers &&
31
30
  payload.context.request.headers.cookie
32
- ) {
33
- let cookie = payload.context.request.headers.cookie;
31
+ ) {
32
+ let cookie = payload.context.request.headers.cookie;
33
+ cookiesToRedact.forEach((sc) => {
34
+ cookie = cookie.replace(
35
+ new RegExp(sc + '=([^;]+)'),
36
+ sc + '=[REDACTED]'
37
+ );
38
+ });
34
39
 
35
- sensitiveCookies(config).forEach((sc) => {
36
- cookie = cookie.replace(new RegExp(sc + '=([^;]+)'), sc + '=[REDACTED]');
37
- });
40
+ payload.context.request.headers.cookie = cookie;
41
+ }
38
42
 
39
- payload.context.request.headers.cookie = cookie;
40
- }
43
+ if (payload.context &&
44
+ payload.context.request &&
45
+ payload.context.request.cookies
46
+ ) {
47
+ cookiesToRedact.forEach((sc) => {
48
+ if (sc in payload.context.request.cookies) {
49
+ payload.context.request.cookies[sc] = '[REDACTED]';
50
+ }
51
+ });
52
+ }
41
53
 
42
- return payload;
54
+ return payload;
55
+ };
43
56
  };
44
57
 
45
58
  module.exports = {
package/lib/index.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'http';
2
- import type { Agent, AgentConfigOptions, Transaction } from 'elastic-apm-node';
3
- import type { MaybeAsync } from '@gasket/engine';
2
+ import type { AgentConfigOptions, Transaction, Payload } from 'elastic-apm-node';
3
+ import type { MaybeAsync, GasketConfig } from '@gasket/core';
4
+ import type { GasketData } from '@gasket/data';
4
5
 
5
- declare module '@gasket/engine' {
6
+ export function filterSensitiveCookies(config: GasketConfig): function(Payload): Payload;
7
+
8
+ export interface ExtendedAgentConfigOptions extends AgentConfigOptions {
9
+ sensitiveCookies?: Array<string>;
10
+ }
11
+
12
+ declare module '@gasket/core' {
6
13
  export interface GasketConfig {
7
- elasticAPM?: AgentConfigOptions & {
8
- sensitiveCookies?: Array<string>
9
- },
14
+ elasticAPM?: ExtendedAgentConfigOptions;
10
15
  }
11
16
 
12
17
  export interface Gasket {
@@ -17,9 +22,20 @@ declare module '@gasket/engine' {
17
22
  apmTransaction(
18
23
  transaction: Transaction,
19
24
  details: {
20
- req: IncomingMessage,
21
- res: ServerResponse
25
+ req: IncomingMessage;
26
+ res: ServerResponse & {
27
+ locals?: {
28
+ gasketData: GasketData & {
29
+ locale?: string;
30
+ };
31
+ };
32
+ };
22
33
  }
23
- ): MaybeAsync<void>
34
+ ): MaybeAsync<void>;
24
35
  }
25
36
  }
37
+
38
+ export default {
39
+ name: '@gasket/plugin-elastic-apm',
40
+ hooks: {}
41
+ };
package/lib/index.js CHANGED
@@ -1,115 +1,65 @@
1
- const { filterSensitiveCookies } = require('./cookies');
2
- const middleware = require('./middleware');
3
- const { dependencies } = require('../package.json');
4
-
5
- const isDefined = o => typeof o !== 'undefined';
1
+ /// <reference types="create-gasket-app" />
2
+ /// <reference types="@gasket/plugin-metadata" />
6
3
 
7
- /**
8
- * Determines if the Elastic APM agent has sufficient config to be active
9
- * @param {object} config gasket config
10
- * @param {object<string,any>} env environment variables
11
- * @returns {boolean} A combined config object
12
- */
13
- const isActive = (config, env) => {
14
- const { active, serverUrl, secretToken } = config;
15
-
16
- if (active || env.ELASTIC_APM_ACTIVE) {
17
- return true;
18
- }
19
-
20
- const combined = {
21
- serverUrl: serverUrl || env.ELASTIC_APM_SERVER_URL,
22
- secretToken: secretToken || env.ELASTIC_APM_SECRET_TOKEN
23
- };
24
-
25
- if (combined.serverUrl && combined.secretToken) {
26
- return true;
27
- }
28
-
29
- return false;
30
- };
4
+ const middleware = require('./middleware');
5
+ const preboot = require('./preboot');
6
+ const configure = require('./configure');
7
+ const { devDependencies, name } = require('../package.json');
31
8
 
32
- module.exports = {
9
+ /** @type {import('@gasket/core').Plugin} */
10
+ const plugin = {
11
+ name,
33
12
  hooks: {
34
- configure: {
35
- handler: async (gasket, config) => {
36
- const { logger } = gasket;
37
- config.elasticAPM = config.elasticAPM || {};
38
-
39
- const { serverUrl, secretToken } = config.elasticAPM;
40
- if (isDefined(serverUrl)) {
41
- logger.notice('DEPRECATED config `elasticAPM.serverUrl`. Use env var: ELASTIC_APM_SERVER_URL');
42
- }
43
- if (isDefined(secretToken)) {
44
- logger.notice('DEPRECATED config `elasticAPM.secretToken`. Use env var: ELASTIC_APM_SECRET_TOKEN');
45
- }
46
-
47
- // eslint-disable-next-line no-process-env
48
- config.elasticAPM.active = isActive(config.elasticAPM, process.env);
49
-
50
- return { ...config };
51
- }
52
- },
53
- preboot: {
54
- handler: async (gasket) => {
55
- const { config, logger, command } = gasket;
56
-
57
- if (command && command.id === 'local') return;
58
-
59
- // prefer app-level dependency in case of duplicates
60
- const apm = require(
61
- require.resolve('elastic-apm-node', { paths: [config.root, __dirname] })
62
- );
63
-
64
- if (!apm.isStarted()) {
65
- apm.start({
66
- ...config.elasticAPM
67
- });
68
- logger.notice('DEPRECATED started Elastic APM agent late. Use `--require elastic-apm-node/start`');
69
- }
70
-
71
- apm.addFilter(filterSensitiveCookies(config));
72
-
73
- gasket.apm = apm;
74
- }
75
- },
13
+ configure,
14
+ preboot,
76
15
  create: {
77
16
  timing: {
78
17
  after: ['@gasket/plugin-start']
79
18
  },
80
- handler(gasket, { pkg }) {
19
+ handler(gasket, { pkg, files }) {
20
+ const generatorDir = `${__dirname}/../generator`;
21
+
81
22
  pkg.add('dependencies', {
82
- 'elastic-apm-node': dependencies['elastic-apm-node']
23
+ 'elastic-apm-node': devDependencies['elastic-apm-node']
83
24
  });
84
25
  pkg.add('scripts', {
85
- start: 'gasket start --require elastic-apm-node/start'
26
+ start: 'gasket start --require ./setup.js'
86
27
  });
28
+
29
+ files.add(`${generatorDir}/*`);
87
30
  }
88
31
  },
89
32
  middleware,
90
33
  metadata(gasket, meta) {
91
34
  return {
92
35
  ...meta,
93
- configurations: [{
94
- name: 'elasticAPM',
95
- link: 'README.md#configuration',
96
- description: 'Configuration to provide additional setup helpers',
97
- type: 'object'
98
- }, {
99
- name: 'elasticAPM.sensitiveCookies',
100
- link: 'README.md#configuration',
101
- description: 'List of sensitive cookies to filter',
102
- type: 'string[]',
103
- default: '[]'
104
- }],
105
- lifecycles: [{
106
- name: 'apmTransaction',
107
- method: 'exec',
108
- description: 'Modify the APM transaction',
109
- link: 'README.md#apmtransaction',
110
- parent: 'middleware'
111
- }]
36
+ configurations: [
37
+ {
38
+ name: 'elasticAPM',
39
+ link: 'README.md#configuration',
40
+ description: 'Configuration to provide additional setup helpers',
41
+ type: 'object'
42
+ },
43
+ {
44
+ name: 'elasticAPM.sensitiveCookies',
45
+ link: 'README.md#configuration',
46
+ description: 'List of sensitive cookies to filter',
47
+ type: 'string[]',
48
+ default: '[]'
49
+ }
50
+ ],
51
+ lifecycles: [
52
+ {
53
+ name: 'apmTransaction',
54
+ method: 'exec',
55
+ description: 'Modify the APM transaction',
56
+ link: 'README.md#apmtransaction',
57
+ parent: 'middleware'
58
+ }
59
+ ]
112
60
  };
113
61
  }
114
62
  }
115
63
  };
64
+
65
+ module.exports = plugin;
package/lib/middleware.js CHANGED
@@ -1,22 +1,10 @@
1
- /* eslint-disable spaced-comment */
2
- // @ts-check
3
- /// <reference types="./index" />
4
- /// <reference types="../../gasket-plugin-nextjs" />
5
-
6
- /**
7
- * @typedef {import('@gasket/engine').Gasket} Gasket
8
- * @typedef {import('http').IncomingMessage} Request
9
- * @typedef {import('http').ServerResponse} Response
10
- */
11
-
12
- const { callbackify } = require('util');
1
+ /// <reference types="@gasket/plugin-express" />
13
2
 
14
3
  /**
15
4
  * Middleware for customizing transactions
16
- *
17
- * @param {Gasket} gasket The Gasket engine
18
- * @param {Request} req The HTTP request being handled
19
- * @param {Response} res The server response
5
+ * @param {import('@gasket/core').Gasket} gasket - The Gasket engine
6
+ * @param {import('http').IncomingMessage} req - The HTTP request being handled
7
+ * @param {import('http').ServerResponse} res - The server response
20
8
  */
21
9
  async function customizeTransaction(gasket, req, res) {
22
10
  const apm = gasket.apm;
@@ -33,9 +21,20 @@ async function customizeTransaction(gasket, req, res) {
33
21
  await gasket.exec('apmTransaction', transaction, { req, res });
34
22
  }
35
23
 
36
- module.exports = (gasket) => {
24
+ /**
25
+ * Add middleware to gather config details
26
+ * @type {import('@gasket/core').HookHandler<'middleware'>}
27
+ */
28
+ module.exports = function middleware(gasket) {
37
29
  return (
38
- gasket.apm
39
- && [callbackify(async (req, res) => customizeTransaction(gasket, req, res))]
30
+ gasket.apm &&
31
+ async function apmTransactionMiddleware(req, res, next) {
32
+ try {
33
+ customizeTransaction(gasket, req, res);
34
+ } catch (error) {
35
+ return next(error);
36
+ }
37
+ next();
38
+ }
40
39
  );
41
40
  };
package/lib/preboot.js ADDED
@@ -0,0 +1,29 @@
1
+ /// <reference types="@gasket/plugin-start" />
2
+ /// <reference types="@gasket/plugin-logger" />
3
+
4
+ const { filterSensitiveCookies } = require('./cookies');
5
+
6
+ /** @type {import('@gasket/core').HookHandler<'preboot'>} */
7
+ module.exports = async function preboot(gasket) {
8
+ const { config, logger, command } = gasket;
9
+
10
+ if (command && command.id === 'local') return;
11
+
12
+ /**
13
+ * @type {import('elastic-apm-node')}
14
+ * Note: Prefer app-level dependency in case of duplicates
15
+ */
16
+ const apm = require(require.resolve('elastic-apm-node', {
17
+ paths: [config.root, __dirname]
18
+ }));
19
+
20
+ if (!apm.isStarted()) {
21
+ logger.warn(
22
+ 'Elastic APM agent is not started. Use `--require ./setup.js`'
23
+ );
24
+ }
25
+
26
+ apm.addFilter(filterSensitiveCookies(config));
27
+
28
+ gasket.apm = apm;
29
+ };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@gasket/plugin-elastic-apm",
3
- "version": "6.46.8",
3
+ "version": "7.0.0--canary-1.0",
4
4
  "description": "Adds Elastic APM instrumentation to your application",
5
- "main": "lib",
5
+ "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [
8
8
  "lib"
@@ -14,9 +14,9 @@
14
14
  "test:runner": "jest test/*.test.js",
15
15
  "test:watch": "npm run test:runner -- --watch",
16
16
  "test:coverage": "npm run test:runner -- --coverage --coverageReporters text",
17
- "posttest": "npm run lint",
17
+ "posttest": "npm run lint && npm run typecheck",
18
18
  "report": "npm run test:runner -- --coverage --coverageReporters lcov",
19
- "typecheck:skip": "tsc",
19
+ "typecheck": "tsc",
20
20
  "typecheck:watch": "tsc --watch"
21
21
  },
22
22
  "repository": {
@@ -41,11 +41,9 @@
41
41
  "url": "https://github.com/godaddy/gasket/issues"
42
42
  },
43
43
  "homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-plugin-elastic-apm",
44
- "dependencies": {
45
- "elastic-apm-node": "^3.50.0"
46
- },
47
44
  "devDependencies": {
48
- "@gasket/engine": "^6.46.8",
45
+ "@gasket/core": "^7.0.0--canary-1.0",
46
+ "elastic-apm-node": "^4.4.1",
49
47
  "eslint": "^8.56.0",
50
48
  "eslint-config-godaddy": "^7.1.0",
51
49
  "eslint-plugin-jest": "^27.6.3",
@@ -72,5 +70,5 @@
72
70
  "jest": {
73
71
  "testEnvironment": "node"
74
72
  },
75
- "gitHead": "a4b0d22e57ca4c3e5b944d7153ac25c30ca094f3"
73
+ "gitHead": "58bd9162c377330143dd26c6ee72cf3d9e1775e0"
76
74
  }