@gasket/plugin-elastic-apm 7.0.0-next.6 → 7.0.0-next.60

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
@@ -4,28 +4,22 @@ Adds Elastic APM instrumentation to your application
4
4
 
5
5
  ## Installation
6
6
 
7
- #### New apps
8
-
9
7
  ```
10
- gasket create <app-name> --plugins @gasket/plugin-elastic-apm
8
+ npm i @gasket/plugin-elastic-apm
11
9
  ```
12
10
 
13
- #### Existing apps
11
+ Update your `gasket` file plugin configuration:
14
12
 
15
- ```
16
- npm install @gasket/plugin-elastic-apm elastic-apm-node
17
- ```
13
+ ```diff
14
+ // gasket.js
18
15
 
19
- Modify `plugins` section of your `gasket.config.js`:
16
+ + import pluginElasticApm from '@gasket/plugin-elastic-apm';
20
17
 
21
- ```diff
22
- module.exports = {
23
- plugins: {
24
- add: [
25
- + '@gasket/plugin-elastic-apm'
26
- ]
27
- }
28
- }
18
+ export default makeGasket({
19
+ plugins: [
20
+ + pluginElasticApm
21
+ ]
22
+ });
29
23
  ```
30
24
 
31
25
  Add a `--require` flag to a `package.json` start script:
@@ -77,14 +71,14 @@ configured under `elasticAPM` in the `gasket.config.js`.
77
71
  If your application’s users send session credentials or any other sensitive
78
72
  information in their cookies, you may wish to filter them out before they are
79
73
  stored in Elasticsearch. Specify a list of cookie names to redact in
80
- `gasket.config.js`:
74
+ `gasket.js`:
81
75
 
82
76
  ```js
83
- module.exports = {
77
+ export default makeGasket({
84
78
  elasticAPM: {
85
79
  sensitiveCookies: ['my_jwt', 'userFullName']
86
80
  }
87
- };
81
+ });
88
82
  ```
89
83
 
90
84
  #### Custom Filtering Sensitive Fields
@@ -131,10 +125,15 @@ request details are as follows:
131
125
  | `res` | The HTTP response or framework-specific wrapper around it |
132
126
 
133
127
  ```javascript
