├── .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 | [](https://www.npmjs.com/package/koa2-winston)
4 | [](https://www.npmjs.com/package/koa2-winston)
5 | [](https://www.npmjs.com/package/koa2-winston)
6 | [](https://www.npmjs.com/package/koa2-winston)
7 | [](https://www.npmjs.com/package/koa2-winston)
8 | [](https://www.npmjs.com/package/koa2-winston)
9 | [](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 | 
4 | [](https://www.npmjs.com/package/koa2-winston)
5 | [](https://www.npmjs.com/package/koa2-winston)
6 | [](https://www.npmjs.com/package/koa2-winston)
7 | [](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 |
--------------------------------------------------------------------------------