@gasket/plugin-elastic-apm 7.3.0-canary.4 → 7.3.1

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,38 +4,32 @@ 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
- Add a `--require` flag to a `package.json` start script:
25
+ Add `NODE_OPTIONS=--import=./setup.js` to the `package.json` start script:
32
26
 
33
27
  ```diff
34
28
  "scripts": {
35
- "build": "gasket build",
36
- - "start": "gasket start",
37
- + "start": "gasket start --require ./setup.js",
38
- "local": "gasket local"
29
+ "build": "next build",
30
+ - "start": "next start",
31
+ + "start": "NODE_OPTIONS=--import=./setup.js next start",
32
+ "local": "next dev"
39
33
  }
40
34
  ```
41
35
 
@@ -43,20 +37,22 @@ Add a `setup.js` script to the root of your app
43
37
 
44
38
  ```
45
39
  // setup.js
46
- require('dotenv').config();
40
+ import apm from 'elastic-apm-node';
47
41
 
48
- require('elastic-apm-node').start({
42
+ // Elastic APM setup
43
+ apm.start({
49
44
  serviceName: 'my-service-name',
45
+ captureHeaders: false,
50
46
  secretToken: process.env.ELASTIC_APM_SECRET_TOKEN,
51
47
  serverUrl: process.env.ELASTIC_APM_SERVER_URL
52
- // any additional configurations options
48
+ // additional configuration options
53
49
  });
54
50
  ```
55
51
 
56
52
  ## Configuration
57
53
 
58
54
  The [start recommendations] for the APM agent are to require it as early as
59
- possible in your app. For Gasket apps, using `--require ./setup.js`
55
+ possible in your app. For Gasket apps, using `NODE_OPTIONS=--import=./setup.js`
60
56
  will accomplish this. To configure the APM agent, set the environment variables
61
57
  described in the [configuration options documentation].
62
58
 
@@ -64,11 +60,28 @@ In particular, the APM server URL (`ELASTIC_APM_SERVER_URL`) and secret token
64
60
  (`ELASTIC_APM_SECRET_TOKEN`) are both required configuration. If either of these
65
61
  are not present, the APM agent will be disabled.
66
62
 
63
+ ### Dotenv
64
+
65
+ If you wish to use `dotenv`, be sure it is installed and imported in `setup.js`:
66
+
67
+ ```
68
+ npm i dotenv
69
+ ```
70
+
71
+ ```diff
72
+ // setup.js
73
+ + import 'dotenv/config';
74
+ import apm from 'elastic-apm-node';
75
+
76
+ // Elastic APM setup
77
+ apm.start({
78
+ ...
79
+ ```
67
80
 
68
81
  ### Plugin Configurations
69
82
 
70
83
  The Gasket plugin provides some additional setup helpers. These can be
71
- configured under `elasticAPM` in the `gasket.config.js`.
84
+ configured under `elasticAPM` in the `gasket.js`.
72
85
 
73
86
  - **`sensitiveCookies`** - (string[]) A list of sensitive cookies to filter
74
87
 
@@ -77,14 +90,14 @@ configured under `elasticAPM` in the `gasket.config.js`.
77
90
  If your application’s users send session credentials or any other sensitive
78
91
  information in their cookies, you may wish to filter them out before they are
79
92
  stored in Elasticsearch. Specify a list of cookie names to redact in
80
- `gasket.config.js`:
93
+ `gasket.js`:
81
94
 
82
95
  ```js
83
- module.exports = {
96
+ export default makeGasket({
84
97
  elasticAPM: {
85
98
  sensitiveCookies: ['my_jwt', 'userFullName']
86
99
  }
87
- };
100
+ });
88
101
  ```
89
102
 
90
103
  #### Custom Filtering Sensitive Fields
@@ -94,17 +107,18 @@ information in their cookies, you may wish to filter them out before they are
94
107
  stored in Elasticsearch. Specify a list of cookie names to redact in
95
108
  `setup.js` using the [sanitizeFieldNames] configuration option:
96
109
 
97
- ```
110
+ ```diff
98
111
  // setup.js
99
- require('dotenv').config();
112
+ import apm from 'elastic-apm-node';
100
113
 