134
- // /lifecycles/apm-transaction.js
135
-
136
- module.exports = (gasket, transaction, { req, res }) => {
137
- transaction.setLabel('language', req.headers['accept-language']);
128
+ // example-plugin.js
129
+
130
+ export default {
131
+ name: 'example-plugin',
132
+ hooks: {
133
+ apmTransaction(gasket, transaction, { req, res }) => {
134
+ transaction.setLabel('language', req.headers['accept-language']);
135
+ }
136
+ }
138
137
  }
139
138
  ```
140
139
 
package/lib/actions.js ADDED
@@ -0,0 +1,21 @@
1
+ /** @type {import('@gasket/core').ActionHandler<'getApmTransaction'>} */
2
+ async function getApmTransaction(gasket, req) {
3
+ const apm = require('elastic-apm-node');
4
+
5
+ if (!apm?.isStarted()) {
6
+ return;
7
+ }
8
+
9
+ const transaction = apm.currentTransaction;
10
+ if (!transaction) {
11
+ return;
12
+ }
13
+
14
+ await gasket.exec('apmTransaction', transaction, { req });
15
+
16
+ return transaction;
17
+ }
18
+
19
+ module.exports = {
20
+ getApmTransaction
21
+ };
@@ -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 = 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,40 +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
- const cookiesToRedact = sensitiveCookies(config);
22
+ const filterSensitiveCookies = function (config) {
23
+ return function (payload) {
24
+ const cookiesToRedact = sensitiveCookies(config);
28
25
 
29
- if (
30
- payload.context &&
26
+ if (
27
+ payload.context &&
31
28
  payload.context.request &&
32
29
  payload.context.request.headers &&
33
30
  payload.context.request.headers.cookie
34
- ) {
35
- let cookie = payload.context.request.headers.cookie;
36
- cookiesToRedact.forEach((sc) => {
37
- cookie = cookie.replace(new RegExp(sc + '=([^;]+)'), sc + '=[REDACTED]');
38
- });
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
+ });
39
39
 
40
- payload.context.request.headers.cookie = cookie;
41
- }
40
+ payload.context.request.headers.cookie = cookie;
41
+ }
42
42
 
43
- if (payload.context &&
43
+ if (payload.context &&
44
44
  payload.context.request &&
45
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
- }
46
+ ) {
47
+ cookiesToRedact.forEach((sc) => {
48
+ if (sc in payload.context.request.cookies) {
49
+ payload.context.request.cookies[sc] = '[REDACTED]';
50
+ }
51
+ });
52
+ }
53
53
 
54
- return payload;
54
+ return payload;
55
+ };
55
56
  };
56
57
 
57
58
  module.exports = {
package/lib/create.js ADDED
@@ -0,0 +1,24 @@
1
+ const { name, version, devDependencies } = require('../package.json');
2
+
3
+ /** @type {import('@gasket/core').HookHandler<'create'>} */
4
+ module.exports = function create(gasket, { pkg, files, gasketConfig }) {
5
+ const generatorDir = `${__dirname}/../generator`;
6
+
7
+ gasketConfig.addPlugin('pluginElasticApm', name);
8
+
9
+ pkg.add('dependencies', {
10
+ [name]: `^${version}`,
11
+ 'dotenv': devDependencies.dotenv,
12
+ 'elastic-apm-node': devDependencies['elastic-apm-node']
13
+ });
14
+
15
+ pkg.extend((current) => {
16
+ return {
17
+ scripts: {
18
+ start: `NODE_OPTIONS=--import=./setup.js ${current.scripts.start}`
19
+ }
20
+ };
21
+ });
22
+
23
+ files.add(`${generatorDir}/*`);
24
+ };
package/lib/index.d.ts CHANGED
@@ -1,25 +1,42 @@
1
- import type { IncomingMessage, ServerResponse } from 'http';
2
- import type { AgentConfigOptions, Transaction } from 'elastic-apm-node';
3
- import type { MaybeAsync } from '@gasket/engine';
1
+ import type { IncomingMessage } from 'http';
2
+ import type { AgentConfigOptions, Transaction, Payload, Agent } from 'elastic-apm-node';
3
+ import type { MaybeAsync, GasketConfig, Plugin } from '@gasket/core';
4
+ import type { Request } from 'express';
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 {
13
18
  apm?: Agent;
14
19
  }
15
20
 
21
+ export interface GasketActions {
22
+ async getApmTransaction(
23
+ req: IncomingMessage | Request
24
+ ): Promise<Transaction | void>
25
+ }
26
+
16
27
  export interface HookExecTypes {
17
28
  apmTransaction(
18
29
  transaction: Transaction,
19
- details: {
20
- req: IncomingMessage,
21
- res: ServerResponse
30
+ context: {
31
+ req: IncomingMessage | Request;
22
32
  }
23
- ): MaybeAsync<void>
33
+ ): MaybeAsync<void>;
24
34
  }
25
35
  }
36
+
37
+ const plugin: Plugin = {
38
+ name: '@gasket/plugin-elastic-apm',
39
+ hooks: {}
40
+ };
41
+
42
+ export = plugin;
package/lib/index.js CHANGED
@@ -1,77 +1,25 @@
1
- const { filterSensitiveCookies } = require('./cookies');
2
- const middleware = require('./middleware');
3
- const { devDependencies } = require('../package.json');
4
-
5
- /**
6
- * Determines if the Elastic APM agent has sufficient config to be active
7
- * @param {object} config gasket config
8
- * @param {object<string,any>} env environment variables
9
- * @returns {boolean} A combined config object
10
- */
11
- const isActive = (config, env) => {
12
- const { active } = config;
1
+ /// <reference types="create-gasket-app" />
2
+ /// <reference types="@gasket/plugin-metadata" />
13
3
 
14
- if (active || env.ELASTIC_APM_ACTIVE) {
15
- return true;
16
- }
17
-
18
- if (env.ELASTIC_APM_SERVER_URL && env.ELASTIC_APM_SECRET_TOKEN) {
19
- return true;
20
- }
21
-
22
- return false;
23
- };
24
-
25
- module.exports = {
4
+ const actions = require('./actions');
5
+ const create = require('./create');
6
+ const middleware = require('./middleware');
7
+ const configure = require('./configure');
8
+ const {
9
+ name,
10
+ version,
11
+ description
12
+ } = require('../package.json');
13
+
14
+ /** @type {import('@gasket/core').Plugin} */
15
+ const plugin = {
16
+ name,
17
+ version,
18
+ description,
19
+ actions,
26
20
  hooks: {
27
- configure: {
28
- handler: (gasket, config) => {
29
- config.elasticAPM = config.elasticAPM || {};
30
-
31
- // eslint-disable-next-line no-process-env
32
- config.elasticAPM.active = isActive(config.elasticAPM, process.env);
33
-
34
- return { ...config };
35
- }
36
- },
37
- preboot: {
38
- handler: async (gasket) => {
39
- const { config, logger, command } = gasket;
40
-
41
- if (command && command.id === 'local') return;
42
-
43
- // prefer app-level dependency in case of duplicates
44
- const apm = require(require.resolve('elastic-apm-node', {
45
- paths: [config.root, __dirname]
46
- }));
47
-
48
- if (!apm.isStarted()) {
49
- logger.warn(
50
- 'Elastic APM agent is not started. Use `--require ./setup.js`'
51
- );
52
- }
53
-
54
- apm.addFilter(filterSensitiveCookies(config));
55
- }
56
- },
57
- create: {
58
- timing: {
59
- after: ['@gasket/plugin-start']
60
- },
61
- handler(gasket, { pkg, files }) {
62
- const generatorDir = `${__dirname}/../generator`;
63
-
64
- pkg.add('dependencies', {
65
- 'elastic-apm-node': devDependencies['elastic-apm-node'],
66
- 'dotenv': devDependencies.dotenv
67
- });
68
- pkg.add('scripts', {
69
- start: 'gasket start --require ./setup.js'
70
- });
71
-
72
- files.add(`${generatorDir}/*`);
73
- }
74
- },
21
+ configure,
22
+ create,
75
23
  middleware,
76
24
  metadata(gasket, meta) {
77
25
  return {
@@ -104,3 +52,5 @@ module.exports = {
104
52
  }
105
53
  }
106
54
  };
55
+
56
+ module.exports = plugin;
package/lib/middleware.js CHANGED
@@ -1,26 +1,12 @@
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
20
7
  */
21
- async function customizeTransaction(gasket, req, res) {
22
- const apm = gasket.apm;
23
-
8
+ async function customizeTransaction(gasket, req) {
9
+ const apm = require('elastic-apm-node');
24
10
  if (!apm?.isStarted()) {
25
11
  return;
26
12
  }
@@ -30,12 +16,26 @@ async function customizeTransaction(gasket, req, res) {
30
16
  return;
31
17
  }
32
18
 
33
- await gasket.exec('apmTransaction', transaction, { req, res });
19
+ await gasket.exec('apmTransaction', transaction, { req });
34
20
  }
35
21
 
36
- module.exports = (gasket) => {
22
+ /**
23
+ * Add middleware to gather config details
24
+ * @type {import('@gasket/core').HookHandler<'middleware'>}
25
+ */
26
+ module.exports = function middleware(gasket) {
27
+ const apm = require('elastic-apm-node');
28
+ // eslint-disable-next-line no-undefined
29
+ const isStarted = apm.isStarted() || undefined;
37
30
  return (
38
- gasket.apm
39
- && [callbackify(async (req, res) => customizeTransaction(gasket, req, res))]
31
+ isStarted &&
32
+ async function apmTransactionMiddleware(req, res, next) {
33
+ try {
34
+ customizeTransaction(gasket, req);
35
+ } catch (error) {
36
+ return next(error);
37
+ }
38
+ next();
39
+ }
40
40
  );
41
41
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@gasket/plugin-elastic-apm",
3
- "version": "7.0.0-next.6",
3
+ "version": "7.0.0-next.60",
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"
@@ -10,12 +10,12 @@
10
10
  "scripts": {
11
11
  "lint": "eslint .",
12
12
  "lint:fix": "npm run lint -- --fix",
13
- "test": "npm run test:runner",
14
- "test:runner": "jest test/*.test.js",
15
- "test:watch": "npm run test:runner -- --watch",
16
- "test:coverage": "npm run test:runner -- --coverage --coverageReporters text",
17
- "posttest": "npm run lint",
18
- "report": "npm run test:runner -- --coverage --coverageReporters lcov"
13
+ "test": "cross-env NODE_OPTIONS='--unhandled-rejections=strict' jest",
14
+ "test:watch": "npm run test -- --watch",
15
+ "test:coverage": "npm run test -- --coverage",
16
+ "posttest": "npm run lint && npm run typecheck",
17
+ "typecheck": "tsc",
18
+ "typecheck:watch": "tsc --watch"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -40,24 +40,27 @@
40
40
  },
41
41
  "homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-plugin-elastic-apm",
42
42
  "devDependencies": {
43
- "@gasket/engine": "^7.0.0-next.6",
44
- "dot-env": "^0.0.1",
43
+ "@gasket/core": "7.0.0-next.60",
44
+ "dotenv": "^16.4.5",
45
45
  "elastic-apm-node": "^4.4.1",
46
46
  "eslint": "^8.56.0",
47
- "eslint-config-godaddy": "^7.0.2",
48
- "eslint-plugin-jest": "^27.6.3",
47
+ "eslint-config-godaddy": "^7.1.1",
48
+ "eslint-plugin-jest": "^28.6.0",
49
49
  "eslint-plugin-json": "^3.1.0",
50
- "eslint-plugin-unicorn": "^44.0.0",
50
+ "eslint-plugin-unicorn": "^55.0.0",
51
51
  "jest": "^29.7.0",
52
- "nyc": "^15.1.0"
52
+ "nyc": "^15.1.0",
53
+ "typescript": "^5.4.5"
53
54
  },
54
55
  "eslintConfig": {
55
56
  "extends": [
56
57
  "godaddy",
57
- "plugin:jest/recommended"
58
+ "plugin:jest/recommended",
59
+ "plugin:jsdoc/recommended-typescript-flavor"
58
60
  ],
59
61
  "plugins": [
60
- "unicorn"
62
+ "unicorn",
63
+ "jsdoc"
61
64
  ],
62
65
  "rules": {
63
66
  "unicorn/filename-case": "error"
@@ -66,5 +69,5 @@
66
69
  "jest": {
67
70
  "testEnvironment": "node"
68
71
  },
69
- "gitHead": "805a1026959d8aa783ee32be8a8ff76c06badd2f"
72
+ "gitHead": "ea9feae6cd291d4361c06e09659e50dbec19bcea"
70
73
  }