Skip to content

Commit

Permalink
Merge branch 'main' into (fastify#4439)-Access-handler-name-add-prope…
Browse files Browse the repository at this point in the history
…rties-to-req-route-options
  • Loading branch information
Fdawgs committed Mar 13, 2023
2 parents 81a87aa + b69ae0f commit acc8b31
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/links-check.yml
Expand Up @@ -19,7 +19,7 @@ jobs:
# See: https://github.com/lycheeverse/lychee-action/issues/17
- name: Link Checker
id: lychee
uses: lycheeverse/lychee-action@4dcb8bee2a0a4531cba1a1f392c54e8375d6dd81
uses: lycheeverse/lychee-action@9ace499fe66cee282a29eaa628fdac2c72fa087f
with:
fail: true
# As external links behaviour is not predictable, we check only internal links
Expand Down
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -304,6 +304,8 @@ listed in alphabetical order.
<https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
* [__Denis Fäcke__](https://github.com/SerayaEryn),
<https://twitter.com/serayaeryn>, <https://www.npmjs.com/~serayaeryn>
* [__Carlos Fuentes__](https://github.com/metcoder95),
<https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
* [__Rafael Gonzaga__](https://github.com/rafaelgss),
<https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
* [__Vincent Le Goff__](https://github.com/zekth)
Expand All @@ -327,6 +329,8 @@ listed in alphabetical order.
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
<https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
* [__Carlos Fuentes__](https://github.com/metcoder95),
<https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
* [__Vincent Le Goff__](https://github.com/zekth)
* [__Salman Mitha__](https://github.com/salmanm),
<https://www.npmjs.com/~salmanm>
Expand All @@ -338,7 +342,7 @@ listed in alphabetical order.
* [__Rafael Gonzaga__](https://github.com/rafaelgss),
<https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
* [__Simone Busoli__](https://github.com/simoneb),
<https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
<https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>

### Great Contributors
Great contributors on a specific area in the Fastify ecosystem will be invited
Expand Down
6 changes: 6 additions & 0 deletions docs/Reference/Errors.md
Expand Up @@ -281,6 +281,12 @@ A callback for a hook timed out

The logger accepts either a `'stream'` or a `'file'` as the destination.

#### FST_ERR_LOG_INVALID_LOGGER
<a id="FST_ERR_LOG_INVALID_LOGGER"></a>

The logger should have all these methods: `'info'`, `'error'`,
`'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`.

#### FST_ERR_REP_INVALID_PAYLOAD_TYPE
<a id="FST_ERR_REP_INVALID_PAYLOAD_TYPE"></a>

Expand Down
115 changes: 83 additions & 32 deletions docs/Reference/Server.md
Expand Up @@ -1545,35 +1545,61 @@ a custom constraint strategy with the same name.
#### printRoutes
<a id="print-routes"></a>

`fastify.printRoutes()`: Prints the representation of the internal radix tree
used by the router, useful for debugging. Alternatively, `fastify.printRoutes({
commonPrefix: false })` can be used to print the flattened routes tree.
`fastify.printRoutes()`: Fastify router builds a tree of routes for each HTTP
method. If you call the prettyPrint without specifying an HTTP method, it will
merge all the trees into one and print it. The merged tree doesn't represent the
internal router structure. **Don't use it for debugging.**

*Remember to call it inside or after a `ready` call.*

```js
fastify.get('/test', () => {})
fastify.get('/test/hello', () => {})
fastify.get('/hello/world', () => {})
fastify.get('/helicopter', () => {})
fastify.get('/testing', () => {})
fastify.get('/testing/:param', () => {})
fastify.put('/update', () => {})

fastify.ready(() => {
console.log(fastify.printRoutes())
// └── /
// ├── test (GET)
// │ └── /hello (GET)
// └── hel
// ├── lo/world (GET)
// └── licopter (GET)
// │ ├── /hello (GET)
// │ └── ing (GET)
// │ └── /
// │ └── :param (GET)
// └── update (PUT)
})
```

console.log(fastify.printRoutes({ commonPrefix: false }))
// └── / (-)
// ├── test (GET)
// │ └── /hello (GET)
// ├── hello/world (GET)
// └── helicopter (GET)
If you want to print the internal router tree, you should specify the `method`
param. Printed tree will represent the internal router structure.
**You can use it for debugging.**

})
```js
console.log(fastify.printRoutes({ method: 'GET' }))
// └── /
// └── test (GET)
// ├── /hello (GET)
// └── ing (GET)
// └── /
// └── :param (GET)

console.log(fastify.printRoutes({ method: 'PUT' }))
// └── /
// └── update (PUT)
```

`fastify.printRoutes({ commonPrefix: false })` will print compressed trees. This
might useful when you have a large number of routes with common prefixes.
It doesn't represent the internal router structure. **Don't use it for debugging.**

```js
console.log(fastify.printRoutes({ commonPrefix: false }))
// ├── /test (GET)
// │ ├── /hello (GET)
// │ └── ing (GET)
// │ └── /:param (GET)
// └── /update (PUT)
```

`fastify.printRoutes({ includeMeta: (true | []) })` will display properties from
Expand All @@ -1583,26 +1609,51 @@ A shorthand option, `fastify.printRoutes({ includeHooks: true })` will include
all [hooks](./Hooks.md).

```js
console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] }))
fastify.get('/test', () => {})
fastify.get('/test/hello', () => {})

const onTimeout = () => {}

fastify.addHook('onRequest', () => {})
fastify.addHook('onTimeout', onTimeout)

console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['errorHandler'] }))
// └── /
// ├── test (GET)
// │ • (onRequest) ["anonymous()","namedFunction()"]
// │ • (metaProperty) "value"
// │ └── /hello (GET)
// └── hel
// ├── lo/world (GET)
// │ • (onTimeout) ["anonymous()"]
// └── licopter (GET)
// └── test (GET)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// • (errorHandler) "defaultErrorHandler()"
// test (HEAD)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// • (onSend) ["headRouteOnSendHandler()"]
// • (errorHandler) "defaultErrorHandler()"
// └── /hello (GET)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// • (errorHandler) "defaultErrorHandler()"
// /hello (HEAD)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// • (onSend) ["headRouteOnSendHandler()"]
// • (errorHandler) "defaultErrorHandler()"

console.log(fastify.printRoutes({ includeHooks: true }))
// └── /
// ├── test (GET)
// │ • (onRequest) ["anonymous()","namedFunction()"]
// │ └── /hello (GET)
// └── hel
// ├── lo/world (GET)
// │ • (onTimeout) ["anonymous()"]
// └── licopter (GET)
// └── test (GET)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// test (HEAD)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// • (onSend) ["headRouteOnSendHandler()"]
// └── /hello (GET)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// /hello (HEAD)
// • (onTimeout) ["onTimeout()"]
// • (onRequest) ["anonymous()"]
// • (onSend) ["headRouteOnSendHandler()"]
```

#### printPlugins
Expand Down
5 changes: 5 additions & 0 deletions lib/errors.js
Expand Up @@ -177,6 +177,11 @@ const codes = {
'Cannot specify both logger.stream and logger.file options'
),

FST_ERR_LOG_INVALID_LOGGER: createError(
'FST_ERR_LOG_INVALID_LOGGER',
"Invalid logger object provided. The logger instance should have these functions(s): '%s'."
),

/**
* reply
*/
Expand Down
92 changes: 51 additions & 41 deletions lib/logger.js
Expand Up @@ -9,17 +9,17 @@
const nullLogger = require('abstract-logging')
const pino = require('pino')
const { serializersSym } = pino.symbols
const { FST_ERR_LOG_INVALID_DESTINATION } = require('./errors')
const {
FST_ERR_LOG_INVALID_DESTINATION,
FST_ERR_LOG_INVALID_LOGGER
} = require('./errors')

function createPinoLogger (opts, stream) {
stream = stream || opts.stream
delete opts.stream

if (stream && opts.file) {
function createPinoLogger (opts) {
if (opts.stream && opts.file) {
throw new FST_ERR_LOG_INVALID_DESTINATION()
} else if (opts.file) {
// we do not have stream
stream = pino.destination(opts.file)
opts.stream = pino.destination(opts.file)
delete opts.file
}

Expand All @@ -39,7 +39,7 @@ function createPinoLogger (opts, stream) {
opts.logger = prevLogger
opts.genReqId = prevGenReqId
} else {
logger = pino(opts, stream)
logger = pino(opts, opts.stream)
}

return logger
Expand Down Expand Up @@ -70,50 +70,60 @@ function now () {
}

function createLogger (options) {
if (isValidLogger(options.logger)) {
if (!options.logger) {
const logger = nullLogger
logger.child = () => logger
return { logger, hasLogger: false }
}

if (validateLogger(options.logger)) {
const logger = createPinoLogger({
logger: options.logger,
serializers: Object.assign({}, serializers, options.logger.serializers)
})
return { logger, hasLogger: true }
} else if (!options.logger) {
const logger = nullLogger
logger.child = () => logger
return { logger, hasLogger: false }
} else {
const localLoggerOptions = {}
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
Reflect.ownKeys(options.logger).forEach(prop => {
Object.defineProperty(localLoggerOptions, prop, {
value: options.logger[prop],
writable: true,
enumerable: true,
configurable: true
})
})
}
localLoggerOptions.level = localLoggerOptions.level || 'info'
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
options.logger = localLoggerOptions
const logger = createPinoLogger(options.logger)
return { logger, hasLogger: true }
}
}

function isValidLogger (logger) {
if (!logger) {
return false
const localLoggerOptions = {}
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
Reflect.ownKeys(options.logger).forEach(prop => {
Object.defineProperty(localLoggerOptions, prop, {
value: options.logger[prop],
writable: true,
enumerable: true,
configurable: true
})
})
}
localLoggerOptions.level = localLoggerOptions.level || 'info'
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
options.logger = localLoggerOptions
const logger = createPinoLogger(options.logger)
return { logger, hasLogger: true }
}

let result = true
/**
* Determines if a provided logger object meets the requirements
* of a Fastify compatible logger.
*
* @param {object} logger Object to validate.
*
* @returns {boolean} `true` when the logger meets the requirements.
*
* @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
* missing required methods.
*/
function validateLogger (logger) {
const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
for (let i = 0; i < methods.length; i += 1) {
if (!logger[methods[i]] || typeof logger[methods[i]] !== 'function') {
result = false
break
}
const missingMethods = methods.filter(method => !logger[method] || typeof logger[method] !== 'function')

if (!missingMethods.length) {
return true
} else if (missingMethods.length === methods.length) {
return false
} else {
throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
}
return result
}

module.exports = {
Expand Down
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -117,6 +117,11 @@
"name": "Luis Orbaiceta",
"email": "luisorbaiceta@gmail.com",
"url": "https://luisorbaiceta.com"
},
{
"name": "Carlos Fuentes",
"email": "me@metcoder.dev",
"url": "https://metcoder.dev"
}
],
"license": "MIT",
Expand Down
32 changes: 31 additions & 1 deletion test/logger.test.js
@@ -1,7 +1,6 @@
'use strict'

const { test, teardown, before } = require('tap')
const helper = require('./helper')
const http = require('http')
const stream = require('stream')
const split = require('split2')
Expand All @@ -13,6 +12,9 @@ const fs = require('fs')
const sget = require('simple-get').concat
const dns = require('dns')

const helper = require('./helper')
const { FST_ERR_LOG_INVALID_LOGGER } = require('../lib/errors')

const files = []
let count = 0
let localhost
Expand Down Expand Up @@ -268,6 +270,34 @@ test('can use external logger instance with custom serializer', t => {
})
})

test('should throw in case the external logger provided does not have a child method', t => {
t.plan(1)
const loggerInstance = {
info: console.info,
error: console.error,
debug: console.debug,
fatal: console.error,
warn: console.warn,
trace: console.trace
}

t.throws(
() => Fastify({ logger: loggerInstance }),
FST_ERR_LOG_INVALID_LOGGER,
"Invalid logger object provided. The logger instance should have these functions(s): 'child'."
)
})

test('should throw in case a partially matching logger is provided', t => {
t.plan(1)

t.throws(
() => Fastify({ logger: console }),
FST_ERR_LOG_INVALID_LOGGER,
"Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'."
)
})

test('expose the logger', t => {
t.plan(2)
let fastify = null
Expand Down

0 comments on commit acc8b31

Please sign in to comment.