101
- require('elastic-apm-node').start({
102
- ...,
103
- sanitizeFieldNames: ['foo', 'bar', '*token*']
104
- });
114
+ // Elastic APM setup
115
+ apm.start({
116
+ + sanitizeFieldNames: ['foo', 'bar', '*token*']
117
+ ...
105
118
  ```
106
119
 
107
120
  The `sanitizeFieldNames` config option can be used for:
121
+
108
122
  - request and response HTTP headers
109
123
  - HTTP request cookies
110
124
  - any form field captured during an `application/x-www-form-urlencoded` data request
@@ -114,9 +128,39 @@ To filter out other data, use the [APM Add Filter API].
114
128
  ### Custom Filters
115
129
 
116
130
  According to the [Elastic APM docs], the _Elastic APM agent for Node.js is a
117
- singleton_. This means that you can require and configure singleton in various
131
+ singleton_.
132
+ This means that you can import and configure the singleton in various
118
133
  hooks of your Gasket app, such as with the [init] or [middleware] lifecycles.
119
134
 
135
+ ## Actions
136
+
137
+ ### getApmTransaction
138
+
139
+ Use the `getApmTransaction` action to access and decorate the current APM
140
+ transaction. This action is available in any lifecycle hook or server-side code.
141
+
142
+ ```js
143
+ // example-plugin.js
144
+
145
+ export default {
146
+ name: 'example-plugin',
147
+ hooks: {
148
+ express(gasket, app) {
149
+ app.use(async (req, res, next) => {
150
+ const transaction = await gasket.actions.getApmTransaction(req);
151
+ const locale = await gasket.actions.getIntlLocale(req);
152
+ transaction.setLabel('locale', locale);
153
+ });
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ In the above example, we are hooking the express lifecycle to add middleware
160
+ to decorate the transaction.
161
+ Calling `getApmTransaction` will also allow other plugins to decorate the
162
+ transaction by hooking the `apmTransaction` lifecycle discussed next.
163
+
120
164
  ## Lifecycles
121
165
 
122
166
  ### apmTransaction
@@ -131,20 +175,22 @@ request details are as follows:
131
175
  | `res` | The HTTP response or framework-specific wrapper around it |
132
176
 
133
177
  ```javascript
134
- // /lifecycles/apm-transaction.js
135
-
136
- module.exports = (gasket, transaction, { req, res }) => {
137
- transaction.setLabel('language', req.headers['accept-language']);
178
+ // example-plugin.js
179
+
180
+ export default {
181
+ name: 'example-plugin',
182
+ hooks: {
183
+ apmTransaction(gasket, transaction, { req, res }) => {
184
+ transaction.setLabel('language', req.headers['accept-language']);
185
+ }
186
+ }
138
187
  }
139
188
  ```
140
189
 
141
190
  ## How it works
142
191
 
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
145
- `preboot` hook finds that the APM agent has not yet been started using the
146
- recommended `--require elastic-apm-node/start`, it will start it here. However,
147
- you risk not bootstrapping necessary modules with a late start.
192
+ This plugin hooks the Gasket [configure] lifecycle to set additional filtering,
193
+ such as for sensitive cookies.
148
194
 
149
195
  ## License
150
196
 
@@ -152,7 +198,6 @@ you risk not bootstrapping necessary modules with a late start.
152
198
 
153
199
  <!-- LINKS -->
154
200
 
155
- [preboot]:/packages/gasket-plugin-start/README.md#preboot
156
201
  [init]:packages/gasket-plugin-command/README.md#init
157
202
  [middleware]:/packages/gasket-plugin-express/README.md#middleware
158
203
  [configuration options documentation]:https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html
package/lib/actions.js ADDED
@@ -0,0 +1,25 @@
1
+ const { withGasketRequest } = require('@gasket/request');
2
+
3
+ /** @type {import('@gasket/core').ActionHandler<'getApmTransaction'>} */
4
+ const getApmTransaction = withGasketRequest(
5
+ async function getApmTransaction(gasket, req) {
6
+ const apm = require('elastic-apm-node');
7
+
8
+ if (!apm?.isStarted()) {
9
+ return;
10
+ }
11
+
12
+ const transaction = apm.currentTransaction;
13
+ if (!transaction) {
14
+ return;
15
+ }
16
+
17
+ await gasket.exec('apmTransaction', transaction, { req });
18
+
19
+ return transaction;
20
+ }
21
+ );
22
+
23
+ module.exports = {
24
+ getApmTransaction
25
+ };
package/lib/cookies.js CHANGED
@@ -17,7 +17,7 @@ const sensitiveCookies = (config) => {
17
17
 
18
18
  /**
19
19
  * Redacts the contents of user-specified sensitive cookies
20
- * @type {import('./index').filterSensitiveCookies}
20
+ * @type {import('.').filterSensitiveCookies}
21
21
  */
22
22
  const filterSensitiveCookies = function (config) {
23
23
  return function (payload) {
@@ -25,9 +25,9 @@ const filterSensitiveCookies = function (config) {
25
25
 
26
26
  if (
27
27
  payload.context &&
28
- payload.context.request &&
29
- payload.context.request.headers &&
30
- payload.context.request.headers.cookie
28
+ payload.context.request &&
29
+ payload.context.request.headers &&
30
+ payload.context.request.headers.cookie
31
31
  ) {
32
32
  let cookie = payload.context.request.headers.cookie;
33
33
  cookiesToRedact.forEach((sc) => {
@@ -41,8 +41,8 @@ const filterSensitiveCookies = function (config) {
41
41
  }
42
42
 
43
43
  if (payload.context &&
44
- payload.context.request &&
45
- payload.context.request.cookies
44
+ payload.context.request &&
45
+ payload.context.request.cookies
46
46
  ) {
47
47
  cookiesToRedact.forEach((sc) => {
48
48
  if (sc in payload.context.request.cookies) {
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,9 +1,8 @@
1
- import type { IncomingMessage, ServerResponse } from 'http';
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';
1
+ import type { AgentConfigOptions, Transaction, Payload, Agent } from 'elastic-apm-node';
2
+ import type { GasketConfig, Plugin, MaybeAsync } from '@gasket/core';
3
+ import type { RequestLike, GasketRequest } from '@gasket/request';
5
4
 
6
- export function filterSensitiveCookies(config: GasketConfig): function(Payload): Payload;
5
+ export function filterSensitiveCookies(config: GasketConfig): (Payload) => Payload;
7
6
 
8
7
  export interface ExtendedAgentConfigOptions extends AgentConfigOptions {
9
8
  sensitiveCookies?: Array<string>;
@@ -18,26 +17,22 @@ declare module '@gasket/core' {
18
17
  apm?: Agent;
19
18
  }
20
19
 
20
+ export interface GasketActions {
21
+ getApmTransaction(
22
+ req: RequestLike
23
+ ): Promise<Transaction | void>
24
+ }
25
+
21
26
  export interface HookExecTypes {
22
27
  apmTransaction(
23
28
  transaction: Transaction,
24
- details: {
25
- req: IncomingMessage;
26
- res: ServerResponse & {
27
- locals?: {
28
- gasketData: GasketData & {
29
- locale?: string;
30
- };
31
- };
32
- };
29
+ context: {
30
+ req: GasketRequest;
33
31
  }
34
32
  ): MaybeAsync<void>;
35
33
  }
36
34
  }
37
35
 
38
- export default {
39
- name: '@gasket/plugin-elastic-apm',
40
- version: '',
41
- description: '',
42
- hooks: {}
43
- };
36
+ declare const plugin: Plugin;
37
+
38
+ export default plugin;
package/lib/index.js CHANGED
@@ -1,14 +1,13 @@
1
1
  /// <reference types="create-gasket-app" />
2
2
  /// <reference types="@gasket/plugin-metadata" />
3
3
 
4
- const middleware = require('./middleware');
5
- const preboot = require('./preboot');
4
+ const actions = require('./actions');
5
+ const create = require('./create');
6
6
  const configure = require('./configure');
7
7
  const {
8
8
  name,
9
9
  version,
10
- description,
11
- devDependencies
10
+ description
12
11
  } = require('../package.json');
13
12
 
14
13
  /** @type {import('@gasket/core').Plugin} */
@@ -16,30 +15,20 @@ const plugin = {
16
15
  name,
17
16
  version,
18
17
  description,
18
+ actions,
19
19
  hooks: {
20
20
  configure,
21
- preboot,
22
- create: {
23
- timing: {
24
- after: ['@gasket/plugin-start']
25
- },
26
- handler(gasket, { pkg, files }) {
27
- const generatorDir = `${__dirname}/../generator`;
28
-
29
- pkg.add('dependencies', {
30
- 'elastic-apm-node': devDependencies['elastic-apm-node']
31
- });
32
- pkg.add('scripts', {
33
- start: 'gasket start --require ./setup.js'
34
- });
35
-
36
- files.add(`${generatorDir}/*`);
37
- }
38
- },
39
- middleware,
21
+ create,
40
22
  metadata(gasket, meta) {
41
23
  return {
42
24
  ...meta,
25
+ actions: [
26
+ {
27
+ name: 'getApmTransaction',
28
+ description: 'Get the APM transaction data',
29
+ link: 'README.md#getApmTransaction'
30
+ }
31
+ ],
43
32
  configurations: [
44
33
  {
45
34
  name: 'elasticAPM',
package/package.json CHANGED
@@ -1,24 +1,12 @@
1
1
  {
2
2
  "name": "@gasket/plugin-elastic-apm",
3
- "version": "7.3.0-canary.4",
3
+ "version": "7.3.1",
4
4
  "description": "Adds Elastic APM instrumentation to your application",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [
8
8
  "lib"
9
9
  ],
10
- "scripts": {
11
- "lint": "eslint .",
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 && npm run typecheck",
18
- "report": "npm run test:runner -- --coverage --coverageReporters lcov",
19
- "typecheck": "tsc",
20
- "typecheck:watch": "tsc --watch"
21
- },
22
10
  "repository": {
23
11
  "type": "git",
24
12
  "url": "git+ssh://git@github.com/godaddy/gasket.git"
@@ -33,25 +21,31 @@
33
21
  "plugin"
34
22
  ],
35
23
  "author": "GoDaddy Operating Company, LLC",
36
- "maintainers": [
37
- "Keith Bartholomew <kbartholomew@godaddy.com>"
38
- ],
39
24
  "license": "MIT",
40
25
  "bugs": {
41
26
  "url": "https://github.com/godaddy/gasket/issues"
42
27
  },
43
28
  "homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-plugin-elastic-apm",
29
+ "dependencies": {
30
+ "@gasket/request": "^7.3.1"
31
+ },
44
32
  "devDependencies": {
45
- "@gasket/core": "^7.3.0-canary.4",
46
- "elastic-apm-node": "^4.4.1",
47
- "eslint": "^8.56.0",
48
- "eslint-config-godaddy": "^7.1.0",
49
- "eslint-plugin-jest": "^27.6.3",
50
- "eslint-plugin-json": "^3.1.0",
51
- "eslint-plugin-unicorn": "^44.0.0",
33
+ "@gasket/core": "^7.3.1",
34
+ "@gasket/plugin-metadata": "^7.3.2",
35
+ "@types/jest": "^29.5.14",
36
+ "@types/node": "^20.17.19",
37
+ "create-gasket-app": "^7.3.2",
38
+ "cross-env": "^7.0.3",
39
+ "dotenv": "^16.4.7",
40
+ "elastic-apm-node": "^4.11.0",
41
+ "eslint": "^8.57.1",
42
+ "eslint-config-godaddy": "^7.1.1",
43
+ "eslint-config-godaddy-typescript": "^4.0.3",
44
+ "eslint-plugin-jest": "^28.11.0",
45
+ "eslint-plugin-unicorn": "^55.0.0",
52
46
  "jest": "^29.7.0",
53
47
  "nyc": "^15.1.0",
54
- "typescript": "^5.4.5"
48
+ "typescript": "^5.7.3"
55
49
  },
56
50
  "eslintConfig": {
57
51
  "extends": [
@@ -65,10 +59,32 @@
65
59
  ],
66
60
  "rules": {
67
61
  "unicorn/filename-case": "error"
68
- }
62
+ },
63
+ "overrides": [
64
+ {
65
+ "files": [
66
+ "lib/*.ts"
67
+ ],
68
+ "extends": [
69
+ "godaddy-typescript"
70
+ ],
71
+ "rules": {
72
+ "jsdoc/*": "off"
73
+ }
74
+ }
75
+ ]
69
76
  },
70
77
  "jest": {
71
78
  "testEnvironment": "node"
72
79
  },
73
- "gitHead": "2abf9afba3b1d1f9a77efe01377a2e12cfeda02c"
74
- }
80
+ "scripts": {
81
+ "lint": "eslint .",
82
+ "lint:fix": "pnpm run lint --fix",
83
+ "test": "cross-env NODE_OPTIONS='--unhandled-rejections=strict' jest",
84
+ "test:watch": "pnpm run test --watch",
85
+ "test:coverage": "pnpm run test --coverage",
86
+ "posttest": "pnpm run lint && pnpm run typecheck",
87
+ "typecheck": "tsc",
88
+ "typecheck:watch": "tsc --watch"
89
+ }
90
+ }
package/lib/middleware.js DELETED
@@ -1,40 +0,0 @@
1
- /// <reference types="@gasket/plugin-express" />
2
-
3
- /**
4
- * Middleware for customizing transactions
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
8
- */
9
- async function customizeTransaction(gasket, req, res) {
10
- const apm = gasket.apm;
11
-
12
- if (!apm?.isStarted()) {
13
- return;
14
- }
15
-
16
- const transaction = apm.currentTransaction;
17
- if (!transaction) {
18
- return;
19
- }
20
-
21
- await gasket.exec('apmTransaction', transaction, { req, res });
22
- }
23
-
24
- /**
25
- * Add middleware to gather config details
26
- * @type {import('@gasket/core').HookHandler<'middleware'>}
27
- */
28
- module.exports = function middleware(gasket) {
29
- return (
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
- }
39
- );
40
- };
package/lib/preboot.js DELETED
@@ -1,29 +0,0 @@
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
- };