├── .eslintrc.json ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.CN.md ├── README.md ├── bench ├── console.js ├── fast_json_console.js ├── logger.js ├── schema-copy.js ├── simple.js └── test-winston-3-logger.js ├── index.js ├── package-lock.json ├── package.json ├── stringify.js ├── stringify_schema.js └── test ├── logger.stringify.test.js ├── logger.test.js └── stringify_schema.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "no-param-reassign": 0, 5 | "max-len": ["error", { "ignoreComments": true }] 6 | }, 7 | "plugins": ["import"] 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18.x, 20.x, 22.x] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm i --no-fund --no-audit 22 | - run: npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | processed*.txt 40 | *.cpuprofile -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bench 2 | .nyc_output 3 | coverage 4 | processed*.txt 5 | *.cpuprofile 6 | *.log 7 | test 8 | .git 9 | .gitignore 10 | .github 11 | .eslintrc 12 | .prettierrc 13 | CHANGELOG.md 14 | README.CN.md 15 | README.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [3.2.1](https://github.com/yidinghan/koa2-winston/compare/v3.2.0...v3.2.1) (2024-10-24) 6 | 7 | ## [3.2.0](https://github.com/yidinghan/koa2-winston/compare/v3.1.1...v3.2.0) (2023-10-18) 8 | 9 | 10 | ### Features 11 | 12 | * **deps:** update to newest version ([24726fc](https://github.com/yidinghan/koa2-winston/commit/24726fc82fbb49099b4dcc93e64df7053925600a)) 13 | * **package:** update fast-json-stringify@1.15.3 ([c2fd149](https://github.com/yidinghan/koa2-winston/commit/c2fd1498d507250efdb021b93dd21a3053a8ac50)) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * **deps:** bump winston from 3.2.1 to 3.11.0 ([0ec392f](https://github.com/yidinghan/koa2-winston/commit/0ec392faf068ec7cab06891bc96714c5dd5c2f1e)) 19 | 20 | 21 | ## [3.1.1](https://github.com/yidinghan/koa2-winston/compare/v3.1.0...v3.1.1) (2018-11-06) 22 | 23 | 24 | 25 | 26 | # [3.1.0](https://github.com/yidinghan/koa2-winston/compare/v3.0.2...v3.1.0) (2018-11-06) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **stringify:** ensure type object in generate fn ([3a103e6](https://github.com/yidinghan/koa2-winston/commit/3a103e6)) 32 | * **stringify:** ignore not parent unselect paths ([95437bd](https://github.com/yidinghan/koa2-winston/commit/95437bd)) 33 | * **stringify:** missing httpVersion in defaultReqSchema ([74aa62d](https://github.com/yidinghan/koa2-winston/commit/74aa62d)) 34 | * **stringify:** missing uniq path handler and prefix ([9c3cb1e](https://github.com/yidinghan/koa2-winston/commit/9c3cb1e)) 35 | 36 | 37 | ### Features 38 | 39 | * **logger:** omit keys info keys ([333f5aa](https://github.com/yidinghan/koa2-winston/commit/333f5aa)) 40 | * **logger:** rename {req,res}.headers to {req,res}.header ([1e5b89f](https://github.com/yidinghan/koa2-winston/commit/1e5b89f)) 41 | * remove key recorder and use stringify schema ([61b13d0](https://github.com/yidinghan/koa2-winston/commit/61b13d0)) 42 | * **package:** mv lodash.assign to dev dependencies ([2adc855](https://github.com/yidinghan/koa2-winston/commit/2adc855)) 43 | * rename meta to info ([9f64f08](https://github.com/yidinghan/koa2-winston/commit/9f64f08)) 44 | * **stringify:** clone default schemas when generate ([fa15bba](https://github.com/yidinghan/koa2-winston/commit/fa15bba)) 45 | * **stringify:** ensuretypeobject in schemaKeysHandlers ([5f8adec](https://github.com/yidinghan/koa2-winston/commit/5f8adec)) 46 | * **stringify:** generate schema fn ([df17a22](https://github.com/yidinghan/koa2-winston/commit/df17a22)) 47 | * **stringify:** generateFormat fn to copy req/res ([5c631a5](https://github.com/yidinghan/koa2-winston/commit/5c631a5)) 48 | * **stringify:** predefined req.body schema ([1327244](https://github.com/yidinghan/koa2-winston/commit/1327244)) 49 | * **stringify:** top level key should also ignore ([6f0e028](https://github.com/yidinghan/koa2-winston/commit/6f0e028)) 50 | * **stringify schema:** init generate fn for winston log info obj ([31a83ed](https://github.com/yidinghan/koa2-winston/commit/31a83ed)) 51 | * **stringify schema:** use asJsonSchemaPath for easy lodash.get/set ([ce78410](https://github.com/yidinghan/koa2-winston/commit/ce78410)) 52 | 53 | 54 | ### BREAKING CHANGES 55 | 56 | * **logger:** rename {req,res}.headers to {req,res}.header in the log info object. like from `{ 57 | req: { headers: { } } }` to `{ req: { header: { } } }` 58 | 59 | 60 | 61 | 62 | ## [3.0.2](https://github.com/yidinghan/koa2-winston/compare/v3.0.1...v3.0.2) (2018-10-27) 63 | 64 | 65 | ### Features 66 | 67 | * **package:** drop support on node 7 ([f2e018c](https://github.com/yidinghan/koa2-winston/commit/f2e018c)) 68 | 69 | 70 | 71 | 72 | ## [3.0.1](https://github.com/yidinghan/koa2-winston/compare/v3.0.0...v3.0.1) (2018-10-27) 73 | 74 | 75 | ### Features 76 | 77 | * **package:** rm winston from peerDependencies ([c48992f](https://github.com/yidinghan/koa2-winston/commit/c48992f)) 78 | 79 | 80 | 81 | 82 | # [3.0.0](https://github.com/yidinghan/koa2-winston/compare/v2.5.1...v3.0.0) (2018-10-27) 83 | 84 | 85 | ### Features 86 | 87 | * **package:** mv winston and winston-transport to dependencies ([1eea08c](https://github.com/yidinghan/koa2-winston/commit/1eea08c)) 88 | * **stringify:** use printf to wrap stringify ([3debb21](https://github.com/yidinghan/koa2-winston/commit/3debb21)) 89 | * **winston:** transports.Stream as default ([0094519](https://github.com/yidinghan/koa2-winston/commit/0094519)) 90 | 91 | 92 | ### BREAKING CHANGES 93 | 94 | * **package:** no need for peer dependencies on winston 95 | 96 | 97 | 98 | 99 | ## [2.5.1](https://github.com/yidinghan/koa2-winston/compare/v2.5.0...v2.5.1) (2018-07-29) 100 | 101 | 102 | ### Performance Improvements 103 | 104 | * **getLogLevel:** math.floor faster then parse int ([d8fdda2](https://github.com/yidinghan/koa2-winston/commit/d8fdda2)) 105 | 106 | 107 | 108 | 109 | # [2.5.0](https://github.com/yidinghan/koa2-winston/compare/v2.4.1...v2.5.0) (2018-07-29) 110 | 111 | 112 | ### Features 113 | 114 | * **package:** update fast-json-stringify to 1.7.1 ([d09675d](https://github.com/yidinghan/koa2-winston/commit/d09675d)) 115 | 116 | 117 | 118 | 119 | ## [2.4.1](https://github.com/yidinghan/koa2-winston/compare/v2.4.0...v2.4.1) (2018-05-15) 120 | 121 | 122 | ### Features 123 | 124 | * **package:** update fast-json-stringify to 1.5.2 ([51eb3d8](https://github.com/yidinghan/koa2-winston/commit/51eb3d8)) 125 | 126 | 127 | 128 | 129 | # [2.4.0](https://github.com/yidinghan/koa2-winston/compare/v2.3.0...v2.4.0) (2018-04-04) 130 | 131 | 132 | ### Features 133 | 134 | * **logger:** lodash.assign to default transports ([8732613](https://github.com/yidinghan/koa2-winston/commit/8732613)) 135 | * **logger:** use object.keys for better perf ([446434f](https://github.com/yidinghan/koa2-winston/commit/446434f)) 136 | * **stringify:** support options.assign ([ee87f80](https://github.com/yidinghan/koa2-winston/commit/ee87f80)) 137 | 138 | 139 | 140 | 141 | # [2.3.0](https://github.com/yidinghan/koa2-winston/compare/v2.2.0...v2.3.0) (2018-04-04) 142 | 143 | 144 | ### Features 145 | 146 | * **stringify:** add flatstr method ([897ba46](https://github.com/yidinghan/koa2-winston/commit/897ba46)) 147 | * **stringify:** all log write to std out ([21dcd7c](https://github.com/yidinghan/koa2-winston/commit/21dcd7c)) 148 | * **stringify:** remove flatstr options ([4106d1c](https://github.com/yidinghan/koa2-winston/commit/4106d1c)) 149 | 150 | 151 | 152 | 153 | # [2.2.0](https://github.com/yidinghan/koa2-winston/compare/v2.1.0...v2.2.0) (2018-03-26) 154 | 155 | 156 | ### Bug Fixes 157 | 158 | * **fast json console:** missing default value ([d12580f](https://github.com/yidinghan/koa2-winston/commit/d12580f)) 159 | 160 | 161 | ### Features 162 | 163 | * export stringify in index ([4fe3a94](https://github.com/yidinghan/koa2-winston/commit/4fe3a94)) 164 | * **fast json console:** customized transports ([70101b6](https://github.com/yidinghan/koa2-winston/commit/70101b6)) 165 | * **fast json console:** speed up as defaults ([729768b](https://github.com/yidinghan/koa2-winston/commit/729768b)) 166 | 167 | 168 | 169 | 170 | # [2.1.0](https://github.com/yidinghan/koa2-winston/compare/v2.0.0...v2.1.0) (2018-03-26) 171 | 172 | 173 | ### Features 174 | 175 | * **stringify:** more reliable schema ([e5ec6ab](https://github.com/yidinghan/koa2-winston/commit/e5ec6ab)) 176 | 177 | 178 | 179 | 180 | # [2.0.0](https://github.com/yidinghan/koa2-winston/compare/v1.7.1...v2.0.0) (2018-03-26) 181 | 182 | 183 | ### Features 184 | 185 | * use fast json stringify as default stringify ([7d70b88](https://github.com/yidinghan/koa2-winston/commit/7d70b88)) 186 | * **stringify:** finish schema properties ([e832e6a](https://github.com/yidinghan/koa2-winston/commit/e832e6a)) 187 | * **stringify:** init fast json schema ([4cb2cd7](https://github.com/yidinghan/koa2-winston/commit/4cb2cd7)) 188 | * **stringify:** top level keys to schema ([3f08891](https://github.com/yidinghan/koa2-winston/commit/3f08891)) 189 | 190 | 191 | 192 | 193 | ## [1.7.1](https://github.com/yidinghan/koa2-winston/compare/v1.7.0...v1.7.1) (2017-08-29) 194 | 195 | 196 | ### Performance Improvements 197 | 198 | * **clone:** less conditions ([f1379f7](https://github.com/yidinghan/koa2-winston/commit/f1379f7)) 199 | 200 | 201 | 202 | 203 | # [1.7.0](https://github.com/yidinghan/koa2-winston/compare/v1.6.5...v1.7.0) (2017-08-29) 204 | 205 | 206 | ### Bug Fixes 207 | 208 | * **clone:** error params on tostring ([f60033f](https://github.com/yidinghan/koa2-winston/commit/f60033f)) 209 | 210 | 211 | ### Features 212 | 213 | * **clone:** use entries to clone object ([68f91ef](https://github.com/yidinghan/koa2-winston/commit/68f91ef)) 214 | 215 | 216 | 217 | 218 | ## [1.6.5](https://github.com/yidinghan/koa2-winston/compare/v1.6.4...v1.6.5) (2017-08-29) 219 | 220 | 221 | ### Bug Fixes 222 | 223 | * rm error arguments ([0fc1c5b](https://github.com/yidinghan/koa2-winston/commit/0fc1c5b)) 224 | * use copy logObject when unset properties ([78bee31](https://github.com/yidinghan/koa2-winston/commit/78bee31)) 225 | 226 | 227 | 228 | 229 | ## [1.6.4](https://github.com/yidinghan/koa2-winston/compare/v1.6.3...v1.6.4) (2017-05-11) 230 | 231 | 232 | 233 | 234 | ## [1.6.3](https://github.com/yidinghan/koa2-winston/compare/v1.6.2...v1.6.3) (2017-05-11) 235 | 236 | 237 | 238 | 239 | ## [1.6.2](https://github.com/yidinghan/koa2-winston/compare/v1.6.1...v1.6.2) (2017-05-05) 240 | 241 | 242 | ### Performance Improvements 243 | 244 | * **key recorder:** concat first ([e8acd4c](https://github.com/yidinghan/koa2-winston/commit/e8acd4c)) 245 | 246 | 247 | 248 | 249 | ## [1.6.1](https://github.com/yidinghan/koa2-winston/compare/v1.6.0...v1.6.1) (2017-04-25) 250 | 251 | 252 | ### Features 253 | 254 | * **logger:** default console transport print single-line output ([f3715ed](https://github.com/yidinghan/koa2-winston/commit/f3715ed)) 255 | 256 | 257 | 258 | 259 | # [1.6.0](https://github.com/yidinghan/koa2-winston/compare/v1.5.0...v1.6.0) (2017-04-24) 260 | 261 | 262 | ### Features 263 | 264 | * **logger:** add duration result to meta ([2049ddb](https://github.com/yidinghan/koa2-winston/commit/2049ddb)) 265 | * **logger:** add koa res serializer ([dff23e4](https://github.com/yidinghan/koa2-winston/commit/dff23e4)) 266 | * **logger:** add started_at and req.length, replace originalUrl by href ([d9840e0](https://github.com/yidinghan/koa2-winston/commit/d9840e0)) 267 | * **logger:** use on-finished to get final response result ([a21cfc2](https://github.com/yidinghan/koa2-winston/commit/a21cfc2)) 268 | * **logger:** use util.format as template to generate logger msg ([399cf24](https://github.com/yidinghan/koa2-winston/commit/399cf24)) 269 | 270 | 271 | 272 | 273 | # [1.5.0](https://github.com/yidinghan/koa2-winston/compare/v1.4.0...v1.5.0) (2017-04-24) 274 | 275 | 276 | ### Features 277 | 278 | * **serializer:** use keysRecorder to build serializer for ctx.request ([e068c46](https://github.com/yidinghan/koa2-winston/commit/e068c46)) 279 | 280 | 281 | 282 | 283 | # [1.4.0](https://github.com/yidinghan/koa2-winston/compare/v1.3.0...v1.4.0) (2017-04-11) 284 | 285 | 286 | ### Features 287 | 288 | * add standardise keys recorder ([83abd7b](https://github.com/yidinghan/koa2-winston/commit/83abd7b)) 289 | * **keysRecorder:** recorder return empty object ([a40ccbb](https://github.com/yidinghan/koa2-winston/commit/a40ccbb)) 290 | 291 | 292 | 293 | 294 | # [1.3.0](https://github.com/yidinghan/koa2-winston/compare/v1.2.1...v1.3.0) (2017-04-10) 295 | 296 | 297 | ### Features 298 | 299 | * **package:** add engines node version limitation ([453f871](https://github.com/yidinghan/koa2-winston/commit/453f871)) 300 | 301 | 302 | 303 | 304 | ## [1.2.1](https://github.com/yidinghan/koa2-winston/compare/v1.1.0...v1.2.1) (2017-04-10) 305 | 306 | 307 | ### Features 308 | 309 | * init index.js ([04f8e2e](https://github.com/yidinghan/koa2-winston/commit/04f8e2e)) 310 | * **ci:** init .travis.yml ([ef2e57e](https://github.com/yidinghan/koa2-winston/commit/ef2e57e)) 311 | * **ci:** update .travis.yml ([e9f1735](https://github.com/yidinghan/koa2-winston/commit/e9f1735)) 312 | * **package:** add lodash.omit and winston ([0f09ea4](https://github.com/yidinghan/koa2-winston/commit/0f09ea4)) 313 | 314 | 315 | 316 | 317 | # [1.2.0](https://github.com/yidinghan/koa2-winston/compare/v1.1.0...v1.2.0) (2017-04-10) 318 | 319 | 320 | ### Features 321 | 322 | * init index.js ([04f8e2e](https://github.com/yidinghan/koa2-winston/commit/04f8e2e)) 323 | * **ci:** init .travis.yml ([ef2e57e](https://github.com/yidinghan/koa2-winston/commit/ef2e57e)) 324 | * **package:** add lodash.omit and winston ([0f09ea4](https://github.com/yidinghan/koa2-winston/commit/0f09ea4)) 325 | 326 | 327 | 328 | 329 | # 1.1.0 (2017-04-10) 330 | 331 | 332 | ### Features 333 | 334 | * **package:** npm init ([fd4db1e](https://github.com/yidinghan/koa2-winston/commit/fd4db1e)) 335 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 yiding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.CN.md: -------------------------------------------------------------------------------- 1 | # koa2-winston 2 | 3 | [![Travis](https://img.shields.io/travis/yidinghan/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 4 | [![npm](https://img.shields.io/npm/l/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 5 | [![npm](https://img.shields.io/npm/v/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 6 | [![npm](https://img.shields.io/npm/dm/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 7 | [![David](https://img.shields.io/david/yidinghan/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 8 | [![David](https://img.shields.io/david/dev/yidinghan/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 9 | [![node](https://img.shields.io/node/v/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 10 | 11 | koa2 版本的 winston logger, 和 [express-winston](https://github.com/bithavoc/express-winston) 类似 12 | 13 | 在3行内将logger添加到koa2服务器 14 | 15 | 16 | 17 | - [koa2-winston](#koa2-winston) 18 | - [用法](#用法) 19 | - [安装](#安装) 20 | - [快速开始](#快速开始) 21 | - [配置](#配置) 22 | - [例子](#例子) 23 | - [不记录任何请求内容](#不记录任何请求内容) 24 | - [不记录任何响应内容](#不记录任何响应内容) 25 | - [不记录 UA](#不记录-ua) 26 | - [额外记录一个响应的字段](#额外记录一个响应的字段) 27 | - [JSDoc](#jsdoc) 28 | - [keysRecorder](#keysrecorder) 29 | - [logger](#logger) 30 | 31 | 32 | 33 | # 用法 34 | 35 | ## 安装 36 | 37 | ```shell 38 | npm i --save koa2-winston 39 | ``` 40 | 41 | ## 快速开始 42 | 43 | ```js 44 | const { logger } = require('koa2-winston'); 45 | app.use(logger()); 46 | ``` 47 | 48 | 访问的日志将会如下出现 49 | ```json 50 | { 51 | "req": { 52 | "headers": { 53 | "host": "localhost:3000", 54 | "connection": "keep-alive", 55 | "upgrade-insecure-requests": "1", 56 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", 57 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 58 | "dnt": "1", 59 | "accept-encoding": "gzip, deflate, sdch, br", 60 | "accept-language": "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,de;q=0.2,ja;q=0.2,it;q=0.2" 61 | }, 62 | "url": "/hello", 63 | "method": "GET", 64 | "href": "http://localhost:3000/hello", 65 | "query": {} 66 | }, 67 | "started_at": 1494554053492, 68 | "res": { 69 | "headers": { 70 | "content-type": "application/json; charset=utf-8", 71 | "content-length": "16" 72 | }, 73 | "status": 200 74 | }, 75 | "duration": 8, 76 | "level": "info", 77 | "message": "HTTP GET /hello" 78 | } 79 | ``` 80 | 81 | ## 配置 82 | 83 | 每一个变量都有一个默认值,你可以通过配置不同的变量自定义你的日志记录器 84 | 85 | ```js 86 | app.use(logger({ 87 | transports: new winston.transports.Console({ json: true, stringify: true }), 88 | level: 'info', 89 | reqKeys: ['headers','url','method', 'httpVersion','href','query','length'], 90 | reqSelect: [], 91 | reqUnselect: ['headers.cookie'], 92 | resKeys: ['headers','status'], 93 | resSelect: [], 94 | resUnselect: [], 95 | })); 96 | ``` 97 | 98 | 更多配置解析,可以在[logger](#logger)中查看 99 | 100 | ## 例子 101 | 102 | ### 不记录任何请求内容 103 | 104 | ```js 105 | app.use(logger({ 106 | reqKeys: [] 107 | })); 108 | ``` 109 | 110 | `req` 对象将会为空 111 | 112 | ```json 113 | { 114 | "req": { 115 | }, 116 | "started_at": 1494486039864, 117 | "res": { 118 | "headers": { 119 | "content-type": "text/plain; charset=utf-8", 120 | "content-length": "8" 121 | }, 122 | "status": 200 123 | }, 124 | "duration": 26, 125 | "level": "info", 126 | "message": "HTTP GET /" 127 | } 128 | ``` 129 | 130 | ### 不记录任何响应内容 131 | ```js 132 | app.use(logger({ 133 | resKeys: [] 134 | })); 135 | ``` 136 | 137 | `res` 对象将会为空 138 | 139 | ```json 140 | { 141 | "req": { 142 | "headers": { 143 | "host": "127.0.0.1:59534", 144 | "accept-encoding": "gzip, deflate", 145 | "user-agent": "node-superagent/3.5.2", 146 | "connection": "close" 147 | }, 148 | "url": "/", 149 | "method": "GET", 150 | "href": "http://127.0.0.1:59534/", 151 | "query": {} 152 | }, 153 | "started_at": 1494486039864, 154 | "res": { 155 | }, 156 | "duration": 26, 157 | "level": "info", 158 | "message": "HTTP GET /" 159 | } 160 | ``` 161 | 162 | ### 不记录 UA 163 | 164 | ```js 165 | app.use(logger({ 166 | reqUnselect: ['headers.cookies', 'headers.user-agent'] 167 | })); 168 | ``` 169 | 170 | 请求的 UA 将会被忽略 171 | 172 | ```json 173 | { 174 | "req": { 175 | "headers": { 176 | "host": "127.0.0.1:59534", 177 | "accept-encoding": "gzip, deflate", 178 | "connection": "close" 179 | }, 180 | "url": "/", 181 | "method": "GET", 182 | "href": "http://127.0.0.1:59534/", 183 | "query": {} 184 | }, 185 | "started_at": 1494486039864, 186 | "res": { 187 | "headers": { 188 | "content-type": "text/plain; charset=utf-8", 189 | "content-length": "8" 190 | }, 191 | "status": 200 192 | }, 193 | "duration": 26, 194 | "level": "info", 195 | "message": "HTTP GET /" 196 | } 197 | ``` 198 | 199 | ### 额外记录一个响应的字段 200 | 201 | ```js 202 | app.use(logger({ 203 | resSelect: ['body.success'] 204 | })); 205 | ``` 206 | 207 | `body` 里面的 `success` 字段将会被记录 208 | 209 | ```json 210 | { 211 | "req": { 212 | "headers": { 213 | "host": "127.0.0.1:59534", 214 | "accept-encoding": "gzip, deflate", 215 | "connection": "close" 216 | }, 217 | "url": "/", 218 | "method": "GET", 219 | "href": "http://127.0.0.1:59534/", 220 | "query": {} 221 | }, 222 | "started_at": 1494486039864, 223 | "res": { 224 | "headers": { 225 | "content-type": "text/plain; charset=utf-8", 226 | "content-length": "8" 227 | }, 228 | "status": 200, 229 | "body": { 230 | // 会记录下任何服务器响应的值 231 | "success": false 232 | } 233 | }, 234 | "duration": 26, 235 | "level": "info", 236 | "message": "HTTP GET /" 237 | } 238 | ``` 239 | 240 | # JSDoc 241 | 242 | 243 | 244 | ## keysRecorder 245 | 246 | keysRecorder 247 | use ldoash pick, get and set to collect data from given target object 248 | 249 | **Parameters** 250 | 251 | - `payload` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** input arguments (optional, default `{}`) 252 | - `payload.defaults` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>?** default keys will be collected 253 | - `payload.selects` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>?** keys will be collected as 254 | additional part 255 | - `payload.unselects` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>?** keys that will be ignored at last 256 | 257 | **Examples** 258 | 259 | ```javascript 260 | // without payload 261 | const recorder = keysRecorder(); 262 | recorder() // {} 263 | recorder({ foo: 1, bar: 2, foobar: { a: 3, b: 4 } }) // {} 264 | 265 | // with defaults 266 | const recorder = keysRecorder({ defaults: ['foo'] }); 267 | recorder() // {} 268 | recorder({ foo: 1, bar: 2, foobar: { a: 3, b: 4 } }) // { foo: 1 } 269 | 270 | // with defaults and selects 271 | const recorder = keysRecorder({ defaults: ['foo'], selects: ['foobar'] }); 272 | recorder() // {} 273 | recorder({ 274 | foo: 1, 275 | bar: 2, 276 | foobar: { a: 3, b: 4 } 277 | }) // { foo: 1, foobar: { a: 3, b: 4 } } 278 | 279 | // with defaults and unselects 280 | const recorder = keysRecorder({ defaults: ['foobar'], unselects: ['foobar.a'] }); 281 | recorder() // {} 282 | recorder({ 283 | foo: 1, 284 | bar: 2, 285 | foobar: { a: 3, b: 4 } 286 | }) // { foobar: { a: 3 } } 287 | 288 | // with defaults and selects and unselects 289 | const recorder = keysRecorder({ 290 | defaults: ['foo'], 291 | selects: ['foobar'], 292 | unselects: ['foobar.b'], 293 | }); 294 | recorder() // {} 295 | recorder({ 296 | foo: 1, 297 | bar: 2, 298 | foobar: { a: 3, b: 4 } 299 | }) // { foo: 1, foobar: { a: 3 } } 300 | ``` 301 | 302 | Returns **[function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** closure function, setting by given payload 303 | 304 | ## logger 305 | 306 | logger middleware for koa2 use winston 307 | 308 | **Parameters** 309 | 310 | - `payload` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** input arguments (optional, default `{}`) 311 | - `payload.transports` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** winston transports instance (optional, default `winston.transports.Console`) 312 | - `payload.level` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** default log level of logger (optional, default `'info'`) 313 | - `payload.reqKeys` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** default request fields to be logged (optional, default `['headers','url','method', 314 | 'httpVersion','href','query','length']`) 315 | - `payload.reqSelect` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** additional request fields to be logged (optional, default `[]`) 316 | - `payload.reqUnselect` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** request field 317 | will be removed from the log (optional, default `['headers.cookie']`) 318 | - `payload.resKeys` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** default response fields to be logged (optional, default `['headers','status']`) 319 | - `payload.resSelect` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** additional response fields to be logged (optional, default `[]`) 320 | - `payload.resUnselect` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** response field will be removed from the log (optional, default `[]`) 321 | 322 | **Examples** 323 | 324 | ```javascript 325 | const { logger } = require('koa2-winston'); 326 | app.use(logger()); 327 | // trrific logger look like down here 328 | // { 329 | // "req": { 330 | // "headers": { 331 | // "host": "127.0.0.1:59534", 332 | // "accept-encoding": "gzip, deflate", 333 | // "user-agent": "node-superagent/3.5.2", 334 | // "connection": "close" 335 | // }, 336 | // "url": "/", 337 | // "method": "GET", 338 | // "href": "http://127.0.0.1:59534/", 339 | // "query": {} 340 | // }, 341 | // "started_at": 1494486039864, 342 | // "res": { 343 | // "headers": { 344 | // "content-type": "text/plain; charset=utf-8", 345 | // "content-length": "8" 346 | // }, 347 | // "status": 200 348 | // }, 349 | // "duration": 26, 350 | // "level": "info", 351 | // "message": "HTTP GET /" 352 | // } 353 | ``` 354 | 355 | Returns **[function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** logger middleware 356 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koa2-winston 2 | 3 | ![GitHub Action](https://github.com/yidinghan/koa2-winston/actions/workflows/node.js.yml/badge.svg) 4 | [![npm](https://img.shields.io/npm/l/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 5 | [![npm](https://img.shields.io/npm/v/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 6 | [![npm](https://img.shields.io/npm/dm/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 7 | [![node](https://img.shields.io/node/v/koa2-winston.svg?style=flat-square)](https://www.npmjs.com/package/koa2-winston) 8 | 9 | koa2 version winston logger like [express-winston](https://github.com/bithavoc/express-winston) 10 | 11 | Add logger to your koa2 server in 3 lines 12 | 13 | [中文介绍](https://github.com/yidinghan/koa2-winston/blob/master/README.CN.md) 14 | 15 | 16 | 17 | - [koa2-winston](#koa2-winston) 18 | - [Usage](#usage) 19 | - [Installation](#installation) 20 | - [Quick Start](#quick-start) 21 | - [Configuration](#configuration) 22 | - [Examples](#examples) 23 | - [Do not record any request fields](#do-not-record-any-request-fields) 24 | - [Do not record any response fields](#do-not-record-any-response-fields) 25 | - [Do not record UA](#do-not-record-ua) 26 | - [Record a response body filed](#record-a-response-body-filed) 27 | - [Simple Benchmark](#simple-benchmark) 28 | - [Schema Stringify](#schema-stringify) 29 | - [v1.7.1 vs v2.4.0](#v171-vs-v240) 30 | - [Math.floor vs parseInt](#mathfloor-vs-parseint) 31 | - [v3](#v3) 32 | - [v3.1](#v31) 33 | - [JSDoc](#jsdoc) 34 | - [Table of Contents](#table-of-contents) 35 | - [logger](#logger) 36 | - [Parameters](#parameters) 37 | - [Examples](#examples-1) 38 | - [asJsonSchemaPath](#asjsonschemapath) 39 | - [Parameters](#parameters-1) 40 | - [ensureTypeObject](#ensuretypeobject) 41 | - [Parameters](#parameters-2) 42 | - [schemaKeysHandlerFn](#schemakeyshandlerfn) 43 | - [Parameters](#parameters-3) 44 | - [schemaKeysHandler](#schemakeyshandler) 45 | - [Parameters](#parameters-4) 46 | - [generateSchema](#generateschema) 47 | - [Parameters](#parameters-5) 48 | 49 | 50 | 51 | # Usage 52 | 53 | ## Installation 54 | 55 | ```shell 56 | npm i --save koa2-winston 57 | ``` 58 | 59 | ## Quick Start 60 | 61 | ```js 62 | const { logger } = require('koa2-winston'); 63 | app.use(logger()); 64 | ``` 65 | 66 | request log will look like 67 | 68 | ```json 69 | { 70 | "req": { 71 | "header": { 72 | "host": "localhost:3000", 73 | "connection": "keep-alive", 74 | "upgrade-insecure-requests": "1", 75 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", 76 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 77 | "dnt": "1", 78 | "accept-encoding": "gzip, deflate, sdch, br", 79 | "accept-language": "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,de;q=0.2,ja;q=0.2,it;q=0.2" 80 | }, 81 | "url": "/hello", 82 | "method": "GET", 83 | "href": "http://localhost:3000/hello", 84 | "query": {} 85 | }, 86 | "started_at": 1494554053492, 87 | "res": { 88 | "header": { 89 | "content-type": "application/json; charset=utf-8", 90 | "content-length": "16" 91 | }, 92 | "status": 200 93 | }, 94 | "duration": 8, 95 | "level": "info", 96 | "message": "HTTP GET /hello" 97 | } 98 | ``` 99 | 100 | ## Configuration 101 | 102 | Each parameter has a default value, and you can customize your logger by changing the configuration 103 | 104 | ```js 105 | app.use( 106 | logger({ 107 | transports: new winston.transports.Console({ json: true, stringify: true }), 108 | level: 'info', 109 | reqKeys: [ 110 | 'header', 111 | 'url', 112 | 'method', 113 | 'httpVersion', 114 | 'href', 115 | 'query', 116 | 'length', 117 | ], 118 | reqSelect: [], 119 | reqUnselect: ['header.cookie'], 120 | resKeys: ['header', 'status'], 121 | resSelect: [], 122 | resUnselect: [], 123 | }) 124 | ); 125 | ``` 126 | 127 | Many configuration explain can be found in [logger](#logger) 128 | 129 | ## Examples 130 | 131 | ### Do not record any request fields 132 | 133 | ```js 134 | app.use( 135 | logger({ 136 | reqKeys: [], 137 | }) 138 | ); 139 | ``` 140 | 141 | The req object will be empty 142 | 143 | ```json 144 | { 145 | "req": {}, 146 | "started_at": 1494486039864, 147 | "res": { 148 | "header": { 149 | "content-type": "text/plain; charset=utf-8", 150 | "content-length": "8" 151 | }, 152 | "status": 200 153 | }, 154 | "duration": 26, 155 | "level": "info", 156 | "message": "HTTP GET /" 157 | } 158 | ``` 159 | 160 | ### Do not record any response fields 161 | 162 | ```js 163 | app.use( 164 | logger({ 165 | resKeys: [], 166 | }) 167 | ); 168 | ``` 169 | 170 | The res object will be empty 171 | 172 | ```json 173 | { 174 | "req": { 175 | "header": { 176 | "host": "127.0.0.1:59534", 177 | "accept-encoding": "gzip, deflate", 178 | "user-agent": "node-superagent/3.5.2", 179 | "connection": "close" 180 | }, 181 | "url": "/", 182 | "method": "GET", 183 | "href": "http://127.0.0.1:59534/", 184 | "query": {} 185 | }, 186 | "started_at": 1494486039864, 187 | "res": {}, 188 | "duration": 26, 189 | "level": "info", 190 | "message": "HTTP GET /" 191 | } 192 | ``` 193 | 194 | ### Do not record UA 195 | 196 | ```js 197 | app.use( 198 | logger({ 199 | reqUnselect: ['header.cookie', 'header.user-agent'], 200 | }) 201 | ); 202 | ``` 203 | 204 | The UA of request will be ignored 205 | 206 | ```json 207 | { 208 | "req": { 209 | "header": { 210 | "host": "127.0.0.1:59534", 211 | "accept-encoding": "gzip, deflate", 212 | "connection": "close" 213 | }, 214 | "url": "/", 215 | "method": "GET", 216 | "href": "http://127.0.0.1:59534/", 217 | "query": {} 218 | }, 219 | "started_at": 1494486039864, 220 | "res": { 221 | "header": { 222 | "content-type": "text/plain; charset=utf-8", 223 | "content-length": "8" 224 | }, 225 | "status": 200 226 | }, 227 | "duration": 26, 228 | "level": "info", 229 | "message": "HTTP GET /" 230 | } 231 | ``` 232 | 233 | ### Record a response body filed 234 | 235 | ```js 236 | app.use( 237 | logger({ 238 | resSelect: ['body.success'], 239 | }) 240 | ); 241 | ``` 242 | 243 | The `success` field on `body` will be recorded 244 | 245 | ```json 246 | { 247 | "req": { 248 | "header": { 249 | "host": "127.0.0.1:59534", 250 | "accept-encoding": "gzip, deflate", 251 | "connection": "close" 252 | }, 253 | "url": "/", 254 | "method": "GET", 255 | "href": "http://127.0.0.1:59534/", 256 | "query": {} 257 | }, 258 | "started_at": 1494486039864, 259 | "res": { 260 | "header": { 261 | "content-type": "text/plain; charset=utf-8", 262 | "content-length": "8" 263 | }, 264 | "status": 200, 265 | "body": { 266 | // Any possible value given by the server 267 | "success": false 268 | } 269 | }, 270 | "duration": 26, 271 | "level": "info", 272 | "message": "HTTP GET /" 273 | } 274 | ``` 275 | 276 | # Simple Benchmark 277 | 278 | At node 8.2 279 | 280 | middleware x 90,281 ops/sec ±7.89% (13 runs sampled) 281 | 282 | At node 8.4 283 | 284 | middleware x 112,011 ops/sec ±10.26% (18 runs sampled) 285 | 286 | ## Schema Stringify 287 | 288 | With [fast-json-stringify](https://github.com/fastify/fast-json-stringify) support, default transport [logger](https://github.com/yidinghan/koa2-winston/blob/master/fast_json_console.js) is much faster 289 | 290 | ```sh 291 | total ops/sec { jsonstringify: 73544 } 292 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 293 | total ops/sec { schemastringify: 90223 } 294 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 295 | ``` 296 | 297 | `schemastringify` is 1.23x faster then `jsonstringify` in this case 298 | 299 | ## v1.7.1 vs v2.4.0 300 | 301 | ```sh 302 | total ops/sec { 'v1.7.1': 111416 } 303 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 304 | total ops/sec { 'v2.4.0': 131234 } 305 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 306 | ``` 307 | 308 | `v2.4.0` is 1.18x faster then `v1.7.1` in this case 309 | 310 | ## Math.floor vs parseInt 311 | 312 | Related commit in [ HERE ](https://github.com/yidinghan/koa2-winston/commit/d8fdda25f114810b225e4683137fdd39b5a27134) 313 | 314 | JSPerf link in [ HERE ](https://jsperf.com/minimum-multiple) 315 | 316 | Testing in Chrome 70.0.3505 / Mac OS X 10.13.5 317 | 318 | ``` 319 | parseInt(401 / 100, 10) { 160,092,130 Ops/sec } 320 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 321 | Math.floor(401 / 100) { 810,032,369 Ops/sec } 322 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 323 | ``` 324 | 325 | `Math.floor` is 5.06x faster then `parseInt` in this case 326 | 327 | ## v3 328 | 329 | Finally, winston v3 support. But winston will install as dependencies not peerDependencies. 330 | 331 | With better backward compatibility, users don't have to worry about the new version of koa2-winston will conflict with other winston usage in the project. 332 | 333 | ## v3.1 334 | 335 | The **`fastest`** koa2-winston ever. Nearly `3x` faster than previous versions. 336 | 337 | ```sh 338 | total ops/sec { 'v3.0.2': 180020 } 339 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 340 | total ops/sec { 'v3.1.0': 541854 } 341 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 342 | ``` 343 | 344 | > The above statistics come from `npm run bench`. 345 | 346 | Biggest change 347 | 348 | - Remove key recorder 349 | - Generate json-schema for fast-json-stringify, not only for object serialization (log message), but also as key selector. 350 | - Log info object key rename. 351 | ```diff 352 | - {req,res}.headers 353 | + {req,res}.header 354 | ``` 355 | 356 | # JSDoc 357 | 358 | 359 | 360 | ### Table of Contents 361 | 362 | - [logger](#logger) 363 | - [Parameters](#parameters) 364 | - [Examples](#examples) 365 | - [asJsonSchemaPath](#asjsonschemapath) 366 | - [Parameters](#parameters-1) 367 | - [ensureTypeObject](#ensuretypeobject) 368 | - [Parameters](#parameters-2) 369 | - [schemaKeysHandlerFn](#schemakeyshandlerfn) 370 | - [Parameters](#parameters-3) 371 | - [schemaKeysHandler](#schemakeyshandler) 372 | - [Parameters](#parameters-4) 373 | - [generateSchema](#generateschema) 374 | - [Parameters](#parameters-5) 375 | 376 | ## logger 377 | 378 | logger middleware for koa2 use winston 379 | 380 | ### Parameters 381 | 382 | - `payload` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** input arguments (optional, default `{}`) 383 | - `payload.transports` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)>** customize transports (optional, default `[newwinston.transports.Stream({stream:process.stdout})]`) 384 | - `payload.level` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** default log level of logger (optional, default `'info'`) 385 | - `payload.reqKeys` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** default request fields to be logged (optional, default `['header','url','method','httpVersion','href','query','length']`) 386 | - `payload.reqSelect` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** additional request fields to be logged (optional, default `[]`) 387 | - `payload.reqUnselect` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** request field will be removed from the log (optional, default `['header.cookie']`) 388 | - `payload.resKeys` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** default response fields to be logged (optional, default `['header','status']`) 389 | - `payload.resSelect` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** additional response fields to be logged (optional, default `[]`) 390 | - `payload.resUnselect` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** response field will be removed from the log (optional, default `[]`) 391 | - `payload.logger` **winston.transports.StreamTransportInstance?** customize winston logger 392 | - `payload.msg` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** customize log msg (optional, default `HTTP%s%s`) 393 | 394 | ### Examples 395 | 396 | ```javascript 397 | const { logger } = require('koa2-winston'); 398 | app.use(logger()); 399 | // request logger look like down here 400 | // { 401 | // "req": { 402 | // "header": { 403 | // "host": "127.0.0.1:59534", 404 | // "accept-encoding": "gzip, deflate", 405 | // "user-agent": "node-superagent/3.5.2", 406 | // "connection": "close" 407 | // }, 408 | // "url": "/", 409 | // "method": "GET", 410 | // "href": "http://127.0.0.1:59534/", 411 | // "query": {} 412 | // }, 413 | // "started_at": 1494486039864, 414 | // "res": { 415 | // "header": { 416 | // "content-type": "text/plain; charset=utf-8", 417 | // "content-length": "8" 418 | // }, 419 | // "status": 200 420 | // }, 421 | // "duration": 26, 422 | // "level": "info", 423 | // "message": "HTTP GET /" 424 | // } 425 | ``` 426 | 427 | Returns **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** logger middleware 428 | 429 | ## asJsonSchemaPath 430 | 431 | ### Parameters 432 | 433 | - `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 434 | 435 | ## ensureTypeObject 436 | 437 | ### Parameters 438 | 439 | - `schema` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** generated json schema 440 | 441 | ## schemaKeysHandlerFn 442 | 443 | Type: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function) 444 | 445 | ### Parameters 446 | 447 | - `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 448 | 449 | ## schemaKeysHandler 450 | 451 | ### Parameters 452 | 453 | - `keys` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** schema keys 454 | - `handler` **[schemaKeysHandlerFn](#schemakeyshandlerfn)** assign path 455 | 456 | ## generateSchema 457 | 458 | logger middleware for koa2 use winston 459 | 460 | ### Parameters 461 | 462 | - `payload` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** input arguments (optional, default `{}`) 463 | - `payload.reqKeys` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** default request fields to be logged (optional, default `['header','url','method','httpVersion','href','query','length']`) 464 | - `payload.reqSelect` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** additional request fields to be logged (optional, default `[]`) 465 | - `payload.reqUnselect` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** request field will be removed from the log (optional, default `['header.cookie']`) 466 | - `payload.resKeys` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** default response fields to be logged (optional, default `['header','status']`) 467 | - `payload.resSelect` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** additional response fields to be logged (optional, default `[]`) 468 | - `payload.resUnselect` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>** response field will be removed from the log (optional, default `[]`) 469 | -------------------------------------------------------------------------------- /bench/console.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | /* eslint import/no-extraneous-dependencies: 0 */ 3 | const Benchmark = require('benchmark'); 4 | const os = require('os'); 5 | 6 | const stringify = require('../stringify'); 7 | 8 | const bench = new Benchmark('console', { 9 | initCount: 100, 10 | onCycle: event => console.log(String(event.target)), 11 | fn() { 12 | process.stdout.write(stringify({ 13 | started_at: 1522078024245, 14 | duration: 388, 15 | level: 'info', 16 | message: 'HTTP get /ding', 17 | req: { headers: {}, url: '/ding', method: 'get' }, 18 | res: {}, 19 | }) + os.EOL); 20 | }, 21 | onComplete: complete => 22 | console.log({ 'total ops/sec': complete.target.hz }), 23 | }); 24 | 25 | bench.run(); 26 | -------------------------------------------------------------------------------- /bench/fast_json_console.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | /* eslint import/no-extraneous-dependencies: 0 */ 3 | const Benchmark = require('benchmark'); 4 | const winston = require('winston'); 5 | const assign = require('lodash.assign'); 6 | const FastJsonConsole = require('../fast_json_console'); 7 | const stringify = require('../stringify'); 8 | 9 | const suite = new Benchmark.Suite(); 10 | 11 | const getOptions = name => ({ 12 | initCount: 100, 13 | onCycle: ({ target }) => console.log(String(target)), 14 | onComplete: ({ target: { hz } }) => 15 | console.log('total ops/sec', { [name]: parseInt(hz, 10) }), 16 | }); 17 | 18 | const TEST_LOG = { 19 | started_at: 1522078024245, 20 | duration: 388, 21 | req: { headers: {}, url: '/ding', method: 'get' }, 22 | res: {}, 23 | }; 24 | 25 | const defaultLogger = new winston.Logger({ 26 | transports: [new FastJsonConsole()], 27 | }); 28 | const schemastringifyLogger = new winston.Logger({ 29 | transports: [new FastJsonConsole({ stringify })], 30 | }); 31 | const loassignLogger = new winston.Logger({ 32 | transports: [new FastJsonConsole({ assign })], 33 | }); 34 | const loschemaLogger = new winston.Logger({ 35 | transports: [new FastJsonConsole({ assign, stringify })], 36 | }); 37 | 38 | suite 39 | .add( 40 | 'warmup', 41 | () => defaultLogger.info('test', TEST_LOG), 42 | getOptions('warmup'), 43 | ) 44 | .add( 45 | 'default', 46 | () => defaultLogger.info('test', TEST_LOG), 47 | getOptions('default'), 48 | ) 49 | .add( 50 | 'loassign', 51 | () => loassignLogger.info('test', TEST_LOG), 52 | getOptions('loassign'), 53 | ) 54 | .add( 55 | 'schemastringify', 56 | () => schemastringifyLogger.info('test', TEST_LOG), 57 | getOptions('schemastringify'), 58 | ) 59 | .add( 60 | 'loschema', 61 | () => loschemaLogger.info('test', TEST_LOG), 62 | getOptions('loschema'), 63 | ) 64 | .run(); 65 | -------------------------------------------------------------------------------- /bench/logger.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | /* eslint import/no-extraneous-dependencies: 0 */ 3 | const Benchmark = require('benchmark'); 4 | const FastJsonConsole = require('../fast_json_console'); 5 | const stringify = require('../stringify'); 6 | const os = require('os'); 7 | const winston = require('winston'); 8 | const EventEmitter = require('events'); 9 | 10 | const { logger } = require('../'); 11 | 12 | const stringifyMW = logger({ 13 | transports: [new FastJsonConsole({ stringify })], 14 | }); 15 | const consoleMW = logger({ 16 | transports: [new winston.transports.Console({ json: true, stringify: true })], 17 | }); 18 | 19 | const suite = new Benchmark.Suite(); 20 | 21 | const getOptions = (name, defer = true) => ({ 22 | initCount: 100, 23 | defer, 24 | onCycle: ({ target }) => console.log(String(target)), 25 | onComplete: ({ target: { hz } }) => 26 | console.log('total ops/sec', { [name]: parseInt(hz, 10) }), 27 | }); 28 | 29 | const TEST_LOG = { 30 | started_at: 1522078024245, 31 | duration: 388, 32 | level: 'info', 33 | message: 'HTTP get /ding', 34 | req: { headers: {}, url: '/ding', method: 'get' }, 35 | res: {}, 36 | }; 37 | 38 | suite 39 | .add( 40 | 'stdout with schemastringify', 41 | () => { 42 | process.stdout.write(stringify(TEST_LOG) + os.EOL); 43 | }, 44 | getOptions('stdout with schemastringify', false), 45 | ) 46 | .add( 47 | 'stdout with jsonstringify', 48 | () => { 49 | process.stdout.write(JSON.stringify(TEST_LOG) + os.EOL); 50 | }, 51 | getOptions('stdout with jsonstringify', false), 52 | ) 53 | .add( 54 | 'console', 55 | async (deferred) => { 56 | const event = new EventEmitter(); 57 | await consoleMW( 58 | { 59 | request: { 60 | method: 'get', 61 | url: '/ding', 62 | headers: { 63 | cookie: 'ding', 64 | }, 65 | }, 66 | response: event, 67 | }, 68 | () => event.emit('end'), 69 | ); 70 | deferred.resolve(); 71 | }, 72 | getOptions('console'), 73 | ) 74 | .add( 75 | 'stringify', 76 | async (deferred) => { 77 | const event = new EventEmitter(); 78 | await stringifyMW( 79 | { 80 | request: { 81 | method: 'get', 82 | url: '/ding', 83 | headers: { 84 | cookie: 'ding', 85 | }, 86 | }, 87 | response: event, 88 | }, 89 | () => event.emit('end'), 90 | ); 91 | deferred.resolve(); 92 | }, 93 | getOptions('stringify'), 94 | ) 95 | .run(); 96 | 97 | // middleware x 41,182 ops/sec ±4.19% (21 runs sampled) 98 | 99 | // node 8.2 100 | // middleware x 80,848 ops/sec ±8.30% (17 runs sampled) 101 | 102 | // node 8.4 103 | // middleware x 107,464 ops/sec ±7.99% (19 runs sampled) 104 | 105 | // node 8.10 106 | // with fast-json-stringify 107 | // middleware x 132,868 ops/sec ±2.58% (19 runs sampled) 108 | -------------------------------------------------------------------------------- /bench/schema-copy.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | /* eslint import/no-extraneous-dependencies: 0 */ 3 | const Benchmark = require('benchmark'); 4 | const fastJson = require('fast-json-stringify'); 5 | 6 | const suite = new Benchmark.Suite(); 7 | 8 | const schema = { 9 | type: 'object', 10 | properties: { 11 | a: { type: 'string' }, 12 | b: { 13 | type: 'object', 14 | properties: { 15 | c: { type: 'number' }, 16 | d: { type: 'string' }, 17 | e: { 18 | type: 'object', 19 | properties: { 20 | f: { type: 'boolean' }, 21 | g: { type: 'string' }, 22 | }, 23 | }, 24 | }, 25 | }, 26 | }, 27 | }; 28 | 29 | const stringify = fastJson(schema); 30 | 31 | const schemaCopy = obj => JSON.parse(stringify(obj)); 32 | const simpleCopy = obj => JSON.parse(JSON.stringify(obj)); 33 | const forkCopy = (obj) => { 34 | if (obj === null || typeof obj !== 'object') { 35 | return obj; 36 | } 37 | 38 | const copycat = {}; 39 | Object.keys(obj).forEach((key) => { 40 | copycat[key] = forkCopy(obj[key]); 41 | }); 42 | 43 | return copycat; 44 | }; 45 | 46 | // const schemaCopy = obj => stringify(obj); 47 | // const simpleCopy = obj => JSON.stringify(obj); 48 | 49 | const TEST_OBJ = { 50 | a: 'ding', 51 | b: { 52 | c: 3, 53 | d: '3', 54 | e: { 55 | f: true, 56 | g: Array(100) 57 | .fill('ding') 58 | .join(), 59 | }, 60 | }, 61 | h: 100, 62 | }; 63 | 64 | const argv = process.argv.slice(2).join(''); 65 | 66 | const getOptions = name => ({ 67 | // initCount: 100, 68 | // onCycle: ({ target }) => console.log(String(target)), 69 | onComplete: ({ target: { hz } }) => 70 | console.log('total ops/sec', { [name]: parseInt(hz, 10) }), 71 | }); 72 | if (!module.parent) { 73 | if (/schema/.test(argv)) { 74 | suite.add( 75 | 'schemaCopy', 76 | () => schemaCopy(TEST_OBJ), 77 | getOptions('schemaCopy'), 78 | ); 79 | } 80 | if (/simple/.test(argv)) { 81 | suite.add( 82 | 'simpleCopy', 83 | () => { 84 | const obj = simpleCopy(TEST_OBJ); 85 | delete obj.h; 86 | }, 87 | getOptions('simpleCopy'), 88 | ); 89 | } 90 | if (/fork/.test(argv)) { 91 | suite.add( 92 | 'forkCopy', 93 | () => { 94 | const obj = forkCopy(TEST_OBJ); 95 | delete obj.h; 96 | }, 97 | getOptions('forkCopy'), 98 | ); 99 | } 100 | 101 | suite.run(); 102 | } 103 | 104 | module.exports = { simpleCopy, schemaCopy, forkCopy }; 105 | 106 | // stats without delete 107 | // ➜ koa2-winston git:(master) node bench/schema-copy.js fork 108 | // total ops/sec { forkCopy: 1531231 } 109 | // total ops/sec { forkCopy: 1544226 } 110 | // total ops/sec { forkCopy: 1531241 } 111 | // ➜ koa2-winston git:(master) node bench/schema-copy.js schema 112 | // total ops/sec { schemaCopy: 151576 } 113 | // total ops/sec { schemaCopy: 152341 } 114 | // total ops/sec { schemaCopy: 153551 } 115 | // ➜ koa2-winston git:(master) node bench/schema-copy.js simple 116 | // total ops/sec { simpleCopy: 149366 } 117 | // total ops/sec { simpleCopy: 149004 } 118 | // total ops/sec { simpleCopy: 147134 } 119 | 120 | // stats with delete 121 | // ➜ koa2-winston git:(master) node bench/schema-copy.js fork 122 | // total ops/sec { forkCopy: 1319846 } 123 | // total ops/sec { forkCopy: 1312784 } 124 | // total ops/sec { forkCopy: 1312258 } 125 | // ➜ koa2-winston git:(master) node bench/schema-copy.js schema 126 | // total ops/sec { schemaCopy: 152244 } 127 | // total ops/sec { schemaCopy: 152654 } 128 | // total ops/sec { schemaCopy: 153078 } 129 | // ➜ koa2-winston git:(master) node bench/schema-copy.js simple 130 | // total ops/sec { simpleCopy: 140539 } 131 | // total ops/sec { simpleCopy: 141226 } 132 | // total ops/sec { simpleCopy: 141576 } 133 | -------------------------------------------------------------------------------- /bench/simple.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | /* eslint max-len: ["error", 100] */ 3 | /* eslint import/no-extraneous-dependencies: 0 */ 4 | const Benchmark = require('benchmark'); 5 | const EventEmitter = require('events'); 6 | 7 | const { logger } = require('../'); 8 | 9 | const middleware = logger(); 10 | const suite = new Benchmark.Suite(); 11 | const getOptions = (name, defer = true) => ({ 12 | initCount: 100, 13 | defer, 14 | onComplete: ({ target: { hz } }) => console.log('total ops/sec', { [name]: parseInt(hz, 10) }), 15 | }); 16 | 17 | suite 18 | .add( 19 | 'middleware', 20 | async (deferred) => { 21 | const event = new EventEmitter(); 22 | await middleware( 23 | { 24 | request: { 25 | method: 'get', 26 | url: '/ding', 27 | header: { 28 | cookie: 'ding', 29 | }, 30 | }, 31 | response: event, 32 | }, 33 | () => event.emit('end'), 34 | ); 35 | deferred.resolve(); 36 | }, 37 | getOptions('middleware'), 38 | ) 39 | .run(); 40 | -------------------------------------------------------------------------------- /bench/test-winston-3-logger.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | const EventEmitter = require('events'); 3 | const { createLogger, format, transports } = require('winston'); 4 | 5 | const { logger } = require('../'); 6 | 7 | const consoleLogger = createLogger({ 8 | format: format.combine( 9 | format.errors({ stack: true }), 10 | format.splat(), 11 | format.json(), 12 | format.prettyPrint(), 13 | ), 14 | transports: [new transports.Console()], 15 | }); 16 | const middleware = logger({ 17 | // @ts-ignore 18 | logger: consoleLogger, 19 | }); 20 | 21 | const event = new EventEmitter(); 22 | middleware( 23 | { 24 | request: { 25 | method: 'get', 26 | url: '/ding', 27 | header: { 28 | cookie: 'ding', 29 | }, 30 | }, 31 | response: event, 32 | }, 33 | () => event.emit('end'), 34 | ); 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-param-reassign: 0 */ 2 | const winston = require('winston'); 3 | const onFinished = require('on-finished'); 4 | const { format } = require('util'); 5 | 6 | const { 7 | generateSchema, 8 | generateFormat, 9 | defaultSchemas, 10 | } = require('./stringify_schema'); 11 | 12 | const { 13 | createLogger, 14 | format: { combine: wfcombine, printf: wfprintf }, 15 | } = winston; 16 | 17 | const C = { 18 | INFO: 'info', 19 | WARN: 'warn', 20 | ERROR: 'error', 21 | MSG: 'HTTP %s %s', 22 | }; 23 | 24 | const getLogLevel = (statusCode = 200, defaultLevel = C.INFO) => { 25 | switch (Math.floor(statusCode / 100)) { 26 | case 5: 27 | return C.ERROR; 28 | case 4: 29 | return C.WARN; 30 | default: 31 | return defaultLevel; 32 | } 33 | }; 34 | 35 | /** 36 | * logger middleware for koa2 use winston 37 | * 38 | * @param {object} [payload={}] - input arguments 39 | * @param {object[]} [payload.transports=[new winston.transports.Stream({ stream: process.stdout })]] customize transports 40 | * @param {string} [payload.level='info'] - default log level of logger 41 | * @param {string} [payload.reqKeys=['header','url','method','httpVersion', 'href', 'query', 'length']] - default request fields to be logged 42 | * @param {string} [payload.reqSelect=[]] - additional request fields to be logged 43 | * @param {string} [payload.reqUnselect=['header.cookie']] - request field will be removed from the log 44 | * @param {string} [payload.resKeys=['header', 'status']] - default response fields to be logged 45 | * @param {string} [payload.resSelect=[]] - additional response fields to be logged 46 | * @param {string} [payload.resUnselect=[]] - response field will be removed from the log 47 | * @param {winston.transports.StreamTransportInstance} [payload.logger] - customize winston logger 48 | * @param {string} [payload.msg=HTTP %s %s] - customize log msg 49 | * @return {function} logger middleware 50 | * @example 51 | * const { logger } = require('koa2-winston'); 52 | * app.use(logger()); 53 | * // request logger look like down here 54 | * // { 55 | * // "req": { 56 | * // "header": { 57 | * // "host": "127.0.0.1:59534", 58 | * // "accept-encoding": "gzip, deflate", 59 | * // "user-agent": "node-superagent/3.5.2", 60 | * // "connection": "close" 61 | * // }, 62 | * // "url": "/", 63 | * // "method": "GET", 64 | * // "href": "http://127.0.0.1:59534/", 65 | * // "query": {} 66 | * // }, 67 | * // "started_at": 1494486039864, 68 | * // "res": { 69 | * // "header": { 70 | * // "content-type": "text/plain; charset=utf-8", 71 | * // "content-length": "8" 72 | * // }, 73 | * // "status": 200 74 | * // }, 75 | * // "duration": 26, 76 | * // "level": "info", 77 | * // "message": "HTTP GET /" 78 | * // } 79 | */ 80 | const logger = (payload = {}) => { 81 | const { 82 | transports = [new winston.transports.Stream({ stream: process.stdout })], 83 | level: defaultLevel = C.INFO, 84 | msg = C.MSG, 85 | } = payload; 86 | 87 | // @ts-ignore 88 | const stringifyFormat = generateFormat(payload); 89 | const winstonLogger = payload.logger 90 | || createLogger({ 91 | transports, 92 | format: wfcombine(wfprintf(stringifyFormat)), 93 | }); 94 | 95 | const onResponseFinished = (ctx, info) => { 96 | info.res = ctx.response; 97 | info.duration = Date.now() - info.started_at; 98 | 99 | info.level = getLogLevel(info.res.status, defaultLevel); 100 | // @ts-ignore 101 | winstonLogger.log(info); 102 | }; 103 | 104 | return async (ctx, next) => { 105 | const info = { req: ctx.request, started_at: Date.now() }; 106 | info.message = format(msg, info.req.method, info.req.url); 107 | 108 | let error; 109 | try { 110 | await next(); 111 | } catch (e) { 112 | // catch and throw it later 113 | error = e; 114 | } finally { 115 | onFinished(ctx.response, onResponseFinished.bind(null, ctx, info)); 116 | } 117 | 118 | if (error) { 119 | throw error; 120 | } 121 | }; 122 | }; 123 | 124 | module.exports = { 125 | logger, 126 | getLogLevel, 127 | generateSchema, 128 | generateFormat, 129 | defaultSchemas, 130 | }; 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa2-winston", 3 | "version": "3.2.1", 4 | "description": "koa2 version winston logger like express-winston", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=8" 8 | }, 9 | "scripts": { 10 | "doc": "documentation readme index.js --section=JSDoc", 11 | "lint": "eslint ./test/*.js ./index.js --fix", 12 | "release": "standard-version", 13 | "bench": "node bench/simple.js | grep 'ops'", 14 | "nyc": "nyc ava && nyc report", 15 | "test": "nyc ava" 16 | }, 17 | "ava": { 18 | "files": [ 19 | "test/**/*" 20 | ] 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/yidinghan/koa2-winston.git" 25 | }, 26 | "keywords": [ 27 | "koa", 28 | "koa2", 29 | "winston", 30 | "logger", 31 | "nodejs" 32 | ], 33 | "author": "yidinghan", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/yidinghan/koa2-winston/issues" 37 | }, 38 | "homepage": "https://github.com/yidinghan/koa2-winston#readme", 39 | "dependencies": { 40 | "fast-json-stringify": "^6.0.0", 41 | "lodash.clonedeep": "^4.5.0", 42 | "lodash.get": "^4.4.2", 43 | "lodash.mapvalues": "^4.6.0", 44 | "lodash.set": "^4.3.2", 45 | "on-finished": "^2.4.1", 46 | "winston": "^3.15.0" 47 | }, 48 | "devDependencies": { 49 | "@types/node": "^20.8.6", 50 | "ava": "^5.3.1", 51 | "benchmark": "^2.1.4", 52 | "documentation": "^14.0.2", 53 | "eslint": "^8.51.0", 54 | "eslint-config-airbnb-base": "^15.0.0", 55 | "eslint-plugin-import": "^2.28.1", 56 | "koa": "^2.14.2", 57 | "koa-bodyparser": "^4.4.1", 58 | "lodash": "^4.17.21", 59 | "lodash.assign": "^4.2.0", 60 | "nyc": "^15.1.0", 61 | "supertest": "^6.3.3", 62 | "triple-beam": "^1.4.1", 63 | "winston-transport": "^4.6.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /stringify.js: -------------------------------------------------------------------------------- 1 | const fastJson = require('fast-json-stringify'); 2 | 3 | // { 4 | // "req": { 5 | // "headers": { 6 | // "host": "127.0.0.1", 7 | // "x-forwarded-for": "127.0.0.1", 8 | // "x-auth": "authkey", 9 | // "accept": "application/json" 10 | // }, 11 | // "url": "/hello?foo=bar", 12 | // "method": "GET", 13 | // "href": "http://127.0.0.1/hello?foo=bar", 14 | // "query": { 15 | // "foo": "bar" 16 | // } 17 | // }, 18 | // "started_at": 1522032522164, 19 | // "res": { 20 | // "headers": { 21 | // "content-type": "application/json; charset=utf-8", 22 | // "content-length": "281" 23 | // }, 24 | // "status": 200 25 | // }, 26 | // "duration": 2, 27 | // "level": "info", 28 | // "message": "HTTP GET /hello?foo=bar" 29 | // } 30 | 31 | const stringify = fastJson({ 32 | title: 'koa2 logger', 33 | type: 'object', 34 | properties: { 35 | started_at: { type: 'integer' }, 36 | duration: { type: 'integer' }, 37 | level: { type: 'string' }, 38 | message: { type: 'string' }, 39 | req: { 40 | type: 'object', 41 | properties: { 42 | headers: { 43 | type: 'object', 44 | additionalProperties: { type: 'string' }, 45 | }, 46 | url: { type: 'string' }, 47 | method: { type: 'string' }, 48 | href: { type: 'string' }, 49 | query: { 50 | type: 'object', 51 | additionalProperties: { type: 'string' }, 52 | }, 53 | origin: { type: 'string' }, 54 | originalUrl: { type: 'string' }, 55 | path: { type: 'string' }, 56 | querystring: { type: 'string' }, 57 | search: { type: 'string' }, 58 | hostname: { type: 'string' }, 59 | URL: { type: 'string' }, 60 | type: { type: 'string' }, 61 | charset: { type: 'string' }, 62 | protocol: { type: 'string' }, 63 | secure: { type: 'string' }, 64 | ip: { type: 'string' }, 65 | }, 66 | }, 67 | res: { 68 | type: 'object', 69 | properties: { 70 | headers: { 71 | type: 'object', 72 | additionalProperties: { type: 'string' }, 73 | }, 74 | status: { type: 'string' }, 75 | body: { 76 | type: 'object', 77 | additionalProperties: true, 78 | }, 79 | }, 80 | }, 81 | }, 82 | }); 83 | 84 | module.exports = stringify; 85 | -------------------------------------------------------------------------------- /stringify_schema.js: -------------------------------------------------------------------------------- 1 | const set = require('lodash.set'); 2 | const get = require('lodash.get'); 3 | const mapvalues = require('lodash.mapvalues'); 4 | const clonedeep = require('lodash.clonedeep'); 5 | const fastJson = require('fast-json-stringify'); 6 | 7 | const PREFIXS = ['req', 'res']; 8 | 9 | const defaultSchemas = { 10 | res: { 11 | title: 'koa2-winston-info-res', 12 | type: 'object', 13 | properties: { 14 | header: { 15 | type: 'object', 16 | additionalProperties: { type: 'string' }, 17 | }, 18 | status: { type: 'string' }, 19 | body: { 20 | type: 'object', 21 | additionalProperties: true, 22 | }, 23 | }, 24 | }, 25 | req: { 26 | title: 'koa2-winston-info-req', 27 | type: 'object', 28 | properties: { 29 | header: { 30 | type: 'object', 31 | additionalProperties: { type: 'string' }, 32 | }, 33 | url: { type: 'string' }, 34 | method: { type: 'string' }, 35 | href: { type: 'string' }, 36 | body: { 37 | type: 'object', 38 | additionalProperties: true, 39 | }, 40 | query: { 41 | type: 'object', 42 | additionalProperties: { type: 'string' }, 43 | }, 44 | origin: { type: 'string' }, 45 | originalUrl: { type: 'string' }, 46 | path: { type: 'string' }, 47 | querystring: { type: 'string' }, 48 | search: { type: 'string' }, 49 | hostname: { type: 'string' }, 50 | URL: { type: 'string' }, 51 | type: { type: 'string' }, 52 | charset: { type: 'string' }, 53 | protocol: { type: 'string' }, 54 | secure: { type: 'string' }, 55 | ip: { type: 'string' }, 56 | httpVersion: { type: 'string' }, 57 | length: { type: 'integer' }, 58 | }, 59 | }, 60 | info: { 61 | title: 'koa2-winston-info', 62 | definitions: { 63 | req: {}, 64 | res: {}, 65 | }, 66 | type: 'object', 67 | properties: { 68 | started_at: { type: 'integer' }, 69 | duration: { type: 'integer' }, 70 | level: { type: 'string' }, 71 | message: { type: 'string' }, 72 | req: { $ref: '#/definitions/req' }, 73 | res: { $ref: '#/definitions/res' }, 74 | }, 75 | }, 76 | }; 77 | 78 | const DOT_RE = /\./g; 79 | /** 80 | * @param {string} path 81 | */ 82 | const asJsonSchemaPath = (path) => path.replace(DOT_RE, '.properties.'); 83 | 84 | /** 85 | * @param {object} schema - generated json schema 86 | */ 87 | const ensureTypeObject = (schema) => mapvalues(schema, (value) => { 88 | if (typeof value !== 'object') { 89 | return value; 90 | } 91 | if (value.properties) { 92 | // eslint-disable-next-line no-param-reassign 93 | value.type = 'object'; 94 | } 95 | return ensureTypeObject(value); 96 | }); 97 | 98 | /** 99 | * @callback schemaKeysHandlerFn 100 | * @param {string} path 101 | */ 102 | 103 | /** 104 | * @param {string[]} keys - schema keys 105 | * @param {schemaKeysHandlerFn} handler - assign path 106 | */ 107 | const schemaKeysHandler = (keys, handler) => keys 108 | .map(asJsonSchemaPath) 109 | .map((path) => `properties.${path}`) 110 | .map(handler); 111 | 112 | const schemaKeysHandlers = ({ 113 | keys, select, unselect, schema, 114 | }) => { 115 | const outputSchema = { type: 'object', properties: {} }; 116 | schemaKeysHandler(keys.concat(select), (path) => { 117 | set(outputSchema, path, get(schema, path, {})); 118 | }); 119 | schemaKeysHandler(unselect, (path) => { 120 | const parentPath = path.match(DOT_RE).length > 2 121 | ? path 122 | .split('.') 123 | .slice(0, -2) 124 | .join('.') 125 | : path; 126 | if (!get(outputSchema, parentPath)) { 127 | return; 128 | } 129 | set(outputSchema, path, { type: 'null' }); 130 | }); 131 | return outputSchema; 132 | }; 133 | 134 | /** 135 | * logger middleware for koa2 use winston 136 | * 137 | * @param {object} [payload={}] - input arguments 138 | * @param {string[]} [payload.reqKeys=['header','url','method','httpVersion', 'href', 'query', 'length']] - default request fields to be logged 139 | * @param {string[]} [payload.reqSelect=[]] - additional request fields to be logged 140 | * @param {string[]} [payload.reqUnselect=['header.cookie']] - request field will be removed from the log 141 | * @param {string[]} [payload.resKeys=['header', 'status']] - default response fields to be logged 142 | * @param {string[]} [payload.resSelect=[]] - additional response fields to be logged 143 | * @param {string[]} [payload.resUnselect=[]] - response field will be removed from the log 144 | */ 145 | const generateSchema = (payload) => { 146 | const options = { 147 | reqUnselect: ['header.cookie'], 148 | reqSelect: [], 149 | reqKeys: [ 150 | 'header', 151 | 'url', 152 | 'method', 153 | 'httpVersion', 154 | 'href', 155 | 'query', 156 | 'length', 157 | ], 158 | resUnselect: [], 159 | resSelect: [], 160 | resKeys: ['header', 'status'], 161 | ...payload, 162 | }; 163 | 164 | const { info: infoSchema } = clonedeep(defaultSchemas); 165 | PREFIXS.forEach((prefix) => { 166 | infoSchema.definitions[prefix] = schemaKeysHandlers({ 167 | keys: options[`${prefix}Keys`], 168 | select: options[`${prefix}Select`], 169 | unselect: options[`${prefix}Unselect`], 170 | schema: defaultSchemas[prefix], 171 | }); 172 | }); 173 | 174 | ensureTypeObject(infoSchema.definitions); 175 | return infoSchema; 176 | }; 177 | 178 | const generateFormat = (payload) => { 179 | const schema = generateSchema(payload); 180 | const stringify = fastJson(schema); 181 | const keys = { 182 | req: Object.keys(schema.definitions.req.properties), 183 | res: Object.keys(schema.definitions.res.properties), 184 | }; 185 | return (info) => { 186 | // enforce get properties from koa ctx.request/responseo 187 | // in koa, Request.toJSON only return ['method', 'url', 'header'] 188 | // https://github.com/koajs/koa/blob/master/lib/request.js#L708 189 | PREFIXS.forEach((prefix) => { 190 | const prefixCopy = {}; 191 | keys[prefix].forEach((key) => { 192 | prefixCopy[key] = info[prefix][key]; 193 | }); 194 | info[prefix] = prefixCopy; 195 | }); 196 | 197 | const infoMsg = stringify(info); 198 | // rewrite info object as omit function 199 | Object.assign(info, JSON.parse(infoMsg)); 200 | return infoMsg; 201 | }; 202 | }; 203 | 204 | module.exports = { 205 | defaultSchemas, 206 | generateSchema, 207 | generateFormat, 208 | asJsonSchemaPath, 209 | }; 210 | -------------------------------------------------------------------------------- /test/logger.stringify.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | const Koa = require('koa'); 3 | const bodyParser = require('koa-bodyparser'); 4 | const Transport = require('winston-transport'); 5 | const request = require('supertest'); 6 | // @ts-ignore 7 | const { MESSAGE } = require('triple-beam'); 8 | const _ = require('lodash'); 9 | 10 | const { logger } = require('../index'); 11 | 12 | class CustomTransport extends Transport { 13 | constructor(infos = []) { 14 | super(); 15 | this.infos = infos; 16 | } 17 | 18 | log(info, callback) { 19 | this.infos.push(info); 20 | callback(); 21 | } 22 | } 23 | const defaultHandler = (ctx) => { 24 | ctx.body = { ding: 'ding' }; 25 | }; 26 | const useLogger = (payload, handler = defaultHandler) => { 27 | const app = new Koa(); 28 | app.use(bodyParser()); 29 | // @ts-ignore 30 | app.use(logger(payload)); 31 | app.use(handler); 32 | 33 | return app.listen(); 34 | }; 35 | 36 | test('parse message, default info obj', async (t) => { 37 | const infos = []; 38 | const app = useLogger({ transports: [new CustomTransport(infos)] }); 39 | await request(app) 40 | .post('/test') 41 | .set('host', 'ding.ding') 42 | .set('user-agent', 'ding.ding.ding') 43 | .expect(200); 44 | 45 | const [info] = infos; 46 | const infoObj = JSON.parse(info[MESSAGE]); 47 | 48 | t.true(Date.now() - infoObj.started_at > infoObj.duration); 49 | t.deepEqual(_.pick(infoObj, ['level', 'message', 'req', 'res']), { 50 | level: 'info', 51 | message: 'HTTP POST /test', 52 | req: { 53 | url: '/test', 54 | method: 'POST', 55 | header: { 56 | 'accept-encoding': 'gzip, deflate', 57 | 'user-agent': 'ding.ding.ding', 58 | connection: 'close', 59 | host: 'ding.ding', 60 | 'content-length': '0', 61 | }, 62 | href: 'http://ding.ding/test', 63 | length: 0, 64 | query: {}, 65 | }, 66 | res: { 67 | status: '200', 68 | header: { 69 | 'content-type': 'application/json; charset=utf-8', 70 | 'content-length': '15', 71 | }, 72 | }, 73 | }); 74 | }); 75 | 76 | test('parse message, omit req.body.password', async (t) => { 77 | const infos = []; 78 | const app = useLogger({ 79 | transports: [new CustomTransport(infos)], 80 | reqSelect: ['body'], 81 | reqUnselect: ['body.password'], 82 | }); 83 | await request(app) 84 | .post('/test') 85 | .send({ username: 'dingding', password: 'dingdingding' }) 86 | .set('host', 'ding.ding') 87 | .set('user-agent', 'ding.ding.ding') 88 | .expect(200); 89 | 90 | const [info] = infos; 91 | const infoObj = JSON.parse(info[MESSAGE]); 92 | 93 | t.true(Date.now() - infoObj.started_at > infoObj.duration); 94 | t.deepEqual(_.pick(infoObj, ['level', 'message', 'req', 'res']), { 95 | level: 'info', 96 | message: 'HTTP POST /test', 97 | req: { 98 | header: { 99 | host: 'ding.ding', 100 | 'accept-encoding': 'gzip, deflate', 101 | 'user-agent': 'ding.ding.ding', 102 | 'content-type': 'application/json', 103 | 'content-length': '49', 104 | connection: 'close', 105 | }, 106 | url: '/test', 107 | method: 'POST', 108 | href: 'http://ding.ding/test', 109 | query: {}, 110 | length: 49, 111 | body: { password: null, username: 'dingding' }, 112 | }, 113 | res: { 114 | header: { 115 | 'content-type': 'application/json; charset=utf-8', 116 | 'content-length': '15', 117 | }, 118 | status: '200', 119 | }, 120 | }); 121 | }); 122 | 123 | test('parse message, only log req.query', async (t) => { 124 | const infos = []; 125 | const app = useLogger({ 126 | transports: [new CustomTransport(infos)], 127 | reqKeys: ['query'], 128 | }); 129 | await request(app) 130 | .get('/test') 131 | .query({ ding: 'ding' }) 132 | .expect(200); 133 | 134 | const [info] = infos; 135 | const infoObj = JSON.parse(info[MESSAGE]); 136 | 137 | t.true(Date.now() - infoObj.started_at > infoObj.duration); 138 | t.deepEqual(_.pick(infoObj, ['level', 'message', 'req', 'res']), { 139 | level: 'info', 140 | message: 'HTTP GET /test?ding=ding', 141 | req: { 142 | query: { ding: 'ding' }, 143 | }, 144 | res: { 145 | header: { 146 | 'content-type': 'application/json; charset=utf-8', 147 | 'content-length': '15', 148 | }, 149 | status: '200', 150 | }, 151 | }); 152 | }); 153 | 154 | test('parse message, only log res.body', async (t) => { 155 | const infos = []; 156 | const app = useLogger({ 157 | transports: [new CustomTransport(infos)], 158 | reqKeys: [], 159 | resKeys: ['body'], 160 | }); 161 | await request(app) 162 | .get('/test') 163 | .expect(200); 164 | 165 | const [info] = infos; 166 | const infoObj = JSON.parse(info[MESSAGE]); 167 | 168 | t.true(Date.now() - infoObj.started_at > infoObj.duration); 169 | t.deepEqual(_.pick(infoObj, ['level', 'message', 'req', 'res']), { 170 | level: 'info', 171 | message: 'HTTP GET /test', 172 | req: {}, 173 | res: { body: { ding: 'ding' } }, 174 | }); 175 | }); 176 | 177 | test('parse message, omit res.body.token', async (t) => { 178 | const infos = []; 179 | const app = useLogger( 180 | { 181 | transports: [new CustomTransport(infos)], 182 | reqKeys: [], 183 | resKeys: ['body'], 184 | resUnselect: ['body.token'], 185 | }, 186 | (ctx) => { 187 | ctx.body = { token: 'dingtoken', name: 'ding' }; 188 | }, 189 | ); 190 | await request(app) 191 | .put('/testtest') 192 | .expect(200); 193 | 194 | const [info] = infos; 195 | const infoObj = JSON.parse(info[MESSAGE]); 196 | 197 | t.true(Date.now() - infoObj.started_at >= infoObj.duration); 198 | t.deepEqual(_.pick(infoObj, ['level', 'message', 'req', 'res']), { 199 | level: 'info', 200 | message: 'HTTP PUT /testtest', 201 | req: {}, 202 | res: { body: { name: 'ding', token: null } }, 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /test/logger.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | const _ = require('lodash'); 3 | const Koa = require('koa'); 4 | const Transport = require('winston-transport'); 5 | const request = require('supertest'); 6 | 7 | const { logger, getLogLevel } = require('../index'); 8 | 9 | class CustomTransport extends Transport { 10 | constructor(msgs = []) { 11 | super(); 12 | this.msgs = msgs; 13 | } 14 | 15 | log(info, callback) { 16 | this.msgs.push(info); 17 | callback(null, true); 18 | } 19 | } 20 | const defaultHandler = (ctx) => { 21 | ctx.body = 'dingding'; 22 | }; 23 | const useLogger = (payload, handler = defaultHandler) => { 24 | const app = new Koa(); 25 | // @ts-ignore 26 | app.use(logger(payload)); 27 | app.use(handler); 28 | 29 | return app.listen(); 30 | }; 31 | 32 | test('log level should return ding as default level', async (t) => { 33 | const level = getLogLevel(undefined, 'ding'); 34 | t.is(level, 'ding'); 35 | }); 36 | 37 | test('log level should return warn as default level', async (t) => { 38 | const level = getLogLevel(undefined, 'warn'); 39 | t.is(level, 'warn'); 40 | }); 41 | 42 | test('log level should return warn', async (t) => { 43 | const level = getLogLevel(400); 44 | t.is(level, 'warn'); 45 | }); 46 | 47 | test('log level should return error', async (t) => { 48 | const level = getLogLevel(500); 49 | t.is(level, 'error'); 50 | }); 51 | 52 | test('log level should use defautl value', async (t) => { 53 | const level = getLogLevel(); 54 | t.is(level, 'info'); 55 | }); 56 | 57 | test('log level should be warn when status=400', async (t) => { 58 | const msgs = []; 59 | const warnHandler = (ctx) => { 60 | ctx.status = 400; 61 | }; 62 | const app = useLogger( 63 | { 64 | transports: [new CustomTransport(msgs)], 65 | }, 66 | warnHandler, 67 | ); 68 | await request(app) 69 | .post('/test') 70 | .expect(400); 71 | 72 | const [{ level }] = msgs; 73 | t.is(level, 'warn'); 74 | }); 75 | 76 | test('cookies should still exists', async (t) => { 77 | const msgs = []; 78 | let cookie = ''; 79 | const cookieHandler = (ctx) => { 80 | const { 81 | header: { cookie: requestCookie }, 82 | } = ctx; 83 | cookie = requestCookie; 84 | ctx.body = 'dingding'; 85 | }; 86 | const app = useLogger( 87 | { 88 | transports: [new CustomTransport(msgs)], 89 | }, 90 | cookieHandler, 91 | ); 92 | await request(app) 93 | .post('/test') 94 | .set('Cookie', 'ding=ding') 95 | .expect(200); 96 | 97 | const [{ level }] = msgs; 98 | t.is(level, 'info'); 99 | t.is(cookie, 'ding=ding', 'should be request cookie'); 100 | }); 101 | 102 | test('successful required logger', (t) => { 103 | t.truthy(logger); 104 | }); 105 | 106 | test('successful create default middleware', async (t) => { 107 | const app = useLogger(); 108 | const { text } = await request(app) 109 | .get('/') 110 | .expect(200); 111 | 112 | t.is(text, 'dingding', 'should get dingding as text'); 113 | }); 114 | 115 | test('successful use custom transport', async (t) => { 116 | const msgs = []; 117 | const app = useLogger({ 118 | transports: [new CustomTransport(msgs)], 119 | }); 120 | await request(app) 121 | .get('/') 122 | .expect(200); 123 | 124 | t.is(msgs.length, 1, 'should record 1 msg'); 125 | 126 | const [info] = msgs; 127 | t.is(info.level, 'info'); 128 | t.is(info.message, 'HTTP GET /'); 129 | const meta = _.omit(info, ['level', 'message']); 130 | t.is(Object.keys(meta).length, 4); 131 | ['req', 'res', 'duration', 'started_at'].forEach((key) => { 132 | t.true(Object.keys(meta).includes(key)); 133 | }); 134 | }); 135 | 136 | test('successful display correct url in msg', async (t) => { 137 | const msgs = []; 138 | const app = useLogger({ 139 | transports: [new CustomTransport(msgs)], 140 | }); 141 | await request(app) 142 | .post('/test') 143 | .expect(200); 144 | 145 | const [{ message }] = msgs; 146 | t.is(message, 'HTTP POST /test'); 147 | }); 148 | 149 | test('should use input level as default level', async (t) => { 150 | const msgs = []; 151 | const app = useLogger({ 152 | level: 'error', 153 | transports: [new CustomTransport(msgs)], 154 | }); 155 | await request(app) 156 | .post('/test') 157 | .expect(200); 158 | 159 | const [{ level }] = msgs; 160 | t.is(level, 'error'); 161 | }); 162 | 163 | test('should still record logger when error have been throw out', async (t) => { 164 | const msgs = []; 165 | const errorHandler = (ctx) => { 166 | ctx.throw('test'); 167 | }; 168 | const app = useLogger( 169 | { 170 | transports: [new CustomTransport(msgs)], 171 | }, 172 | errorHandler, 173 | ); 174 | await request(app) 175 | .post('/test') 176 | .expect(500); 177 | 178 | const [{ level }] = msgs; 179 | t.is(level, 'error'); 180 | }); 181 | -------------------------------------------------------------------------------- /test/stringify_schema.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | 3 | const { generateSchema } = require('../stringify_schema'); 4 | 5 | test('default schema on definitions', (t) => { 6 | const schema = generateSchema({}); 7 | t.deepEqual(schema.definitions, { 8 | req: { 9 | type: 'object', 10 | properties: { 11 | header: { 12 | type: 'object', 13 | additionalProperties: { type: 'string' }, 14 | properties: { cookie: { type: 'null' } }, 15 | }, 16 | url: { type: 'string' }, 17 | method: { type: 'string' }, 18 | httpVersion: { type: 'string' }, 19 | href: { type: 'string' }, 20 | query: { type: 'object', additionalProperties: { type: 'string' } }, 21 | length: { type: 'integer' }, 22 | }, 23 | }, 24 | res: { 25 | type: 'object', 26 | properties: { 27 | header: { type: 'object', additionalProperties: { type: 'string' } }, 28 | status: { type: 'string' }, 29 | }, 30 | }, 31 | }); 32 | }); 33 | 34 | test('unselect res.body.success should not work', (t) => { 35 | const schema = generateSchema({ resUnselect: ['body.success'] }); 36 | t.deepEqual(schema.definitions, { 37 | req: { 38 | type: 'object', 39 | properties: { 40 | header: { 41 | type: 'object', 42 | additionalProperties: { type: 'string' }, 43 | properties: { cookie: { type: 'null' } }, 44 | }, 45 | url: { type: 'string' }, 46 | method: { type: 'string' }, 47 | httpVersion: { type: 'string' }, 48 | href: { type: 'string' }, 49 | query: { type: 'object', additionalProperties: { type: 'string' } }, 50 | length: { type: 'integer' }, 51 | }, 52 | }, 53 | res: { 54 | type: 'object', 55 | properties: { 56 | header: { type: 'object', additionalProperties: { type: 'string' } }, 57 | status: { type: 'string' }, 58 | }, 59 | }, 60 | }); 61 | }); 62 | 63 | test('unselect res.status should not work', (t) => { 64 | const schema = generateSchema({ 65 | reqKeys: [], 66 | resKeys: [], 67 | resUnselect: ['status'], 68 | }); 69 | t.deepEqual(schema.definitions, { 70 | req: { type: 'object', properties: {} }, 71 | res: { type: 'object', properties: {} }, 72 | }); 73 | }); 74 | --------------------------------------------------------------------------------