Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring-your-own logger #640

Merged
merged 32 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3bff1c2
Bring-your-own logger
jpage-godaddy Dec 14, 2023
bd8bb0f
Fix linting
jpage-godaddy Dec 14, 2023
795f143
Try a clearer name
jpage-godaddy Dec 14, 2023
a379502
refactor: Updates to refactor ts code to js.
kawikabader Feb 8, 2024
a05cfb2
Merge branch 'v7' into byo-logger
kawikabader Feb 9, 2024
49c355d
chore: Update lock file
kawikabader Feb 9, 2024
fc2d1a8
fix: Fix express tests
kawikabader Feb 9, 2024
d5d9ab3
chore: Audit fix
kawikabader Feb 12, 2024
7cadd5c
chore: Align packages
kawikabader Feb 12, 2024
0900d51
chore: Update lock file from align packages
kawikabader Feb 12, 2024
338054b
fix: transport stubs
agerard-godaddy Feb 12, 2024
375530e
Update packages/gasket-plugin-winston/README.md
kbader-godaddy Feb 12, 2024
537eee6
feat: Replace logger "log" instances with "info".
kawikabader Feb 13, 2024
aa33955
refactor: Converted logger "logs" to "info"
kawikabader Feb 13, 2024
edc6154
chore: Lint fix.
kawikabader Feb 13, 2024
c76fdd3
refactor: Updated to use logger "warn" instead of "warning".
kawikabader Feb 13, 2024
e4f60e5
feat: Set required log levels
kawikabader Feb 20, 2024
204c91b
Update packages/gasket-plugin-winston/README.md
kbader-godaddy Feb 23, 2024
c282cea
remove: Remove gasket/log
kawikabader Feb 23, 2024
da1fa97
Update packages/gasket-plugin-winston/README.md
kbader-godaddy Feb 23, 2024
6b2797d
fix: PR feedback
kawikabader Feb 23, 2024
174a8f0
chore: Update minor deps
kawikabader Feb 26, 2024
a2f9eac
Merge branch 'v7' into byo-logger
kawikabader Feb 26, 2024
dcf4b81
fix: broken tests.
kawikabader Feb 26, 2024
34e886c
fix: Remove config log
kawikabader Feb 26, 2024
083b7e2
test: Update winston and logger pluggin types and tests.
kawikabader Feb 26, 2024
40ad9ea
chore: Run align packages and npm dedupe
kawikabader Feb 26, 2024
113175d
fix: Update lock file
kawikabader Feb 26, 2024
1b31488
fix: Regen package-lock
kawikabader Feb 26, 2024
08f2fe8
Regen package-lock
kawikabader Feb 26, 2024
0eb038d
Regen package-lock
kawikabader Feb 26, 2024
568626e
fix: attempt to fix lock file
agerard-godaddy Feb 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,939 changes: 1,820 additions & 1,119 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/gasket-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@gasket/plugin-command": "^6.43.0",
"@gasket/plugin-git": "^6.43.0",
"@gasket/plugin-lifecycle": "^6.43.0",
"@gasket/plugin-logger": "^6.43.0",
"@gasket/plugin-metadata": "^6.43.1",
"@gasket/plugin-start": "^6.43.0",
"@gasket/resolve": "^6.43.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/gasket-cli/src/config/default-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module.exports = [
require('@gasket/plugin-lifecycle'),
require('@gasket/plugin-metadata'),
require('@gasket/plugin-start'),
require('@gasket/plugin-git')
require('@gasket/plugin-git'),
require('@gasket/plugin-logger')
];
2 changes: 2 additions & 0 deletions packages/gasket-plugin-express/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# `@gasket/plugin-express`

- Add a logger to the request object

### 6.41.2

- Fix ordering of error middlewares so they come after API routes
Expand Down
16 changes: 16 additions & 0 deletions packages/gasket-plugin-express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ middleware are triggered for which requests.
]
```

## Logging

This plugin attaches a `logger` object to the request object. This object has a `metadata` method that allows you to attach details to any log entry related to the request. For example, you can add the user ID to each log entry. When logging within the context of a request, use the `req.logger` object instead of the global `gasket.logger` so that contextual information is included in the log entry. Here is an example of how to attach metadata to `req.logger` object and how to use it:

```js
function someMiddleware(req, res, next) {
req.logger.metadata({ userId: req.user.id });
next();
}

function someOtherMiddleware(req, res, next) {
req.logger.info('Some log message');
next();
}
```

## Lifecycles

### middleware
Expand Down
27 changes: 20 additions & 7 deletions packages/gasket-plugin-express/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ module.exports = {
const { root, express: { routes } = {}, http2, middleware: middlewareConfig } = config;
const excludedRoutesRegex = config.express && config.express.excludedRoutesRegex;
const app = http2 ? require('http2-express-bridge')(express) : express();
const conditionallyUse = (...middleware) => middleware.forEach(mw => {
kbader-godaddy marked this conversation as resolved.
Show resolved Hide resolved
if (excludedRoutesRegex) {
app.use(excludedRoutesRegex, mw);
} else {
app.use(mw);
}
});

if (http2) {
app.use(
Expand All @@ -66,12 +73,20 @@ module.exports = {
});
}

if (excludedRoutesRegex) {
app.use(excludedRoutesRegex, cookieParser());
} else {
app.use(cookieParser());
function attachLogEnhancer(req) {
req.logger.metadata = metadata => {
req.logger = req.logger.child(metadata);
attachLogEnhancer(req);
};
kbader-godaddy marked this conversation as resolved.
Show resolved Hide resolved
}

conditionallyUse((req, res, next) => {
req.logger = gasket.logger;
attachLogEnhancer(req);
next();
});
kbader-godaddy marked this conversation as resolved.
Show resolved Hide resolved
conditionallyUse(cookieParser());

const { compression: compressionConfig = true } = config.express || {};
if (compressionConfig) {
app.use(compression());
Expand Down Expand Up @@ -101,10 +116,8 @@ module.exports = {
const { paths } = layer;
if (paths) {
app.use(paths, layer);
} else if (excludedRoutesRegex) {
app.use(excludedRoutesRegex, layer);
} else {
app.use(layer);
conditionallyUse(layer);
}
});

Expand Down
29 changes: 24 additions & 5 deletions packages/gasket-plugin-express/test/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ describe('createServers', () => {

it('adds middleware from lifecycle (ignores falsy)', async () => {
await plugin.hooks.createServers(gasket, {});
expect(app.use).toHaveBeenCalledTimes(2);
expect(app.use).toHaveBeenCalledTimes(3);

app.use.mockClear();
mockMwPlugins = [
Expand All @@ -220,7 +220,7 @@ describe('createServers', () => {
];

await plugin.hooks.createServers(gasket, {});
expect(app.use).toHaveBeenCalledTimes(3);
expect(app.use).toHaveBeenCalledTimes(4);
});

it('supports async middleware hooks', async () => {
Expand Down Expand Up @@ -256,16 +256,35 @@ describe('createServers', () => {
];
await plugin.hooks.createServers(gasket, {});

expect(app.use.mock.calls[2]).toContain(paths);
expect(app.use.mock.calls[3]).toContain(paths);
});

it('adds errorMiddleware from lifecycle (ignores falsy)', async () => {
await plugin.hooks.createServers(gasket, {});
expect(app.use).toHaveBeenCalledTimes(2);
expect(app.use).toHaveBeenCalledTimes(3);
lifecycles.errorMiddleware.mockResolvedValue([() => {}, null]);
app.use.mockClear();
await plugin.hooks.createServers(gasket, {});
expect(app.use).toHaveBeenCalledTimes(3);
expect(app.use).toHaveBeenCalledTimes(4);
});

it('attaches a logger to the request', async () => {
const augmentedLogger = { info: jest.fn() };
gasket.logger = { child: jest.fn().mockReturnValue(augmentedLogger) };
await plugin.hooks.createServers(gasket, {});

const req = {};
const res = {};
const next = jest.fn();

app.use.mock.calls.forEach(([middleware]) => middleware(req, res, next));
expect(req).toHaveProperty('logger', gasket.logger);

req.logger.metadata({ userId: '123' });
expect(gasket.logger.child).toHaveBeenCalledWith({ userId: '123' });

req.logger.info('test');
expect(augmentedLogger.info).toHaveBeenCalledWith('test');
});

function findCall(aSpy, aPredicate) {
Expand Down
6 changes: 3 additions & 3 deletions packages/gasket-plugin-intl/lib/build-manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module.exports = async function buildManifest(gasket) {
.filter(f => f !== manifestFilename);

if (!files.length) {
logger.warning(`build:locales: No locale files found (${localesDir}).`);
logger?.warning?.(`build:locales: No locale files found (${localesDir}).`);
kbader-godaddy marked this conversation as resolved.
Show resolved Hide resolved
}

// generate a content hash for each file
Expand Down Expand Up @@ -57,9 +57,9 @@ module.exports = async function buildManifest(gasket) {

try {
await fs.writeFile(tgtFile, JSON.stringify(manifest), 'utf-8');
logger.log('build:locales: Wrote locales manifest.');
logger?.log?.('build:locales: Wrote locales manifest.');
kbader-godaddy marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
logger.error('build:locales: Unable to write locales manifest.');
logger?.error?.('build:locales: Unable to write locales manifest.');
throw err;
}
};
4 changes: 2 additions & 2 deletions packages/gasket-plugin-intl/lib/build-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class BuildModules {
const pkgName = await this.getPackageName(srcDir);
const tgtDir = path.join(this._outputDir, pkgName);

this._logger.log(`build:locales: Updating locale files for: ${pkgName}`);
this._logger?.log?.(`build:locales: Updating locale files for: ${pkgName}`);

await fs.remove(tgtDir);
await fs.mkdirp(tgtDir);
Expand All @@ -142,7 +142,7 @@ class BuildModules {
await this.processFiles(srcDir, tgtDir, fileNames);
}

this._logger.log(`build:locales: Completed locale files update.`);
this._logger?.log?.(`build:locales: Completed locale files update.`);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/gasket-plugin-intl/lib/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ function getIntlConfig(gasket) {
function deprecatedOptions(gasket, intlConfig) {
const { logger } = gasket;
const { languageMap, defaultLanguage, assetPrefix } = intlConfig;
if (isDefined(languageMap)) logger.warning('DEPRECATED intl config `languageMap` - use `localesMap`');
if (isDefined(defaultLanguage)) logger.warning('DEPRECATED intl config `defaultLanguage` - use `defaultLocale`');
if (isDefined(assetPrefix)) logger.warning('DEPRECATED intl config `assetPrefix` - use `basePath`');
if (isDefined(languageMap)) logger?.warning?.('DEPRECATED intl config `languageMap` - use `localesMap`');
if (isDefined(defaultLanguage)) logger?.warning?.('DEPRECATED intl config `defaultLanguage` - use `defaultLocale`');
if (isDefined(assetPrefix)) logger?.warning?.('DEPRECATED intl config `assetPrefix` - use `basePath`');
return { languageMap, defaultLanguage, assetPrefix };
}

Expand Down
1 change: 0 additions & 1 deletion packages/gasket-plugin-intl/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const buildModules = require('./build-modules');
const { getIntlConfig } = require('./configure');

module.exports = {
dependencies: ['@gasket/plugin-log'],
name,
hooks: {
init,
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-plugin-intl/lib/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function getPreferredLocale(gasket, req, locales, defaultLocale) {
preferredLocale = formatLocale(accept.language(acceptLanguage, locales));
debug(`Using ${preferredLocale} as starting locale`);
} catch (error) {
gasket.logger.debug(`Unable to parse accept-language header: ${error.message}`);
gasket.logger?.debug?.(`Unable to parse accept-language header: ${error.message}`);
}
} else {
debug(`No accept-language header; starting with default ${preferredLocale}`);
Expand Down
4 changes: 0 additions & 4 deletions packages/gasket-plugin-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
},
"devDependencies": {
"@gasket/engine": "^6.43.0",
"@gasket/plugin-log": "^6.43.0",
"@gasket/react-intl": "^6.42.0",
"cross-env": "^7.0.3",
"eslint": "^8.7.0",
Expand All @@ -66,9 +65,6 @@
"setup-env": "^2.0.0",
"webpack": "^5.21.2"
},
"peerDependencies": {
"@gasket/plugin-log": "^6.0.0"
},
"eslintConfig": {
"extends": [
"godaddy",
Expand Down
15 changes: 15 additions & 0 deletions packages/gasket-plugin-logger/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": [
"godaddy-typescript",
"plugin:jest/recommended"
],
"plugins": [
"unicorn",
"babel"
],
"rules": {
"unicorn/filename-case": "error",
"no-redeclare": "off"
},
"ignorePatterns": "lib/*"
}
1 change: 1 addition & 0 deletions packages/gasket-plugin-logger/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock.json binary
1 change: 1 addition & 0 deletions packages/gasket-plugin-logger/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib
3 changes: 3 additions & 0 deletions packages/gasket-plugin-logger/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `@gasket/plugin-logger`

- Initial release
21 changes: 21 additions & 0 deletions packages/gasket-plugin-logger/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2023 GoDaddy Operating Company, LLC.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
48 changes: 48 additions & 0 deletions packages/gasket-plugin-logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# @gasket/plugin-logger

Adds a logger to your Gasket application and introduces lifecycles for custom logger implementations. This plugin is included by default in all Gasket applications. At this time, there is only one plugin which implements a custom logger: `@gasket/plugin-winston`.

## Lifecycles

### createLogger

To implement a custom logger, hook the `createLogger` lifecycle. Your hook, which may be asynchronous, must return an object with this shape:

```typescript
type Logger = {
[level: string]: (...args: Array<any>) => void,
child: (metadata: Object) => Logger,
close?: () => Promise<void> // Optional
}
```

The `level` keys are the log levels that your logger supports. The values are functions that accept any number of arguments and write them to the log. Your logger must support, at minimum, the following levels:

- `error`
- `warn`
kbader-godaddy marked this conversation as resolved.
Show resolved Hide resolved
- `info`
- `verbose`
- `debug`

The `child` function is used to create a new logger with additional metadata. The `metadata` argument is set of properties that will be included in every log entry. The `child` function must return a new logger with the same shape as the parent logger.

The `close` function, if supplied, is called when the application is shutting down and should be used to close any open resources.

## Test

If you are contributing to this plugin, use the following to run the tests:

```shell
npm test
```

## License

[MIT](./LICENSE.md)

<!-- LINKS -->

[winston]: https://github.com/winstonjs/winston
[winston documentation]: https://github.com/winstonjs/winston#creating-your-own-logger
[@gasket/log]: /packages/gasket-log/README.md
[Formats]: https://github.com/winstonjs/winston#formats
5 changes: 5 additions & 0 deletions packages/gasket-plugin-logger/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node'
};
53 changes: 53 additions & 0 deletions packages/gasket-plugin-logger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@gasket/plugin-logger",
"version": "6.43.0",
"description": "Gasket plugin for logging",
"main": "lib",
"types": "lib/index.d.ts",
"files": [
"lib",
"src"
],
"scripts": {
"lint": "eslint ./src",
"lint:fix": "npm run lint -- --fix",
"test": "cross-env NODE_OPTIONS='--unhandled-rejections=strict' jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"posttest": "npm run lint",
"build": "tsc -P ./tsconfig.build.json",
"prepublishOnly": "npm test && npm run build"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/godaddy/gasket.git"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"gasket",
"plugin",
"logging"
],
"author": "GoDaddy Operating Company, LLC",
"license": "MIT",
"bugs": {
"url": "https://github.com/godaddy/gasket/issues"
},
"homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-plugin-logger",
"devDependencies": {
"@gasket/engine": "^6.43.1",
"@gasket/plugin-command": "^6.43.0",
"@gasket/plugin-https": "^6.43.0",
"@gasket/plugin-metadata": "^6.43.1",
"@tsconfig/node18": "^18.2.2",
"cross-env": "^7.0.3",
"eslint": "^8.55.0",
"eslint-config-godaddy-typescript": "^4.0.2",
"eslint-plugin-jest": "^27.6.0",
"eslint-plugin-unicorn": "^49.0.0",
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
}
}
Loading