├── packages ├── moleculer-jaeger │ ├── .gitignore │ ├── examples │ │ ├── index.js │ │ ├── api │ │ │ └── index.js │ │ └── simple │ │ │ └── index.js │ ├── CHANGELOG.md │ ├── index.js │ ├── LICENSE │ ├── .eslintrc.js │ ├── package.json │ ├── README.md │ ├── src │ │ └── index.js │ └── test │ │ └── unit │ │ └── index.spec.js ├── moleculer-prometheus │ ├── .gitignore │ ├── examples │ │ ├── index.js │ │ └── simple │ │ │ └── index.js │ ├── CHANGELOG.md │ ├── index.js │ ├── LICENSE │ ├── .eslintrc.js │ ├── package.json │ ├── README.md │ ├── src │ │ └── index.js │ ├── test │ │ └── unit │ │ │ └── index.spec.js │ └── grafana-dashboards │ │ └── Moleculer Prometheus.json ├── moleculer-console-tracer │ ├── .gitignore │ ├── examples │ │ ├── index.js │ │ ├── .eslintrc.js │ │ └── simple │ │ │ └── index.js │ ├── CHANGELOG.md │ ├── index.js │ ├── LICENSE │ ├── .eslintrc.js │ ├── package.json │ ├── README.md │ ├── test │ │ └── unit │ │ │ └── index.spec.js │ └── src │ │ └── index.js └── moleculer-zipkin │ ├── .gitignore │ ├── examples │ ├── index.js │ ├── .eslintrc.js │ └── simple │ │ └── index.js │ ├── CHANGELOG.md │ ├── index.js │ ├── LICENSE │ ├── .eslintrc.js │ ├── package.json │ ├── README.md │ ├── src │ └── index.js │ └── test │ └── unit │ └── index.spec.js ├── dev.js ├── .travis.yml ├── lerna.json ├── .editorconfig ├── .codeclimate.yml ├── LICENSE ├── readme-generator.js ├── .gitignore ├── .vscode └── launch.json ├── .eslintrc.js ├── package.json └── README.md /packages/moleculer-jaeger/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-prometheus/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log -------------------------------------------------------------------------------- /packages/moleculer-zipkin/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | stats.json 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-zipkin/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-prometheus/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); -------------------------------------------------------------------------------- /packages/moleculer-jaeger/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.2.0 (2018-07-10) 3 | 4 | ## Changes 5 | - support Moleculer v0.13.x 6 | 7 | -------------------------------------------------- 8 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.2.0 (2018-07-10) 3 | 4 | ## Changes 5 | - support Moleculer v0.13.x 6 | 7 | -------------------------------------------------- 8 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.2.0 (2018-07-10) 3 | 4 | ## Changes 5 | - support Moleculer v0.13.x 6 | 7 | -------------------------------------------------- 8 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 0.2.0 (2018-07-10) 3 | 4 | ## Changes 5 | - support Moleculer v0.13.x 6 | 7 | -------------------------------------------------- 8 | -------------------------------------------------------------------------------- /dev.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2]; 4 | const example = process.argv[3] || "simple"; 5 | process.argv.splice(2, 2); 6 | 7 | require("./packages/" + moduleName + "/examples/" + example); -------------------------------------------------------------------------------- /packages/moleculer-jaeger/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-jaeger 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); -------------------------------------------------------------------------------- /packages/moleculer-zipkin/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-zipkin 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | node_js: 6 | - "12" 7 | - "10" 8 | - "8" 9 | script: 10 | - npm run setup && npm test 11 | after_success: 12 | - npm run coverall 13 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-prometheus 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.5", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "independent", 7 | "commands": { 8 | "publish": { 9 | "ignore": [ 10 | "*.md" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-console-tracer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = require("./src"); -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = tab 11 | indent_size = 4 12 | space_after_anon_function = true 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | indent_style = space 22 | indent_size = 4 23 | trim_trailing_whitespace = false 24 | 25 | [{package,bower}.json] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.js] 30 | quote_type = "double" 31 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | checks: 4 | argument-count: 5 | enabled: false 6 | complex-logic: 7 | enabled: false 8 | file-lines: 9 | enabled: false 10 | method-complexity: 11 | enabled: false 12 | method-count: 13 | enabled: false 14 | method-lines: 15 | enabled: false 16 | nested-control-flow: 17 | enabled: false 18 | return-statements: 19 | enabled: false 20 | similar-code: 21 | enabled: false 22 | identical-code: 23 | enabled: false 24 | 25 | plugins: 26 | duplication: 27 | enabled: false 28 | config: 29 | languages: 30 | - javascript 31 | eslint: 32 | enabled: true 33 | channel: "eslint-4" 34 | fixme: 35 | enabled: true 36 | 37 | exclude_paths: 38 | - test/ 39 | - benchmark/ 40 | - examples/ 41 | - typings/ 42 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/examples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | ], 10 | "parserOptions": { 11 | "sourceType": "module", 12 | "ecmaVersion": 2017, 13 | "ecmaFeatures": { 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "rules": { 18 | "indent": [ 19 | "warn", 20 | "tab", 21 | { SwitchCase: 1 } 22 | ], 23 | "quotes": [ 24 | "warn", 25 | "double" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-var": [ 32 | "warn" 33 | ], 34 | "no-console": [ 35 | "off" 36 | ], 37 | "no-unused-vars": [ 38 | "off" 39 | ], 40 | "no-trailing-spaces": [ 41 | "error" 42 | ], 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/examples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | ], 10 | "parserOptions": { 11 | "sourceType": "module", 12 | "ecmaVersion": 2017, 13 | "ecmaFeatures": { 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "rules": { 18 | "indent": [ 19 | "warn", 20 | "tab", 21 | { SwitchCase: 1 } 22 | ], 23 | "quotes": [ 24 | "warn", 25 | "double" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-var": [ 32 | "warn" 33 | ], 34 | "no-console": [ 35 | "off" 36 | ], 37 | "no-unused-vars": [ 38 | "off" 39 | ], 40 | "no-trailing-spaces": [ 41 | "error" 42 | ], 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MoleculerJS 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-generator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const glob = require("glob"); 5 | const markdownMagic = require("markdown-magic"); 6 | 7 | const readmePath = path.join(__dirname, "README.md"); 8 | console.log(readmePath); 9 | markdownMagic(readmePath, { 10 | 11 | transforms: { 12 | RENDERLIST: function(content, opts) { 13 | const folders = glob.sync(path.join(opts.folder, "*")); 14 | //console.log(folders); 15 | if (folders.length == 0) return " "; 16 | 17 | let table = [ 18 | "## " + opts.title, 19 | "| Name | Version | Description |", 20 | "| ---- | ------- | ----------- |" 21 | 22 | ]; 23 | folders.forEach(folder => { 24 | let name = path.basename(folder); 25 | let pkg = require(path.resolve(folder, "package.json")); 26 | 27 | let line = `| [${name}](/${opts.folder}/${name}#readme) | [![NPM version](https://img.shields.io/npm/v/${name}.svg)](https://www.npmjs.com/package/${name}) | ${pkg.description} |`; 28 | 29 | table.push(line); 30 | }); 31 | 32 | return table.join("\n"); 33 | } 34 | }, 35 | DEBUG: false 36 | 37 | }, () => { 38 | console.log("README.md generated!"); 39 | }); -------------------------------------------------------------------------------- /packages/moleculer-prometheus/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MoleculerJS 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 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MoleculerJS 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | lerna-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | **/.idea 61 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch dev", 11 | "program": "${workspaceRoot}\\dev", 12 | "cwd": "${workspaceRoot}", 13 | "args": [ 14 | "moleculer-console-tracer", 15 | "simple" 16 | ] 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Launch readme gen", 22 | "program": "${workspaceRoot}/node_modules/moleculer-docgen/index.js", 23 | "args": [ 24 | "-t", 25 | "packages\\moleculer-zipkin\\README.test.md", 26 | "packages\\moleculer-zipkin\\src\\index.js" 27 | ], 28 | "stopOnEntry": true 29 | 30 | }, 31 | { 32 | "type": "node", 33 | "request": "launch", 34 | "name": "Jest", 35 | "program": "${workspaceRoot}\\node_modules\\jest-cli\\bin\\jest.js", 36 | "args": ["--runInBand"], 37 | "cwd": "${workspaceRoot}\\packages\\moleculer-prometheus", 38 | "runtimeArgs": [ 39 | "--nolazy" 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jquery": false, 7 | "jest": true, 8 | "jasmine": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:security/recommended" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaVersion": "2017" 17 | }, 18 | "plugins": [ 19 | "promise", 20 | "security" 21 | ], 22 | "rules": { 23 | "indent": [ 24 | "warn", 25 | "tab", 26 | { SwitchCase: 1 } 27 | ], 28 | "quotes": [ 29 | "warn", 30 | "double" 31 | ], 32 | "semi": [ 33 | "error", 34 | "always" 35 | ], 36 | "no-var": [ 37 | "error" 38 | ], 39 | "no-console": [ 40 | "error" 41 | ], 42 | "no-unused-vars": [ 43 | "warn" 44 | ], 45 | "no-trailing-spaces": [ 46 | "error" 47 | ], 48 | "no-alert": 0, 49 | "no-shadow": 0, 50 | "security/detect-object-injection": ["off"], 51 | "security/detect-non-literal-require": ["off"], 52 | "security/detect-non-literal-fs-filename": ["off"], 53 | "no-process-exit": ["off"], 54 | "node/no-unpublished-require": 0, 55 | "space-before-function-paren": [ 56 | "warn", 57 | { 58 | "anonymous": "never", 59 | "named": "never", 60 | "asyncArrow": "always" 61 | } 62 | ], 63 | "object-curly-spacing": [ 64 | "warn", 65 | "always" 66 | ] 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jquery": false, 7 | "jest": true, 8 | "jasmine": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:security/recommended" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaVersion": "2017" 17 | }, 18 | "plugins": [ 19 | "promise", 20 | "security" 21 | ], 22 | "rules": { 23 | "indent": [ 24 | "warn", 25 | "tab", 26 | { SwitchCase: 1 } 27 | ], 28 | "quotes": [ 29 | "warn", 30 | "double" 31 | ], 32 | "semi": [ 33 | "error", 34 | "always" 35 | ], 36 | "no-var": [ 37 | "error" 38 | ], 39 | "no-console": [ 40 | "error" 41 | ], 42 | "no-unused-vars": [ 43 | "warn" 44 | ], 45 | "no-trailing-spaces": [ 46 | "error" 47 | ], 48 | "no-alert": 0, 49 | "no-shadow": 0, 50 | "security/detect-object-injection": ["off"], 51 | "security/detect-non-literal-require": ["off"], 52 | "security/detect-non-literal-fs-filename": ["off"], 53 | "no-process-exit": ["off"], 54 | "node/no-unpublished-require": 0, 55 | "space-before-function-paren": [ 56 | "warn", 57 | { 58 | "anonymous": "never", 59 | "named": "never", 60 | "asyncArrow": "always" 61 | } 62 | ], 63 | "object-curly-spacing": [ 64 | "warn", 65 | "always" 66 | ] 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jquery": false, 7 | "jest": true, 8 | "jasmine": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:security/recommended" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaVersion": "2017" 17 | }, 18 | "plugins": [ 19 | "promise", 20 | "security" 21 | ], 22 | "rules": { 23 | "indent": [ 24 | "warn", 25 | "tab", 26 | { SwitchCase: 1 } 27 | ], 28 | "quotes": [ 29 | "warn", 30 | "double" 31 | ], 32 | "semi": [ 33 | "error", 34 | "always" 35 | ], 36 | "no-var": [ 37 | "error" 38 | ], 39 | "no-console": [ 40 | "error" 41 | ], 42 | "no-unused-vars": [ 43 | "warn" 44 | ], 45 | "no-trailing-spaces": [ 46 | "error" 47 | ], 48 | "no-alert": 0, 49 | "no-shadow": 0, 50 | "security/detect-object-injection": ["off"], 51 | "security/detect-non-literal-require": ["off"], 52 | "security/detect-non-literal-fs-filename": ["off"], 53 | "no-process-exit": ["off"], 54 | "node/no-unpublished-require": 0, 55 | "space-before-function-paren": [ 56 | "warn", 57 | { 58 | "anonymous": "never", 59 | "named": "never", 60 | "asyncArrow": "always" 61 | } 62 | ], 63 | "object-curly-spacing": [ 64 | "warn", 65 | "always" 66 | ] 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jquery": false, 7 | "jest": true, 8 | "jasmine": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:security/recommended" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaVersion": "2017" 17 | }, 18 | "plugins": [ 19 | "promise", 20 | "security" 21 | ], 22 | "rules": { 23 | "indent": [ 24 | "warn", 25 | "tab", 26 | { SwitchCase: 1 } 27 | ], 28 | "quotes": [ 29 | "warn", 30 | "double" 31 | ], 32 | "semi": [ 33 | "error", 34 | "always" 35 | ], 36 | "no-var": [ 37 | "error" 38 | ], 39 | "no-console": [ 40 | "error" 41 | ], 42 | "no-unused-vars": [ 43 | "warn" 44 | ], 45 | "no-trailing-spaces": [ 46 | "error" 47 | ], 48 | "no-alert": 0, 49 | "no-shadow": 0, 50 | "security/detect-object-injection": ["off"], 51 | "security/detect-non-literal-require": ["off"], 52 | "security/detect-non-literal-fs-filename": ["off"], 53 | "no-process-exit": ["off"], 54 | "node/no-unpublished-require": 0, 55 | "space-before-function-paren": [ 56 | "warn", 57 | { 58 | "anonymous": "never", 59 | "named": "never", 60 | "asyncArrow": "always" 61 | } 62 | ], 63 | "object-curly-spacing": [ 64 | "warn", 65 | "always" 66 | ] 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jquery": false, 7 | "jest": true, 8 | "jasmine": true 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:security/recommended" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaVersion": "2017" 17 | }, 18 | "plugins": [ 19 | "promise", 20 | "security" 21 | ], 22 | "rules": { 23 | "no-control-regex": "off", 24 | "indent": [ 25 | "warn", 26 | "tab", 27 | {SwitchCase: 1} 28 | ], 29 | "quotes": [ 30 | "warn", 31 | "double" 32 | ], 33 | "semi": [ 34 | "error", 35 | "always" 36 | ], 37 | "no-var": [ 38 | "error" 39 | ], 40 | "no-console": [ 41 | "error" 42 | ], 43 | "no-unused-vars": [ 44 | "warn" 45 | ], 46 | "no-trailing-spaces": [ 47 | "error" 48 | ], 49 | "no-alert": 0, 50 | "no-shadow": 0, 51 | "security/detect-object-injection": ["off"], 52 | "security/detect-non-literal-require": ["off"], 53 | "security/detect-non-literal-fs-filename": ["off"], 54 | "no-process-exit": ["off"], 55 | "node/no-unpublished-require": 0, 56 | "space-before-function-paren": [ 57 | "warn", 58 | { 59 | "anonymous": "never", 60 | "named": "never", 61 | "asyncArrow": "always" 62 | } 63 | ], 64 | "object-curly-spacing": [ 65 | "warn", 66 | "always" 67 | ] 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const { MoleculerError } = require("moleculer").Errors; 5 | const PromService = require("../../index"); 6 | 7 | // Create broker 8 | const broker = new ServiceBroker({ 9 | logger: console, 10 | logLevel: "info", 11 | metrics: true, 12 | sampleCount: 1 13 | }); 14 | 15 | // Load Prometheus service 16 | broker.createService({ 17 | mixins: [PromService], 18 | settings: { 19 | metrics: { 20 | "custom_value": { type: "Gauge", help: "Moleculer Prometheus custom metric" }, 21 | } 22 | } 23 | }); 24 | 25 | broker.createService({ 26 | name: "posts", 27 | actions: { 28 | find: { 29 | handler() { 30 | if (Math.random() > 0.95) 31 | return this.Promise.reject(new MoleculerError("Something went wrong")); 32 | 33 | return this.Promise.resolve([]).delay(Math.round(Math.random() * 200)); 34 | } 35 | } 36 | } 37 | }); 38 | 39 | // Start server 40 | broker.start().then(() => { 41 | broker.repl(); 42 | 43 | setInterval(() => { 44 | // Call action 45 | broker.call("posts.find") 46 | .then(() => broker.logger.info("Request done.")) 47 | .catch(err => broker.logger.info("Request error: ", err.message)); 48 | }, 100); 49 | 50 | setInterval(() => { 51 | broker.broadcast("metrics.update", { 52 | name: "custom_value", 53 | method: "set", 54 | value: Math.round(Math.random() * 100) 55 | }); 56 | }, 1000); 57 | }); 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-metrics-addons", 3 | "description": "Official monitoring & metrics addons for Moleculer framework", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "setup": "npm install && lerna bootstrap", 7 | "clean": "lerna clean", 8 | "dev": "nodemon dev.js", 9 | "demo": "node dev.js", 10 | "test": "jest --coverage", 11 | "ci": "jest --watch", 12 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 13 | "init": "cd packages && moleculer init addon", 14 | "audit": "lerna exec --concurrency 1 npm audit fix", 15 | "deps": "lerna exec --concurrency 1 npm run deps", 16 | "release": "lerna publish", 17 | "readme": "node readme-generator.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/moleculerjs/moleculer-metrics.git" 22 | }, 23 | "keywords": [], 24 | "author": "MoleculerJS", 25 | "license": "MIT", 26 | "homepage": "https://github.com/moleculerjs/moleculer-metrics#readme", 27 | "devDependencies": { 28 | "coveralls": "3.0.6", 29 | "eslint": "5.16.0", 30 | "eslint-plugin-node": "9.1.0", 31 | "eslint-plugin-promise": "4.2.1", 32 | "eslint-plugin-security": "1.4.0", 33 | "glob": "7.1.4", 34 | "jest": "24.9.0", 35 | "jest-cli": "24.9.0", 36 | "lerna": "3.16.4", 37 | "markdown-magic": "0.1.25", 38 | "nodemon": "1.19.2" 39 | }, 40 | "dependencies": { 41 | "moleculer-cli": "^0.7.0" 42 | }, 43 | "jest": { 44 | "testEnvironment": "node", 45 | "coveragePathIgnorePatterns": [ 46 | "/node_modules/", 47 | "/test/services/" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-prometheus", 3 | "version": "0.2.5", 4 | "description": "Moleculer metrics module for Prometheus.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "deps": "npm-check -u", 12 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 13 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 14 | }, 15 | "keywords": [ 16 | "microservice", 17 | "moleculer" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git@github.com:moleculerjs/moleculer-addons.git" 22 | }, 23 | "homepage": "https://github.com/moleculerjs/moleculer-addons/tree/master/packages/moleculer-prometheus#readme", 24 | "author": "MoleculerJS", 25 | "license": "MIT", 26 | "peerDependencies": { 27 | "moleculer": ">=0.12.0 || >=0.13.0" 28 | }, 29 | "devDependencies": { 30 | "coveralls": "3.0.2", 31 | "eslint": "5.16.0", 32 | "eslint-plugin-node": "9.1.0", 33 | "eslint-plugin-promise": "4.2.1", 34 | "eslint-plugin-security": "1.4.0", 35 | "jest": "24.8.0", 36 | "jest-cli": "24.8.0", 37 | "moleculer": "0.13.9", 38 | "moleculer-docgen": "0.2.2", 39 | "nodemon": "1.17.5", 40 | "npm-check": "^5.9.2" 41 | }, 42 | "jest": { 43 | "testEnvironment": "node", 44 | "coveragePathIgnorePatterns": [ 45 | "/node_modules/", 46 | "/test/services/" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">= 6.x.x" 51 | }, 52 | "dependencies": { 53 | "polka": "0.4.0", 54 | "prom-client": "11.1.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-zipkin", 3 | "version": "0.2.3", 4 | "description": "Moleculer metrics module for Zipkin.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "deps": "npm-check -u", 12 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 13 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 14 | }, 15 | "keywords": [ 16 | "microservice", 17 | "moleculer" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git@github.com:moleculerjs/moleculer-addons.git" 22 | }, 23 | "homepage": "https://github.com/moleculerjs/moleculer-addons/tree/master/packages/moleculer-zipkin#readme", 24 | "author": "MoleculerJS", 25 | "license": "MIT", 26 | "peerDependencies": { 27 | "moleculer": ">=0.12.0 || >=0.13.0" 28 | }, 29 | "devDependencies": { 30 | "benchmarkify": "2.1.2", 31 | "coveralls": "3.0.2", 32 | "eslint": "5.16.0", 33 | "eslint-plugin-node": "9.1.0", 34 | "eslint-plugin-promise": "4.2.1", 35 | "eslint-plugin-security": "1.4.0", 36 | "jest": "24.8.0", 37 | "jest-cli": "24.8.0", 38 | "lolex": "^4.2.0", 39 | "moleculer": "0.13.9", 40 | "moleculer-docgen": "0.2.2", 41 | "moleculer-web": "^0.9.1", 42 | "nodemon": "1.19.1", 43 | "npm-check": "^5.9.2" 44 | }, 45 | "jest": { 46 | "testEnvironment": "node", 47 | "coveragePathIgnorePatterns": [ 48 | "/node_modules/", 49 | "/test/services/" 50 | ] 51 | }, 52 | "engines": { 53 | "node": ">= 6.x.x" 54 | }, 55 | "dependencies": { 56 | "axios": "0.19.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-jaeger", 3 | "version": "0.2.3", 4 | "description": "Moleculer metrics module for Jaeger.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "deps": "npm-check -u", 12 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 13 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 14 | }, 15 | "keywords": [ 16 | "microservice", 17 | "moleculer" 18 | ], 19 | "pre-commit": [ 20 | "lint" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:moleculerjs/moleculer-addons.git" 25 | }, 26 | "homepage": "https://github.com/moleculerjs/moleculer-addons/tree/master/packages/moleculer-jaeger#readme", 27 | "author": "MoleculerJS", 28 | "license": "MIT", 29 | "peerDependencies": { 30 | "moleculer": ">=0.12.0 || >=0.13.0" 31 | }, 32 | "devDependencies": { 33 | "coveralls": "^3.0.11", 34 | "eslint": "5.16.0", 35 | "eslint-plugin-node": "9.1.0", 36 | "eslint-plugin-promise": "4.2.1", 37 | "eslint-plugin-security": "1.4.0", 38 | "jest": "24.8.0", 39 | "jest-cli": "24.8.0", 40 | "moleculer": "0.13.9", 41 | "moleculer-docgen": "0.2.2", 42 | "moleculer-web": "^0.9.1", 43 | "nodemon": "1.19.1", 44 | "npm-check": "^5.9.2", 45 | "pre-commit": "^1.2.2" 46 | }, 47 | "jest": { 48 | "testEnvironment": "node", 49 | "coveragePathIgnorePatterns": [ 50 | "/node_modules/", 51 | "/test/services/" 52 | ] 53 | }, 54 | "engines": { 55 | "node": ">= 6.x.x" 56 | }, 57 | "dependencies": { 58 | "jaeger-client": "^3.17.2", 59 | "lodash.isfunction": "3.0.9", 60 | "node-int64": "0.4.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moleculer-console-tracer", 3 | "version": "0.2.3", 4 | "description": "Simple tracer service to print metric traces to the console.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon examples/index.js", 8 | "ci": "jest --watch", 9 | "test": "jest --coverage", 10 | "lint": "eslint --ext=.js src test", 11 | "lint:fix": "eslint --ext=.js --fix src", 12 | "deps": "npm-check -u", 13 | "readme": "node node_modules/moleculer-docgen/index.js -t README.md src/index.js", 14 | "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 15 | }, 16 | "keywords": [ 17 | "microservice", 18 | "moleculer" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:moleculerjs/moleculer-addons.git" 23 | }, 24 | "homepage": "https://github.com/moleculerjs/moleculer-addons/tree/master/packages/moleculer-console-tracer#readme", 25 | "author": "MoleculerJS", 26 | "license": "MIT", 27 | "peerDependencies": { 28 | "moleculer": ">=0.12.0 || >=0.13.0" 29 | }, 30 | "devDependencies": { 31 | "coveralls": "^3.0.11", 32 | "eslint": "5.16.0", 33 | "eslint-plugin-node": "9.1.0", 34 | "eslint-plugin-promise": "4.2.1", 35 | "eslint-plugin-security": "1.4.0", 36 | "jest": "24.8.0", 37 | "jest-cli": "24.8.0", 38 | "moleculer": "^0.13.9", 39 | "moleculer-docgen": "0.2.2", 40 | "moleculer-web": "^0.9.1", 41 | "nodemon": "1.17.5", 42 | "npm-check": "^5.9.2" 43 | }, 44 | "jest": { 45 | "testEnvironment": "node", 46 | "coveragePathIgnorePatterns": [ 47 | "/node_modules/", 48 | "/test/services/" 49 | ] 50 | }, 51 | "engines": { 52 | "node": ">= 6.x.x" 53 | }, 54 | "dependencies": { 55 | "chalk": "2.4.2", 56 | "lodash.isnan": "3.0.2", 57 | "lodash.repeat": "4.1.0", 58 | "slice-ansi": "1.0.0", 59 | "tiny-human-time": "1.2.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/examples/api/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const { MoleculerError } = require("moleculer").Errors; 5 | const JaegerService = require("../../index"); 6 | const ApiGateway = require("moleculer-web"); 7 | 8 | const THROW_ERR = true; 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug", 14 | metrics: true, 15 | sampleCount: 1 16 | }); 17 | 18 | broker.createService(ApiGateway); 19 | 20 | // Load Jaeger service 21 | broker.createService({ 22 | mixins: [JaegerService], 23 | settings: { 24 | host: "192.168.0.181" 25 | } 26 | }); 27 | 28 | const POSTS = [ 29 | { id: 1, title: "First post", content: "Content of first post", author: 2 }, 30 | { id: 2, title: "Second post", content: "Content of second post", author: 1 }, 31 | { id: 3, title: "3rd post", content: "Content of 3rd post", author: 2 }, 32 | ]; 33 | 34 | broker.createService({ 35 | name: "posts", 36 | actions: { 37 | find: { 38 | metrics: { 39 | params: true, 40 | }, 41 | handler(ctx) { 42 | const posts = POSTS.map(post => ({...post})); 43 | 44 | return this.Promise.all(posts.map(post => { 45 | return this.Promise.all([ 46 | ctx.call("users.get", { id: post.author }).then(author => post.author = author), 47 | ctx.call("votes.count", { postID: post.id }).then(votes => post.votes = votes), 48 | ]); 49 | })).then(() => posts); 50 | } 51 | } 52 | } 53 | }); 54 | 55 | const USERS = [ 56 | { id: 1, name: "John Doe" }, 57 | { id: 2, name: "Jane Doe" }, 58 | ]; 59 | 60 | broker.createService({ 61 | name: "users", 62 | actions: { 63 | get: { 64 | metrics: { 65 | params: true, 66 | }, 67 | handler(ctx) { 68 | return this.Promise.resolve() 69 | .then(() => { 70 | const user = USERS.find(user => user.id == ctx.params.id); 71 | if (user) { 72 | const res = {...user}; 73 | return ctx.call("friends.count", { userID: user.id }) 74 | .then(friends => res.friends = friends) 75 | .then(() => res); 76 | } 77 | }); 78 | } 79 | } 80 | } 81 | }); 82 | 83 | broker.createService({ 84 | name: "votes", 85 | actions: { 86 | count: { 87 | metrics: { 88 | params: ["postID"], 89 | meta: false, 90 | }, 91 | handler(ctx) { 92 | return this.Promise.resolve().delay(20).then(() => ctx.params.postID * 3); 93 | } 94 | } 95 | } 96 | }); 97 | 98 | broker.createService({ 99 | name: "friends", 100 | actions: { 101 | count: { 102 | metrics: { 103 | params: ["userID"], 104 | meta: false, 105 | }, 106 | handler(ctx) { 107 | if (THROW_ERR && ctx.params.userID == 1) 108 | throw new MoleculerError("Friends is not found!", 404, "FRIENDS_NOT_FOUND", { userID: ctx.params.userID }); 109 | 110 | return this.Promise.resolve().delay(30).then(() => ctx.params.userID * 3); 111 | } 112 | } 113 | } 114 | }); 115 | 116 | // Start server 117 | broker.start().then(() => { 118 | broker.repl(); 119 | 120 | broker.logger.info("Open http://localhost:3000/posts/find?limit=5"); 121 | }); 122 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const { MoleculerError } = require("moleculer").Errors; 5 | const JaegerService = require("../../index"); 6 | 7 | const THROW_ERR = false; 8 | 9 | // Create broker 10 | const broker = new ServiceBroker({ 11 | logger: console, 12 | logLevel: "debug", 13 | metrics: true, 14 | sampleCount: 1 15 | }); 16 | 17 | // Load Zipkin service 18 | broker.createService({ 19 | mixins: [JaegerService], 20 | settings: { 21 | host: "192.168.0.181" 22 | } 23 | }); 24 | 25 | const POSTS = [ 26 | { id: 1, title: "First post", content: "Content of first post", author: 2 }, 27 | { id: 2, title: "Second post", content: "Content of second post", author: 1 }, 28 | { id: 3, title: "3rd post", content: "Content of 3rd post", author: 2 }, 29 | ]; 30 | 31 | broker.createService({ 32 | name: "posts", 33 | actions: { 34 | find: { 35 | metrics: { 36 | params: true, 37 | }, 38 | handler(ctx) { 39 | const posts = POSTS.map(post => ({...post})); 40 | 41 | return this.Promise.all(posts.map(post => { 42 | return this.Promise.all([ 43 | ctx.call("users.get", { id: post.author }).then(author => post.author = author), 44 | ctx.call("votes.count", { postID: post.id }).then(votes => post.votes = votes), 45 | ]); 46 | })).then(() => posts); 47 | } 48 | } 49 | } 50 | }); 51 | 52 | const USERS = [ 53 | { id: 1, name: "John Doe" }, 54 | { id: 2, name: "Jane Doe" }, 55 | ]; 56 | 57 | broker.createService({ 58 | name: "users", 59 | actions: { 60 | get: { 61 | metrics: { 62 | params: true, 63 | }, 64 | handler(ctx) { 65 | return this.Promise.resolve() 66 | .then(() => { 67 | const user = USERS.find(user => user.id === ctx.params.id); 68 | if (user) { 69 | const res = {...user}; 70 | return ctx.call("friends.count", { userID: user.id }) 71 | .then(friends => res.friends = friends) 72 | .then(() => res); 73 | } 74 | }); 75 | } 76 | } 77 | } 78 | }); 79 | 80 | broker.createService({ 81 | name: "votes", 82 | actions: { 83 | count: { 84 | metrics: { 85 | params: ["postID"], 86 | meta: false, 87 | }, 88 | handler(ctx) { 89 | return this.Promise.resolve().delay(20).then(() => ctx.params.postID * 3); 90 | } 91 | } 92 | } 93 | }); 94 | 95 | broker.createService({ 96 | name: "friends", 97 | actions: { 98 | count: { 99 | metrics: { 100 | params: ["userID"], 101 | meta: false, 102 | }, 103 | handler(ctx) { 104 | if (THROW_ERR && ctx.params.userID == 1) 105 | throw new MoleculerError("Friends is not found!", 404, "FRIENDS_NOT_FOUND", { userID: ctx.params.userID }); 106 | 107 | return this.Promise.resolve().delay(30).then(() => ctx.params.userID * 3); 108 | } 109 | } 110 | } 111 | }); 112 | 113 | // Start server 114 | broker.start().then(() => { 115 | broker.repl(); 116 | 117 | // Call action 118 | broker 119 | .call("posts.find", { limit: 5 }, { meta: { loggedIn: { username: "Adam" } } }) 120 | .then(console.log) 121 | .catch(console.error); 122 | }); 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | [![Build Status](https://travis-ci.org/moleculerjs/moleculer-metrics.svg?branch=master)](https://travis-ci.org/moleculerjs/moleculer-metrics) 4 | [![Coverage Status](https://coveralls.io/repos/github/moleculerjs/moleculer-metrics/badge.svg?branch=master)](https://coveralls.io/github/moleculerjs/moleculer-metrics?branch=master) 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2e89f4d0a2cf4af992de65189ca324fb)](https://www.codacy.com/app/mereg-norbert/moleculer-metrics?utm_source=github.com&utm_medium=referral&utm_content=moleculerjs/moleculer-metrics&utm_campaign=Badge_Grade) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/a65fe24846b345e94923/maintainability)](https://codeclimate.com/github/moleculerjs/moleculer-metrics/maintainability) 7 | [![Known Vulnerabilities](https://snyk.io/test/github/moleculerjs/moleculer-metrics/badge.svg)](https://snyk.io/test/github/moleculerjs/moleculer-metrics) 8 | [![Join the chat at https://gitter.im/moleculerjs/moleculer](https://badges.gitter.im/moleculerjs/moleculer.svg)](https://gitter.im/moleculerjs/moleculer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | # Monitoring & metrics addons for Moleculer framework 11 | 12 | 13 | ## Modules 14 | | Name | Version | Description | 15 | | ---- | ------- | ----------- | 16 | | [moleculer-console-tracer](/packages/moleculer-console-tracer#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-console-tracer.svg)](https://www.npmjs.com/package/moleculer-console-tracer) | Simple tracer service to print metric traces to the console. | 17 | | [moleculer-jaeger](/packages/moleculer-jaeger#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-jaeger.svg)](https://www.npmjs.com/package/moleculer-jaeger) | Moleculer metrics module for Jaeger. | 18 | | [moleculer-prometheus](/packages/moleculer-prometheus#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-prometheus.svg)](https://www.npmjs.com/package/moleculer-prometheus) | Moleculer metrics module for Prometheus. | 19 | | [moleculer-zipkin](/packages/moleculer-zipkin#readme) | [![NPM version](https://img.shields.io/npm/v/moleculer-zipkin.svg)](https://www.npmjs.com/package/moleculer-zipkin) | Moleculer metrics module for Zipkin. | 20 | 21 | 22 | # Contribution 23 | 24 | ## Install dependencies 25 | ```bash 26 | $ npm run setup 27 | ``` 28 | 29 | ## Development 30 | **Run the `simple` example in `moleculer-zipkin` service with watching** 31 | ```bash 32 | $ npm run dev moleculer-zipkin 33 | ``` 34 | 35 | **Run the `full` example in `moleculer-zipkin` service w/o watching** 36 | ```bash 37 | $ npm run demo moleculer-zipkin full 38 | ``` 39 | 40 | ## Test 41 | ```bash 42 | $ npm test 43 | ``` 44 | 45 | ## Create a new addon 46 | ```bash 47 | $ npm run init moleculer- 48 | ``` 49 | 50 | ## Publish new releases 51 | ```bash 52 | $ npm run release 53 | ``` 54 | 55 | # License 56 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 57 | 58 | # Contact 59 | Copyright (c) 2016-2018 MoleculerJS 60 | 61 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 62 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const { MoleculerError } = require("moleculer").Errors; 5 | const ZipkinService = require("../../index"); 6 | const ApiGateway = require("moleculer-web"); 7 | 8 | const THROW_ERR = true; 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logLevel: "debug", 14 | metrics: true, 15 | sampleCount: 1 16 | }); 17 | 18 | broker.createService(ApiGateway); 19 | 20 | // Load Zipkin service 21 | broker.createService({ 22 | mixins: [ZipkinService], 23 | settings: { 24 | baseURL: process.env.ZIPKIN_URL || "http://192.168.0.181:9411", 25 | //batchTime: 0, 26 | //version: "v2", 27 | } 28 | }); 29 | 30 | const POSTS = [ 31 | { id: 1, title: "First post", content: "Content of first post", author: 2 }, 32 | { id: 2, title: "Second post", content: "Content of second post", author: 1 }, 33 | { id: 3, title: "3rd post", content: "Content of 3rd post", author: 2 }, 34 | ]; 35 | 36 | broker.createService({ 37 | name: "posts", 38 | actions: { 39 | find: { 40 | metrics: { 41 | params: true, 42 | }, 43 | handler(ctx) { 44 | const posts = POSTS.map( x=> ({...x})); 45 | 46 | return this.Promise.all(posts.map(post => { 47 | return this.Promise.all([ 48 | ctx.call("users.get", { id: post.author }).then(author => post.author = author), 49 | ctx.call("votes.count", { postID: post.id }).then(votes => post.votes = votes), 50 | ]); 51 | })).then(() => posts); 52 | } 53 | } 54 | } 55 | }); 56 | 57 | const USERS = [ 58 | { id: 1, name: "John Doe" }, 59 | { id: 2, name: "Jane Doe" }, 60 | ]; 61 | 62 | broker.createService({ 63 | name: "users", 64 | actions: { 65 | get: { 66 | metrics: { 67 | params: true, 68 | }, 69 | handler(ctx) { 70 | return this.Promise.resolve() 71 | .then(() => { 72 | const user = USERS.find(user => user.id === ctx.params.id); 73 | if (user) { 74 | const res = {...user}; 75 | return ctx.call("friends.count", { userID: user.id }) 76 | .then(friends => res.friends = friends) 77 | .then(() => res); 78 | } 79 | }); 80 | } 81 | } 82 | } 83 | }); 84 | 85 | broker.createService({ 86 | name: "votes", 87 | actions: { 88 | count: { 89 | metrics: { 90 | params: ["postID"], 91 | meta: false, 92 | }, 93 | handler(ctx) { 94 | return this.Promise.resolve().delay(20).then(() => ctx.params.postID * 3); 95 | } 96 | } 97 | } 98 | }); 99 | 100 | broker.createService({ 101 | name: "friends", 102 | actions: { 103 | count: { 104 | metrics: { 105 | params: ["userID"], 106 | meta: false, 107 | }, 108 | handler(ctx) { 109 | if (THROW_ERR && ctx.params.userID === 1) 110 | throw new MoleculerError("Friends is not found!", 404, "FRIENDS_NOT_FOUND", { userID: ctx.params.userID }); 111 | 112 | return this.Promise.resolve().delay(30).then(() => ctx.params.userID * 3); 113 | } 114 | } 115 | } 116 | }); 117 | 118 | // Start server 119 | broker.start().then(() => { 120 | broker.repl(); 121 | 122 | broker.logger.info("Open http://localhost:3000/posts/find?limit=5"); 123 | 124 | // Call action 125 | broker 126 | .call("posts.find", { limit: 5 }, { meta: { loggedIn: { username: "Adam" } } }) 127 | .then(console.log) 128 | .catch(console.error); 129 | }); 130 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const { MoleculerError } = require("moleculer").Errors; 5 | const TracingService = require("../../index"); 6 | const ApiGateway = require("moleculer-web"); 7 | 8 | const THROW_ERR = false; 9 | 10 | // Create broker 11 | const broker = new ServiceBroker({ 12 | logger: console, 13 | logFormatter: "simple", 14 | logLevel: "info", 15 | metrics: true, 16 | sampleCount: 1, 17 | cacher: true 18 | }); 19 | 20 | broker.createService(ApiGateway); 21 | 22 | // Load Console Tracing service 23 | broker.createService({ 24 | mixins: [TracingService], 25 | settings: { 26 | //width: 50, 27 | //gaugeWidth: 25 28 | } 29 | }); 30 | 31 | const POSTS = [ 32 | { id: 1, title: "First post", content: "Content of first post", author: 2 }, 33 | { id: 2, title: "Second post", content: "Content of second post", author: 1 }, 34 | { id: 3, title: "3rd post", content: "Content of 3rd post", author: 2 }, 35 | ]; 36 | 37 | broker.createService({ 38 | name: "posts", 39 | actions: { 40 | find: { 41 | metrics: { 42 | params: true, 43 | }, 44 | handler(ctx) { 45 | const posts = POSTS.map(post=> ({...post})); 46 | 47 | return this.Promise.all(posts.map(post => { 48 | return this.Promise.all([ 49 | ctx.call("users.get", { id: post.author }).then(author => post.author = author), 50 | ctx.call("votes.count", { postID: post.id }).then(votes => post.votes = votes), 51 | ]); 52 | })).then(() => posts); 53 | } 54 | } 55 | } 56 | }); 57 | 58 | const USERS = [ 59 | { id: 1, name: "John Doe" }, 60 | { id: 2, name: "Jane Doe" }, 61 | ]; 62 | 63 | broker.createService({ 64 | name: "users", 65 | actions: { 66 | get: { 67 | metrics: { 68 | params: true, 69 | }, 70 | handler(ctx) { 71 | return this.Promise.resolve() 72 | .then(() => { 73 | const user = USERS.find(user => user.id == ctx.params.id); 74 | if (user) { 75 | const res = {...user}; 76 | return ctx.call("friends.count", { userID: user.id }) 77 | .then(friends => res.friends = friends) 78 | .then(() => res); 79 | } 80 | }); 81 | } 82 | } 83 | } 84 | }); 85 | 86 | broker.createService({ 87 | name: "votes", 88 | actions: { 89 | count: { 90 | metrics: { 91 | params: ["postID"], 92 | meta: false, 93 | }, 94 | handler(ctx) { 95 | return this.Promise.resolve().delay(10).then(() => ctx.params.postID * 3); 96 | } 97 | } 98 | } 99 | }); 100 | 101 | broker.createService({ 102 | name: "friends", 103 | actions: { 104 | count: { 105 | cache: true, 106 | metrics: { 107 | params: ["userID"], 108 | meta: false, 109 | }, 110 | handler(ctx) { 111 | if (THROW_ERR && ctx.params.userID == 1) 112 | throw new MoleculerError("Friends is not found!", 404, "FRIENDS_NOT_FOUND", { userID: ctx.params.userID }); 113 | 114 | return this.Promise.resolve().delay(5).then(() => ctx.params.userID * 3); 115 | } 116 | } 117 | } 118 | }); 119 | 120 | // Start server 121 | broker.start().then(() => { 122 | broker.repl(); 123 | 124 | broker.logger.info("Open http://localhost:3000/posts/find?limit=5"); 125 | 126 | // Call action 127 | broker 128 | .call("posts.find", { limit: 5 }, { meta: { loggedIn: { username: "Adam" } } }) 129 | //.then(console.log) 130 | .catch(err => console.error(err.message)); 131 | }); 132 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-console-tracer [![NPM version](https://img.shields.io/npm/v/moleculer-console-tracer.svg)](https://www.npmjs.com/package/moleculer-console-tracer) 4 | 5 | Simple tracer service to print metric traces to the console. 6 | ***Do not use it in production. Just for prototyping and testing.*** 7 | 8 | ![Console Tracing screenshot](https://user-images.githubusercontent.com/306521/37969145-560ad38a-31d0-11e8-87f5-7a531181926b.png) 9 | 10 | _Don't execute multiple instances because it is not a centralized tracing solution._ 11 | 12 | # Features 13 | 14 | # Install 15 | 16 | ```bash 17 | $ npm install moleculer-console-tracer 18 | ``` 19 | 20 | # Usage 21 | 22 | ```js 23 | // services/metrics.tracer.service.js 24 | 25 | const Tracer = require("moleculer-console-tracer"); 26 | 27 | module.exports = { 28 | mixins: [Tracer], 29 | settings: { 30 | width: 100, 31 | gaugeWidth: 50 32 | } 33 | }; 34 | 35 | // moleculer.config.js 36 | module.exports = { 37 | // ... 38 | metrics: true, 39 | // ... 40 | } 41 | ``` 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | # Settings 57 | 58 | 59 | | Property | Type | Default | Description | 60 | | -------- | ---- | ------- | ----------- | 61 | | `width` | `Number` | `80` | Table width. | 62 | | `gaugeWidth` | `Number` | `40` | Gauge width. | 63 | 64 | 65 | 66 | 77 | 78 | # Actions 79 | 80 | 81 | 82 | 117 | 118 | # Methods 119 | 120 | 121 | 122 | 123 | 158 | 159 | # Test 160 | ``` 161 | $ npm test 162 | ``` 163 | 164 | In development with watching 165 | 166 | ``` 167 | $ npm run ci 168 | ``` 169 | 170 | # License 171 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 172 | 173 | # Contact 174 | Copyright (c) 2016-2018 MoleculerJS 175 | 176 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 177 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("moleculer"); 4 | const TracerService = require("../../src"); 5 | 6 | describe("Test TracerService constructor", () => { 7 | const broker = new ServiceBroker({ logger: false }); 8 | const service = broker.createService(TracerService); 9 | 10 | it("should be created", () => { 11 | expect(service).toBeDefined(); 12 | expect(service.requests).toBeInstanceOf(Object); 13 | }); 14 | 15 | }); 16 | 17 | describe("Test event listener", () => { 18 | const broker = new ServiceBroker({ logger: false }); 19 | const service = broker.createService(TracerService); 20 | service.printRequest = jest.fn(); 21 | 22 | beforeAll(() => broker.start()); 23 | afterAll(() => broker.stop()); 24 | 25 | it("should save the payload", () => { 26 | const payload = { id: "1" }; 27 | broker.emit("metrics.trace.span.start", payload); 28 | 29 | expect(service.requests["1"]).toBe(payload); 30 | }); 31 | 32 | it("should save the payload to the parent", () => { 33 | const payload = { id: "2", parent: "1" }; 34 | broker.emit("metrics.trace.span.start", payload); 35 | 36 | expect(service.requests["2"]).toBe(payload); 37 | expect(service.requests["1"].spans.length).toBe(1); 38 | expect(service.requests["1"].spans[0]).toBe("2"); 39 | }); 40 | 41 | it("should upload request", () => { 42 | service.printRequest.mockClear(); 43 | 44 | const payload = { id: "2", parent: "1", duration: 50 }; 45 | broker.emit("metrics.trace.span.finish", payload); 46 | 47 | expect(service.requests["2"]).toEqual({ 48 | id: "2", 49 | parent: "1", 50 | duration: 50, 51 | spans: [] 52 | }); 53 | 54 | expect(service.printRequest).toHaveBeenCalledTimes(0); 55 | }); 56 | 57 | it("should call printRequest", () => { 58 | service.printRequest.mockClear(); 59 | 60 | const payload = { id: "1", duration: 20 }; 61 | broker.emit("metrics.trace.span.finish", payload); 62 | 63 | expect(service.requests["1"].duration).toBe(20); 64 | 65 | expect(service.printRequest).toHaveBeenCalledTimes(1); 66 | expect(service.printRequest).toHaveBeenCalledWith("1"); 67 | }); 68 | 69 | }); 70 | 71 | describe("Test printRequest method", () => { 72 | const broker = new ServiceBroker({ logger: false }); 73 | const service = broker.createService(TracerService, { 74 | settings: { 75 | colors: false 76 | } 77 | }); 78 | 79 | const output = []; 80 | service.logger.info = jest.fn((...args) => output.push(args.join(" "))); 81 | 82 | beforeAll(() => broker.start()); 83 | afterAll(() => broker.stop()); 84 | 85 | it("should print traces", () => { 86 | 87 | service.requests["1"] = { 88 | id: "1", 89 | duration: 25.40, 90 | action: { name: "posts.find" }, 91 | startTime: 1000, 92 | endTime: 1025.4, 93 | level: 1, 94 | 95 | spans: ["2", "3"] 96 | }; 97 | 98 | service.requests["2"] = { 99 | id: "2", 100 | duration: 11.80, 101 | action: { name: "posts.votes" }, 102 | startTime: 1005, 103 | endTime: 1016.8, 104 | level: 2, 105 | 106 | spans: ["4"] 107 | }; 108 | 109 | service.requests["3"] = { 110 | id: "3", 111 | duration: 2.50, 112 | action: { name: "posts.likes" }, 113 | startTime: 1010, 114 | endTime: 1012.5, 115 | level: 2, 116 | fromCache: true, 117 | 118 | spans: [] 119 | }; 120 | 121 | service.requests["4"] = { 122 | id: "4", 123 | duration: 8.10, 124 | action: { name: "users.get" }, 125 | startTime: 1008, 126 | endTime: 1016.1, 127 | level: 3, 128 | remoteCall: true, 129 | 130 | spans: [] 131 | }; 132 | 133 | service.printRequest("1"); 134 | 135 | expect(output).toEqual([ 136 | "┌──────────────────────────────────────────────────────────────────────────────┐", 137 | "│ ID: 1 Depth: 3 Total: 4 │", 138 | "├──────────────────────────────────────────────────────────────────────────────┤", 139 | "│ posts.find 25ms [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■] │", 140 | "│ posts.votes 11ms [.......■■■■■■■■■■■■■■■■■■■..............] │", 141 | "│ users.get » 8ms [............■■■■■■■■■■■■■...............] │", 142 | "│ posts.likes * 2ms [...............■■■■.....................] │", 143 | "└──────────────────────────────────────────────────────────────────────────────┘" 144 | ]); 145 | }); 146 | 147 | }); 148 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-zipkin [![NPM version](https://img.shields.io/npm/v/moleculer-zipkin.svg)](https://www.npmjs.com/package/moleculer-zipkin) 4 | 5 | Moleculer metrics module for [Zipkin](https://zipkin.io/). 6 | 7 | ![Zipkin screenshot](https://user-images.githubusercontent.com/306521/37258287-ca6f8fac-2575-11e8-80b8-446a0423895c.png) 8 | 9 | # Features 10 | - support `v1` & `v2` API. 11 | - send spans via HTTP. 12 | - batch or single sending. 13 | 14 | # Install 15 | 16 | ```bash 17 | $ npm install moleculer-zipkin 18 | ``` 19 | 20 | # Usage 21 | 22 | ```js 23 | // services/metrics.zipkin.service.js 24 | 25 | const ZipkinService = require("moleculer-zipkin"); 26 | 27 | module.exports = { 28 | mixins: [ZipkinService], 29 | settings: { 30 | baseURL: "http://192.168.0.181:9411", 31 | version: "v2", 32 | batchTime: 1000, 33 | payloadOptions: { 34 | debug: false, 35 | shared: false 36 | } 37 | } 38 | }; 39 | 40 | // moleculer.config.js 41 | module.exports = { 42 | // ... 43 | metrics: true, 44 | // ... 45 | } 46 | ``` 47 | 48 | 49 | 50 | 51 | 58 | 59 | 60 | 61 | # Settings 62 | 63 | 64 | | Property | Type | Default | Description | 65 | | -------- | ---- | ------- | ----------- | 66 | | `baseURL` | `String` | **required** | Base URL for Zipkin server. | 67 | | `version` | `String` | **required** | Zipkin REST API version. | 68 | | `batchTime` | `Number` | **required** | Batch send time interal. Disable: 0 | 69 | | `payloadOptions` | `Object` | **required** | Additional payload options. | 70 | | `payloadOptions.debug` | `Boolean` | **required** | Set `debug` property in v2 payload. | 71 | | `payloadOptions.shared` | `Boolean` | **required** | Set `shared` property in v2 payload. | 72 | 73 | 74 | 75 | 86 | 87 | # Actions 88 | 89 | 90 | 91 | 126 | 127 | # Methods 128 | 129 | 130 | 131 | 132 | 167 | 168 | # Test 169 | ``` 170 | $ npm test 171 | ``` 172 | 173 | In development with watching 174 | 175 | ``` 176 | $ npm run ci 177 | ``` 178 | 179 | # License 180 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 181 | 182 | # Contact 183 | Copyright (c) 2016-2018 MoleculerJS 184 | 185 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 186 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-prometheus [![NPM version](https://img.shields.io/npm/v/moleculer-prometheus.svg)](https://www.npmjs.com/package/moleculer-prometheus) 4 | 5 | Moleculer metrics module for [Prometheus](https://prometheus.io/). 6 | 7 | ![Grafana screenshot](https://user-images.githubusercontent.com/306521/37919389-ff994100-3123-11e8-9da9-b771978e635f.png) 8 | 9 | # Features 10 | - collect default Node.js metrics. 11 | - measure service calls. 12 | - support custom metrics. 13 | - Grafana [dashboard example](grafana-dashboards/). 14 | 15 | # Install 16 | 17 | ```bash 18 | $ npm install moleculer-prometheus 19 | ``` 20 | 21 | # Usage 22 | 23 | ```js 24 | // services/metrics.prometheus.service.js 25 | 26 | const PromService = require("moleculer-prometheus"); 27 | 28 | module.exports = { 29 | mixins: [PromService], 30 | settings: { 31 | port: 3030, 32 | collectDefaultMetrics: true, 33 | timeout: 5 * 1000, 34 | } 35 | }; 36 | 37 | // moleculer.config.js 38 | module.exports = { 39 | // ... 40 | metrics: true, 41 | // ... 42 | } 43 | ``` 44 | 45 | ### Add custom metric 46 | 47 | ```js 48 | // services/metrics.prometheus.js 49 | const PromService = require("moleculer-prometheus"); 50 | 51 | module.exports = { 52 | mixins: [PromService], 53 | settings: { 54 | metrics: { 55 | "custom_value": { type: "Gauge", help: "Moleculer Prometheus custom metric" } 56 | } 57 | } 58 | }; 59 | ``` 60 | 61 | **Broadcast a `metrics.update` event to set the metric value** 62 | ```js 63 | broker.broadcast("metrics.update", { 64 | name: "custom_value", 65 | method: "set", 66 | value: Math.round(Math.random() * 100) 67 | }); 68 | ``` 69 | 70 | 71 | 72 | 73 | 74 | 75 | 82 | 83 | 84 | 85 | # Settings 86 | 87 | | Property | Type | Default | Description | 88 | | -------- | ---- | ------- | ----------- | 89 | | `port` | `Number` | `3030` | Exposed HTTP port. | 90 | | `collectDefaultMetrics` | `Boolean` | `true` | Enable to collect default metrics. | 91 | | `timeout` | `Number` | `10000` | Timeout option for 'collectDefaultMetrics' in milliseconds. | 92 | | `metrics` | `Object` | `{}` | Metric definitions. | 93 | 94 | # Default Moleculer metrics 95 | 96 | | Name | Type | Labels | Description | 97 | | ---- | ---- | ------ | ----------- | 98 | | `moleculer_nodes_total` | Gauge | - | Moleculer nodes count | 99 | | `moleculer_services_total` | Gauge | - | Moleculer services count | 100 | | `moleculer_actions_total` | Gauge | - | Moleculer actions count | 101 | | `moleculer_events_total` | Gauge | - | Moleculer event subscription count | 102 | | `moleculer_nodes` | Gauge | `nodeID`, `type`, `version`, `langVersion` | Moleculer node list | 103 | | `moleculer_action_endpoints_total` | Gauge | `action` | Moleculer action endpoints | 104 | | `moleculer_service_endpoints_total` | Gauge | `service`, `version` | Moleculer service endpoints | 105 | | `moleculer_event_endpoints_total` | Gauge | `event`, `group` | Moleculer event endpoints | 106 | | `moleculer_req_total` | Counter | `action`, `service`, `nodeID` | Moleculer request count | 107 | | `moleculer_req_errors_total` | Counter | `action`, `service`, `nodeID`, `errorCode`, `errorName`, `errorType` | Moleculer request error count | 108 | | `moleculer_req_duration_ms` | Histogram | `action`, `service`, `nodeID` | Moleculer request durations | 109 | 110 | 111 | # Methods 112 | 113 | 114 | ## `update` 115 | 116 | Update a metric value. 117 | 118 | ### Parameters 119 | | Property | Type | Default | Description | 120 | | -------- | ---- | ------- | ----------- | 121 | | `name` | `String` | **required** | | 122 | | `method` | `String` | **required** | | 123 | | `labels` | `Object` | - | | 124 | | `value` | `any` | **required** | | 125 | | `timestamp` | `any` | **required** | | 126 | 127 | 128 | 129 | 130 | 131 | 166 | 167 | # Test 168 | ``` 169 | $ npm test 170 | ``` 171 | 172 | In development with watching 173 | 174 | ``` 175 | $ npm run ci 176 | ``` 177 | 178 | # License 179 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 180 | 181 | # Contact 182 | Copyright (c) 2016-2018 MoleculerJS 183 | 184 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 185 | -------------------------------------------------------------------------------- /packages/moleculer-console-tracer/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable security/detect-unsafe-regex */ 2 | /* 3 | * moleculer-console-tracer 4 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 5 | * MIT Licensed 6 | */ 7 | 8 | "use strict"; 9 | 10 | const isNaN = require("lodash.isnan"); 11 | const r = require("lodash.repeat"); 12 | const chalk = require("chalk"); 13 | const humanize = require("tiny-human-time").short; 14 | const slice = require("slice-ansi"); 15 | 16 | /** 17 | * Simple tracer service to print metric traces to the console. 18 | * 19 | * @name moleculer-console-tracer 20 | * @module Service 21 | */ 22 | module.exports = { 23 | 24 | name: "console-tracer", 25 | 26 | /** 27 | * Default settings 28 | */ 29 | settings: { 30 | /** @type {Number} Table width. */ 31 | width: 80, 32 | 33 | /** @type {Number} Gauge width. */ 34 | gaugeWidth: 40, 35 | 36 | /** @type {Boolean} Enable colors. */ 37 | colors: true 38 | }, 39 | 40 | /** 41 | * Events 42 | */ 43 | events: { 44 | 45 | "metrics.trace.span.start"(payload) { 46 | this.requests[payload.id] = payload; 47 | payload.spans = []; 48 | 49 | if (payload.parent) { 50 | let parent = this.requests[payload.parent]; 51 | if (parent) 52 | parent.spans.push(payload.id); 53 | } 54 | }, 55 | 56 | "metrics.trace.span.finish"(payload) { 57 | let item = this.requests[payload.id]; 58 | Object.assign(item, payload); 59 | 60 | if (!payload.parent) { 61 | this.printRequest(payload.id); 62 | 63 | // TODO: remove old printed requests 64 | } 65 | } 66 | }, 67 | 68 | /** 69 | * Methods 70 | */ 71 | methods: { 72 | 73 | /** 74 | * Get span name from metric event. By default it returns the action name 75 | * 76 | * @param {Object} metric 77 | * @returns {String} 78 | */ 79 | getSpanName(metric) { 80 | if (metric.name) 81 | return metric.name; 82 | 83 | if (metric.action) 84 | return metric.action.name; 85 | }, 86 | 87 | drawTableTop() { 88 | return chalk.grey("┌" + r("─", this.settings.width - 2) + "┐"); 89 | }, 90 | 91 | drawHorizonalLine() { 92 | return chalk.grey("├" + r("─", this.settings.width - 2) + "┤"); 93 | }, 94 | 95 | drawLine(text) { 96 | return chalk.grey("│ ") + text + chalk.grey(" │"); 97 | }, 98 | 99 | drawTableBottom() { 100 | return chalk.grey("└" + r("─", this.settings.width - 2) + "┘"); 101 | }, 102 | 103 | drawAlignedTexts(leftStr, rightStr, width) { 104 | const ll = leftStr.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "").length; 105 | const rl = rightStr.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "").length; 106 | 107 | const space = width - rl; 108 | 109 | let left; 110 | if (ll <= space) 111 | left = leftStr + r(" ", space - ll); 112 | else { 113 | left = slice(leftStr, 0, Math.max(space - 3, 0)); 114 | left += r(".", Math.min(3, space)); 115 | } 116 | 117 | return left + rightStr; 118 | }, 119 | 120 | drawGauge(gstart, gstop) { 121 | const gw = this.settings.gaugeWidth; 122 | const p1 = Math.floor(gw * gstart / 100); 123 | const p2 = Math.max(Math.floor(gw * gstop / 100) - p1, 1); 124 | const p3 = Math.max(gw - (p1 + p2), 0); 125 | 126 | return [ 127 | chalk.grey("["), 128 | chalk.grey(r(".", p1)), 129 | r("■", p2), 130 | chalk.grey(r(".", p3)), 131 | chalk.grey("]") 132 | ].join(""); 133 | }, 134 | 135 | getCaption(span) { 136 | let caption = this.getSpanName(span); 137 | 138 | if (span.fromCache) 139 | caption += " *"; 140 | if (span.remoteCall) 141 | caption += " »"; 142 | if (span.error) 143 | caption += " ×"; 144 | 145 | return caption; 146 | }, 147 | 148 | getColor(span) { 149 | let c = chalk.bold; 150 | if (span.fromCache) 151 | c = chalk.yellow; 152 | if (span.remoteCall) 153 | c = chalk.cyan; 154 | if (span.duration == null) 155 | c = chalk.grey; 156 | if (span.error) 157 | c = chalk.red.bold; 158 | 159 | return c; 160 | }, 161 | 162 | getTraceInfo(main) { 163 | let depth = 0; 164 | let total = 0; 165 | let check = (span, level) => { 166 | total++; 167 | if (level > depth) 168 | depth = level; 169 | 170 | if (span.spans.length > 0) 171 | span.spans.forEach(spanID => check(this.requests[spanID], level + 1)); 172 | }; 173 | 174 | check(main, 1); 175 | 176 | return { depth, total }; 177 | }, 178 | 179 | /** 180 | * Print a span row 181 | * 182 | * @param {Object} span 183 | * @param {Object} main 184 | */ 185 | printSpanTime(span, main, level) { 186 | const margin = 2 * 2; 187 | const w = (this.settings.width || 80) - margin; 188 | const gw = this.settings.gaugeWidth || 40; 189 | 190 | const time = span.duration == null ? "?" : humanize(span.duration); 191 | const caption = r(" ", level - 1) + this.getCaption(span); 192 | const info = this.drawAlignedTexts(caption, " " + time, w - gw - 3); 193 | 194 | const startTime = span.startTime || main.startTime; 195 | const endTime = span.endTime || main.endTime; 196 | 197 | let gstart = (startTime - main.startTime) / (main.endTime - main.startTime) * 100; 198 | let gstop = (endTime - main.startTime) / (main.endTime - main.startTime) * 100; 199 | 200 | if (isNaN(gstart) && isNaN(gstop)) { 201 | gstart = 0; 202 | gstop = 100; 203 | } 204 | if (gstop > 100) 205 | gstop = 100; 206 | 207 | const c = this.getColor(span); 208 | this.logger.info(this.drawLine(c(info + " " + this.drawGauge(gstart, gstop)))); 209 | 210 | if (span.spans.length > 0) 211 | span.spans.forEach(spanID => this.printSpanTime(this.requests[spanID], main, level + 1)); 212 | }, 213 | 214 | /** 215 | * Print request traces 216 | * 217 | * @param {String} id 218 | */ 219 | printRequest(id) { 220 | const main = this.requests[id]; 221 | const margin = 2 * 2; 222 | const w = (this.settings.width || 80) - margin; 223 | 224 | this.logger.info(this.drawTableTop()); 225 | 226 | const { total, depth } = this.getTraceInfo(main); 227 | 228 | const headerLeft = chalk.grey("ID: ") + chalk.bold(id); 229 | const headerRight = chalk.grey("Depth: ") + chalk.bold(depth) + " " + chalk.grey("Total: ") + chalk.bold(total); 230 | const line = this.drawAlignedTexts(headerLeft, " " + headerRight, w); 231 | this.logger.info(this.drawLine(line)); 232 | 233 | this.logger.info(this.drawHorizonalLine()); 234 | 235 | this.printSpanTime(main, main, 1); 236 | 237 | this.logger.info(this.drawTableBottom()); 238 | }, 239 | }, 240 | 241 | /** 242 | * Service created lifecycle event handler 243 | */ 244 | created() { 245 | this.requests = {}; 246 | 247 | chalk.enabled = this.settings.colors; 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/README.md: -------------------------------------------------------------------------------- 1 | ![Moleculer logo](http://moleculer.services/images/banner.png) 2 | 3 | # moleculer-jaeger [![NPM version](https://img.shields.io/npm/v/moleculer-jaeger.svg)](https://www.npmjs.com/package/moleculer-jaeger) 4 | 5 | Moleculer metrics module for [Jaeger](https://github.com/jaegertracing/jaeger). 6 | 7 | ![Jaeger screenshot](https://user-images.githubusercontent.com/306521/37258289-cc5e1e00-2575-11e8-93df-b81a6a444188.png) 8 | 9 | # Features 10 | - 5 types sampler 11 | - UDP sender 12 | 13 | # Install 14 | 15 | ```bash 16 | $ npm install moleculer-jaeger 17 | ``` 18 | 19 | # Usage 20 | 21 | ```js 22 | // moleculer.config.js 23 | module.exports = { 24 | // ... 25 | metrics: true, 26 | // ... 27 | } 28 | 29 | // services/metrics.jaeger.service.js 30 | const JaegerService = require("moleculer-jaeger"); 31 | 32 | module.exports = { 33 | mixins: [JaegerService], 34 | settings: { 35 | host: "jaeger-server", 36 | port: 6832 37 | } 38 | }; 39 | ``` 40 | 41 | ## Sampler configurations 42 | More info: [http://jaeger.readthedocs.io/en/latest/client_libraries/#sampling](http://jaeger.readthedocs.io/en/latest/client_libraries/#sampling) 43 | 44 | **Setup ConstSampler (default):** 45 | ```js 46 | module.exports = { 47 | mixins: [JaegerService], 48 | settings: { 49 | host: "jaeger-server", 50 | port: 6832, 51 | 52 | sampler: { 53 | type: "Const", 54 | options: { 55 | decision: 1 56 | } 57 | } 58 | } 59 | }); 60 | ``` 61 | 62 | **Setup RateLimitingSampler:** 63 | ```js 64 | module.exports = { 65 | mixins: [JaegerService], 66 | settings: { 67 | host: "jaeger-server", 68 | port: 6832, 69 | 70 | sampler: { 71 | type: "RateLimiting", 72 | options: { 73 | maxTracesPerSecond: 2, 74 | initBalance: 5 75 | } 76 | } 77 | } 78 | }); 79 | ``` 80 | 81 | **Setup ProbabilisticSampler:** 82 | ```js 83 | module.exports = { 84 | mixins: [JaegerService], 85 | settings: { 86 | host: "jaeger-server", 87 | port: 6832, 88 | 89 | sampler: { 90 | type: "Probabilistic", 91 | options: { 92 | samplingRate: 0.1 // 10% 93 | } 94 | } 95 | } 96 | }); 97 | ``` 98 | 99 | **Setup GuaranteedThroughputSampler:** 100 | >GuaranteedThroughputProbabilisticSampler is a sampler that leverages both probabilisticSampler and rateLimitingSampler. The rateLimitingSampler is used as a guaranteed lower bound sampler such that every operation is sampled at least once in a time interval defined by the lowerBound. ie a lowerBound of `1.0 / (60 * 10)` will sample an operation at least once every 10 minutes. 101 | 102 | ```js 103 | module.exports = { 104 | mixins: [JaegerService], 105 | settings: { 106 | host: "jaeger-server", 107 | port: 6832, 108 | 109 | sampler: { 110 | type: "GuaranteedThroughput", 111 | options: { 112 | lowerBound: 0.1, 113 | samplingRate: 0.1 114 | } 115 | } 116 | } 117 | }); 118 | ``` 119 | 120 | **Setup RemoteControlledSampler:** 121 | ```js 122 | module.exports = { 123 | mixins: [JaegerService], 124 | settings: { 125 | host: "jaeger-server", 126 | port: 6832, 127 | 128 | sampler: { 129 | type: "RemoteControlled", 130 | options: { 131 | //... 132 | } 133 | } 134 | } 135 | }); 136 | ``` 137 | 138 | 139 | 140 | 141 | 148 | 149 | 150 | 151 | # Settings 152 | 153 | 154 | | Property | Type | Default | Description | 155 | | -------- | ---- | ------- | ----------- | 156 | | `host` | `String` | **required** | UDP Sender host option. | 157 | | `port` | `Number` | `null` | UDP Sender port option. | 158 | | `sampler` | `Object` | `null` | Sampler configuration. | 159 | | `sampler.type` | `String` | `null` | Sampler type | 160 | | `sampler.options` | any | **required** | | 161 | | `options` | `Object` | `null` | Additional options for `Jaeger.Tracer` | 162 | 163 | 164 | 165 | 176 | 177 | # Actions 178 | 179 | 180 | 181 | 216 | 217 | # Methods 218 | 219 | 220 | 221 | 222 | 257 | 258 | # Test 259 | ``` 260 | $ npm test 261 | ``` 262 | 263 | In development with watching 264 | 265 | ``` 266 | $ npm run ci 267 | ``` 268 | 269 | # License 270 | The project is available under the [MIT license](https://tldrlegal.com/license/mit-license). 271 | 272 | # Contact 273 | Copyright (c) 2016-2018 MoleculerJS 274 | 275 | [![@moleculerjs](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS) 276 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-jaeger 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const isFunction = require("lodash.isfunction"); 10 | const Jaeger = require("jaeger-client"); 11 | const GuaranteedThroughputSampler = require("jaeger-client/dist/src/samplers/guaranteed_throughput_sampler").default; 12 | const RemoteControlledSampler = require("jaeger-client/dist/src/samplers/remote_sampler").default; 13 | const UDPSender = require("jaeger-client/dist/src/reporters/udp_sender").default; 14 | 15 | const Int64 = require("node-int64"); 16 | 17 | /** 18 | * Moleculer metrics module for Jaeger. 19 | * 20 | * http://jaeger.readthedocs.io/en/latest/getting_started/#all-in-one-docker-image 21 | * 22 | * Running Jaeger in Docker: 23 | * 24 | * docker run -d --name jaeger -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest 25 | * 26 | * UI: http://:16686/ 27 | * 28 | * @name moleculer-jaeger 29 | * @module Service 30 | */ 31 | module.exports = { 32 | name: "jaeger", 33 | 34 | /** 35 | * Default settings 36 | */ 37 | settings: { 38 | /** @type {String} UDP Sender host option. */ 39 | host: "127.0.0.1", 40 | /** @type {Number?} UDP Sender port option. */ 41 | port: 6832, 42 | 43 | /** @type {Object?} Sampler configuration. */ 44 | sampler: { 45 | /** @type {String?} Sampler type */ 46 | type: "Const", 47 | 48 | /** @type: {Object?} Sampler specific options. */ 49 | options: { 50 | } 51 | }, 52 | 53 | /** @type {Object?} Additional options for `Jaeger.Tracer` */ 54 | options: {} 55 | }, 56 | 57 | /** 58 | * Events 59 | */ 60 | events: { 61 | "metrics.trace.span.finish"(metric) { 62 | this.makePayload(metric); 63 | } 64 | }, 65 | 66 | /** 67 | * Methods 68 | */ 69 | methods: { 70 | /** 71 | * Get service name from metric event 72 | * 73 | * @param {Object} metric 74 | * @returns {String} 75 | */ 76 | getServiceName(metric) { 77 | if (metric.service) 78 | return metric.service.name ? metric.service.name : metric.service; 79 | 80 | let parts = metric.action.name.split("."); 81 | parts.pop(); 82 | return parts.join("."); 83 | }, 84 | 85 | /** 86 | * Get span name from metric event. By default it returns the action name 87 | * 88 | * @param {Object} metric 89 | * @returns {String} 90 | */ 91 | getSpanName(metric) { 92 | if (metric.name) 93 | return metric.name; 94 | 95 | if (metric.action) 96 | return metric.action.name; 97 | }, 98 | 99 | /** 100 | * Create Jaeger payload from metric event 101 | * 102 | * @param {Object} metric 103 | */ 104 | makePayload(metric) { 105 | const serviceName = this.getServiceName(metric); 106 | const tracer = this.getTracer(serviceName); 107 | 108 | let parentCtx; 109 | if (metric.parent) { 110 | parentCtx = new Jaeger.SpanContext( 111 | this.convertID(metric.requestID), // traceId, 112 | this.convertID(metric.parent), // spanId, 113 | null, // parentId, 114 | null, // traceIdStr 115 | null, // spanIdStr 116 | null, // parentIdStr 117 | 1, // flags 118 | {}, // baggage 119 | "" // debugId 120 | ); 121 | } 122 | 123 | const span = tracer.startSpan(this.getSpanName(metric), { 124 | startTime: metric.startTime, 125 | childOf: parentCtx, 126 | tags: { 127 | nodeID: metric.nodeID, 128 | level: metric.level, 129 | remoteCall: metric.remoteCall 130 | } 131 | }); 132 | this.addTags(span, "service", serviceName); 133 | if (metric.action && metric.action.name) 134 | this.addTags(span, "action", metric.action.name); 135 | 136 | this.addTags(span, Jaeger.opentracing.Tags.SPAN_KIND, Jaeger.opentracing.Tags.SPAN_KIND_RPC_SERVER); 137 | 138 | const sc = span.context(); 139 | sc.traceId = this.convertID(metric.requestID); 140 | sc.spanId = this.convertID(metric.id); 141 | 142 | if (metric.callerNodeID) 143 | this.addTags(span, "callerNodeID", metric.callerNodeID); 144 | 145 | if (metric.params) 146 | this.addTags(span, "params", metric.params); 147 | 148 | if (metric.meta) 149 | this.addTags(span, "meta", metric.meta); 150 | 151 | if (metric.error) { 152 | this.addTags(span, Jaeger.opentracing.Tags.ERROR, true); 153 | this.addTags(span, "error.message", metric.error.message); 154 | this.addTags(span, "error.type", metric.error.type); 155 | this.addTags(span, "error.code", metric.error.code); 156 | 157 | if (metric.error.data) 158 | this.addTags(span, "error.data", metric.error.data); 159 | 160 | if (metric.error.stack) 161 | this.addTags(span, "error.stack", metric.error.stack.toString()); 162 | } 163 | 164 | span.finish(metric.endTime); 165 | }, 166 | 167 | /** 168 | * Add tags to span 169 | * 170 | * @param {Object} span 171 | * @param {String} key 172 | * @param {any} value 173 | * @param {String?} prefix 174 | */ 175 | addTags(span, key, value, prefix) { 176 | const name = prefix ? `${prefix}.${key}` : key; 177 | if (value && typeof value == "object") { 178 | Object.keys(value).forEach(k => this.addTags(span, k, value[k], name)); 179 | } else { 180 | span.setTag(name, value); 181 | } 182 | }, 183 | 184 | /** 185 | * Convert Context ID to Zipkin format 186 | * 187 | * @param {String} id 188 | * @returns {String} 189 | */ 190 | convertID(id) { 191 | if (id) { 192 | return new Int64(id.replace(/-/g, "").substring(0, 16)).toBuffer(); 193 | } 194 | return null; 195 | }, 196 | 197 | /** 198 | * Get sampler instance for Tracer 199 | * 200 | */ 201 | getSampler(serviceName) { 202 | if (isFunction(this.settings.sampler)) 203 | return this.settings.sampler; 204 | 205 | if (this.settings.sampler.type == "RateLimiting") 206 | return new Jaeger.RateLimitingSampler(this.settings.sampler.options.maxTracesPerSecond, this.settings.sampler.options.initBalance); 207 | 208 | if (this.settings.sampler.type == "Probabilistic") 209 | return new Jaeger.ProbabilisticSampler(this.settings.sampler.options.samplingRate); 210 | 211 | if (this.settings.sampler.type == "GuaranteedThroughput") 212 | return new GuaranteedThroughputSampler(this.settings.sampler.options.lowerBound, this.settings.sampler.options.samplingRate); 213 | 214 | if (this.settings.sampler.type == "RemoteControlled") 215 | return new RemoteControlledSampler(serviceName, this.settings.sampler.options); 216 | 217 | return new Jaeger.ConstSampler(this.settings.sampler.options && this.settings.sampler.options.decision != null ? this.settings.sampler.options.decision : 1); 218 | }, 219 | 220 | /** 221 | * Get reporter instance for Tracer 222 | * 223 | */ 224 | getReporter() { 225 | return new Jaeger.RemoteReporter(new UDPSender({ host: this.settings.host, port: this.settings.port })); 226 | }, 227 | 228 | /** 229 | * Get a tracer instance by service name 230 | * 231 | * @param {any} serviceName 232 | * @returns {Jaeger.Tracer} 233 | */ 234 | getTracer(serviceName) { 235 | if (this.tracers[serviceName]) 236 | return this.tracers[serviceName]; 237 | 238 | const sampler = this.getSampler(); 239 | const reporter = this.getReporter(); 240 | 241 | const tracer = new Jaeger.Tracer(serviceName, reporter, sampler, this.settings.options); 242 | this.tracers[serviceName] = tracer; 243 | 244 | return tracer; 245 | } 246 | }, 247 | 248 | /** 249 | * Service created lifecycle event handler 250 | */ 251 | created() { 252 | this.tracers = {}; 253 | }, 254 | 255 | /** 256 | * Service started lifecycle event handler 257 | */ 258 | started() { 259 | 260 | }, 261 | 262 | /** 263 | * Service stopped lifecycle event handler 264 | */ 265 | stopped() { 266 | Object.keys(this.tracers).forEach(service => { 267 | this.tracers[service].close(); 268 | }); 269 | this.tracers = {}; 270 | } 271 | }; 272 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-prometheus 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const polka = require("polka"); 10 | 11 | /** 12 | * Moleculer metrics module for Prometheus. 13 | * 14 | * https://prometheus.io/ 15 | * 16 | * Running Prometheus & Grafana in Docker: 17 | * 18 | * git clone https://github.com/vegasbrianc/prometheus.git 19 | * cd prometheus 20 | * 21 | * Please note, don't forget add your endpoint to static targets in prometheus/prometheus.yml file 22 | * 23 | * static_configs: 24 | * - targets: ['localhost:9090', 'moleculer-node-123:3030'] 25 | * 26 | * Start containers: 27 | * 28 | * docker-compose up -d 29 | * 30 | * Grafana dashboard: http://:3000 31 | * 32 | * @name moleculer-prometheus 33 | * @module Service 34 | */ 35 | module.exports = { 36 | name: "prometheus", 37 | 38 | /** 39 | * Default settings 40 | */ 41 | settings: { 42 | /** @type {Number} Exposed HTTP port. */ 43 | port: 3030, 44 | 45 | /** @type {Boolean} Enable to collect default metrics. */ 46 | collectDefaultMetrics: true, 47 | 48 | /** @type {Number} Timeout option for 'collectDefaultMetrics'. */ 49 | timeout: 10 * 1000, 50 | 51 | /** @type {Object} Metric definitions. */ 52 | metrics: { 53 | // Common metrics 54 | "moleculer_nodes_total": { type: "Gauge", help: "Moleculer nodes count" }, 55 | "moleculer_services_total": { type: "Gauge", help: "Moleculer services count" }, 56 | "moleculer_actions_total": { type: "Gauge", help: "Moleculer actions count" }, 57 | "moleculer_events_total": { type: "Gauge", help: "Moleculer event subscriptions" }, 58 | 59 | // Nodes 60 | "moleculer_nodes": { type: "Gauge", labelNames: [ "nodeID", "type", "version", "langVersion" ], help: "Moleculer node list" }, 61 | 62 | // Actions 63 | "moleculer_action_endpoints_total": { type: "Gauge", labelNames: [ "action" ], help: "Moleculer action endpoints" }, 64 | 65 | // Services 66 | "moleculer_service_endpoints_total": { type: "Gauge", labelNames: [ "service", "version" ], help: "Moleculer service endpoints" }, 67 | 68 | // Events 69 | "moleculer_event_endpoints_total": { type: "Gauge", labelNames: [ "event", "group" ], help: "Moleculer event endpoints" }, 70 | 71 | // Service requests 72 | "moleculer_req_total": { type: "Counter", labelNames: [ "action", "service", "nodeID" ], help: "Moleculer action request count"}, 73 | "moleculer_req_errors_total": { type: "Counter", labelNames: [ "action", "service", "nodeID", "errorCode", "errorName", "errorType" ], help: "Moleculer request error count"}, 74 | "moleculer_req_duration_ms": { type: "Histogram", labelNames: [ "action", "service", "nodeID" ], help: "Moleculer request durations"}, 75 | } 76 | }, 77 | 78 | /** 79 | * Events 80 | */ 81 | events: { 82 | "metrics.trace.span.finish"(payload) { 83 | let serviceName = this.getServiceName(payload); 84 | let spanName = this.getSpanName(payload); 85 | 86 | this.update("moleculer_req_total", "inc", { action: spanName, service: serviceName, nodeID: payload.nodeID }); 87 | this.update("moleculer_req_duration_ms", "observe", { action: spanName, service: serviceName, nodeID: payload.nodeID }, payload.duration); 88 | 89 | if (payload.error) { 90 | this.update("moleculer_req_errors_total", "inc", { 91 | action: spanName, 92 | service: serviceName, 93 | nodeID: payload.nodeID, 94 | errorCode: payload.error.code, 95 | errorName: payload.error.name, 96 | errorType: payload.error.type ? payload.error.type : "" 97 | }); 98 | } 99 | }, 100 | 101 | "metrics.update"(payload) { 102 | this.update(payload.name, payload.method, payload.labels, payload.value, payload.timestamp); 103 | }, 104 | 105 | "$services.changed"() { 106 | this.updateCommonValues(); 107 | }, 108 | 109 | "$node.connected"() { 110 | this.updateCommonValues(); 111 | }, 112 | 113 | "$node.disconnected"() { 114 | this.updateCommonValues(); 115 | } 116 | }, 117 | 118 | /** 119 | * Methods 120 | */ 121 | methods: { 122 | /** 123 | * Get service name from metric event 124 | * 125 | * @param {Object} metric 126 | * @returns {String} 127 | */ 128 | getServiceName(metric) { 129 | if (metric.service) 130 | return metric.service.name ? metric.service.name : metric.service; 131 | 132 | let parts = metric.action.name.split("."); 133 | parts.pop(); 134 | return parts.join("."); 135 | }, 136 | 137 | /** 138 | * Get span name from metric event. By default it returns the action name 139 | * 140 | * @param {Object} metric 141 | * @returns {String} 142 | */ 143 | getSpanName(metric) { 144 | if (metric.name) 145 | return metric.name; 146 | 147 | if (metric.action) 148 | return metric.action.name; 149 | }, 150 | 151 | /** 152 | * Create Prometheus metrics. 153 | * 154 | * @param {Object} metricsDefs 155 | */ 156 | createMetrics(metricsDefs) { 157 | this.metrics = {}; 158 | if (!metricsDefs) return; 159 | 160 | Object.keys(metricsDefs).forEach(name => { 161 | const def = metricsDefs[name]; 162 | 163 | if (def) 164 | this.metrics[name] = new this.client[def.type](Object.assign({ 165 | name, 166 | registers: this.register? [this.register]:[] 167 | }, def)); 168 | }); 169 | }, 170 | 171 | /** 172 | * Update common Moleculer metric values. 173 | */ 174 | updateCommonValues() { 175 | if (!this.metrics) return; 176 | 177 | return this.broker.mcall({ 178 | nodes: { action: "$node.list" }, 179 | services: { action: "$node.services", params: { withActions: false, grouping: true, skipInternal: true } }, 180 | actions: { action: "$node.actions", params: { withEndpoints: true, skipInternal: true } }, 181 | events: { action: "$node.events", params: { withEndpoints: true, skipInternal: true } } 182 | }).then(({ nodes, services, actions, events}) => { 183 | 184 | this.update("moleculer_nodes_total", "set", null, nodes.filter(node => node.available).length); 185 | nodes.forEach(node => this.update("moleculer_nodes", "set", { nodeID: node.id, type: node.client.type, version: node.client.version, langVersion: node.client.langVersion }, node.available ? 1 : 0)); 186 | 187 | this.update("moleculer_services_total", "set", null, services.length); 188 | services.forEach(svc => this.update("moleculer_service_endpoints_total", "set", { service: svc.name, version: svc.version }, svc.nodes.length)); 189 | 190 | this.update("moleculer_actions_total", "set", null, actions.length); 191 | actions.forEach(action => this.update("moleculer_action_endpoints_total", "set", { action: action.name }, action.endpoints ? action.endpoints.length : 0)); 192 | 193 | this.update("moleculer_events_total", "set", null, events.length); 194 | events.forEach(event => this.update("moleculer_event_endpoints_total", "set", { event: event.name, group: event.group }, event.endpoints ? event.endpoints.length : 0)); 195 | }); 196 | }, 197 | 198 | /** 199 | * Update a metric value. 200 | * 201 | * @methods 202 | * @param {String} name 203 | * @param {String} method 204 | * @param {Object?} labels 205 | * @param {any} value 206 | * @param {any} timestamp 207 | */ 208 | update(name, method, labels, value, timestamp) { 209 | if (this.metrics[name]) { 210 | if (labels) 211 | this.metrics[name][method](labels, value, timestamp); 212 | else 213 | this.metrics[name][method](value, timestamp); 214 | } 215 | } 216 | }, 217 | 218 | /** 219 | * Service created lifecycle event handler 220 | */ 221 | created() { 222 | this.server = polka(); 223 | }, 224 | 225 | /** 226 | * Service started lifecycle event handler 227 | */ 228 | started() { 229 | this.client = require("prom-client"); 230 | this.register = new this.client.Registry(); 231 | 232 | if (this.settings.collectDefaultMetrics) { 233 | this.timer = this.client.collectDefaultMetrics({ 234 | timeout: this.settings.timeout, 235 | register: this.register 236 | }); 237 | } 238 | 239 | this.createMetrics(this.settings.metrics); 240 | 241 | this.server.get("/metrics", (req, res) => { 242 | res.setHeader("Content-Type", this.client.contentType); 243 | res.end(this.register.metrics()); 244 | }); 245 | 246 | return this.server.listen(this.settings.port).then(() => { 247 | this.logger.info(`Prometheus collector is listening on port ${this.settings.port}, metrics exposed on /metrics endpoint`); 248 | 249 | this.updateCommonValues(); 250 | }); 251 | }, 252 | 253 | /** 254 | * Service stopped lifecycle event handler 255 | */ 256 | stopped() { 257 | if (this.timer) { 258 | clearInterval(this.timer); 259 | this.timer = null; 260 | } 261 | 262 | this.register = null; 263 | 264 | if (this.server) 265 | this.server.server.close(); 266 | } 267 | }; 268 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer-zipkin 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer-addons) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | let axios = require("axios"); 10 | 11 | /** 12 | * Zipkin tracing addons. 13 | * 14 | * API v2: https://zipkin.io/zipkin-api/#/ 15 | * API v1: https://zipkin.io/pages/data_model.html 16 | * 17 | * Running Zipkin in Docker: 18 | * 19 | * docker run -d -p 9411:9411 --name=zipkin openzipkin/zipkin 20 | * 21 | * @name moleculer-zipkin 22 | * @module Service 23 | */ 24 | module.exports = { 25 | name: "zipkin", 26 | 27 | /** 28 | * Default settings 29 | */ 30 | settings: { 31 | /** @type {String} Base URL for Zipkin server. */ 32 | baseURL: "http://localhost:9411", 33 | 34 | /** @type {String} Zipkin REST API version. */ 35 | version: "v2", 36 | 37 | /** @type {Number} Batch send time interal. Disable: 0 */ 38 | batchTime: 1000, 39 | 40 | /** @type {Object} Additional payload options. */ 41 | payloadOptions: { 42 | 43 | /** @type {Boolean} Set `debug` property in v2 payload. */ 44 | debug: false, 45 | 46 | /** @type {Boolean} Set `shared` property in v2 payload. */ 47 | shared: false 48 | } 49 | }, 50 | 51 | /** 52 | * Events 53 | */ 54 | events: { 55 | "metrics.trace.span.finish"(metric) { 56 | if (this.settings.version == "v2") { 57 | this.makeZipkinPayloadV2(metric); 58 | } else { 59 | this.makeZipkinPayloadV1(metric); 60 | } 61 | } 62 | }, 63 | 64 | /** 65 | * Methods 66 | */ 67 | methods: { 68 | /** 69 | * Get service name from metric event 70 | * 71 | * @param {Object} metric 72 | * @returns {String} 73 | */ 74 | getServiceName(metric) { 75 | if (metric.service) 76 | return metric.service.name ? metric.service.name : metric.service; 77 | 78 | let parts = metric.action.name.split("."); 79 | parts.pop(); 80 | return parts.join("."); 81 | }, 82 | 83 | /** 84 | * Get span name from metric event. By default it returns the action name 85 | * 86 | * @param {Object} metric 87 | * @returns {String} 88 | */ 89 | getSpanName(metric) { 90 | if (metric.name) 91 | return metric.name; 92 | 93 | if (metric.action) 94 | return metric.action.name; 95 | }, 96 | 97 | /** 98 | * Create Zipkin v1 payload from metric event 99 | * 100 | * @param {Object} metric 101 | */ 102 | makeZipkinPayloadV1(metric) { 103 | const serviceName = this.getServiceName(metric); 104 | 105 | const payload = { 106 | name: this.getSpanName(metric), 107 | 108 | // Trace & span IDs 109 | traceId: this.convertID(metric.requestID), 110 | id: this.convertID(metric.id), 111 | parentId: this.convertID(metric.parent), 112 | 113 | // Annotations 114 | annotations: [ 115 | { 116 | endpoint: { serviceName: serviceName, ipv4: "", port: 0 }, 117 | timestamp: this.convertTime(metric.startTime), 118 | value: "sr" 119 | }, 120 | { 121 | endpoint: { serviceName: serviceName, ipv4: "", port: 0 }, 122 | timestamp: this.convertTime(metric.endTime), 123 | value: "ss" 124 | } 125 | ], 126 | 127 | // Binary annotations 128 | binaryAnnotations: [ 129 | { key: "nodeID", value: metric.nodeID }, 130 | { key: "level", value: metric.level.toString() }, 131 | { key: "remoteCall", value: metric.remoteCall.toString() }, 132 | { key: "callerNodeID", value: metric.callerNodeID ? metric.callerNodeID : "" } 133 | ], 134 | 135 | timestamp: this.convertTime(metric.endTime) 136 | }; 137 | 138 | if (metric.params) 139 | this.addBinaryAnnotation(payload, "params", metric.params); 140 | 141 | if (metric.meta) 142 | this.addBinaryAnnotation(payload, "meta", metric.meta); 143 | 144 | if (metric.error) { 145 | this.addBinaryAnnotation(payload, "error", metric.error.message); 146 | this.addBinaryAnnotation(payload, "error.type", metric.error.type); 147 | this.addBinaryAnnotation(payload, "error.code", metric.error.code); 148 | 149 | if (metric.error.data) 150 | this.addBinaryAnnotation(payload, "error.data", metric.error.data); 151 | 152 | if (metric.error.stack) 153 | this.addBinaryAnnotation(payload, "error.stack", metric.error.stack.toString()); 154 | 155 | payload.annotations.push({ 156 | value: "error", 157 | endpoint: { serviceName: serviceName, ipv4: "", port: 0 }, 158 | timestamp: this.convertTime(metric.endTime) 159 | }); 160 | } 161 | 162 | this.enqueue(payload); 163 | }, 164 | 165 | /** 166 | * Create Zipkin v2 payload from metric event 167 | * 168 | * @param {Object} metric 169 | */ 170 | makeZipkinPayloadV2(metric) { 171 | const serviceName = this.getServiceName(metric); 172 | 173 | const payload = { 174 | name: this.getSpanName(metric), 175 | kind: "CONSUMER", 176 | 177 | // Trace & span IDs 178 | traceId: this.convertID(metric.requestID), 179 | id: this.convertID(metric.id), 180 | parentId: this.convertID(metric.parent), 181 | 182 | localEndpoint: { 183 | serviceName: serviceName, 184 | }, 185 | 186 | remoteEndpoint: { 187 | serviceName: serviceName, 188 | }, 189 | 190 | annotations: [ 191 | { timestamp: this.convertTime(metric.startTime), value: "sr" }, 192 | { timestamp: this.convertTime(metric.endTime), value: "ss" }, 193 | ], 194 | 195 | // Tags 196 | tags: { 197 | nodeID: metric.nodeID, 198 | level: metric.level.toString(), 199 | remoteCall: metric.remoteCall.toString(), 200 | callerNodeID: metric.callerNodeID ? metric.callerNodeID : "" 201 | }, 202 | 203 | timestamp: this.convertTime(metric.startTime), 204 | duration: Math.round(metric.duration * 1000), 205 | 206 | debug: this.settings.payloadOptions.debug, 207 | shared: this.settings.payloadOptions.shared 208 | }; 209 | 210 | if (metric.params) 211 | this.addTags(payload, "params", metric.params); 212 | 213 | if (metric.meta) 214 | this.addTags(payload, "meta", metric.meta); 215 | 216 | if (metric.error) { 217 | this.addTags(payload, "error", metric.error.message); 218 | this.addTags(payload, "error.type", metric.error.type); 219 | this.addTags(payload, "error.code", metric.error.code); 220 | 221 | if (metric.error.data) 222 | this.addTags(payload, "error.data", metric.error.data); 223 | 224 | if (metric.error.stack) 225 | this.addTags(payload, "error.stack", metric.error.stack.toString()); 226 | 227 | 228 | payload.annotations.push({ 229 | value: "error", 230 | endpoint: { serviceName: serviceName, ipv4: "", port: 0 }, 231 | timestamp: this.convertTime(metric.endTime) 232 | }); 233 | } 234 | 235 | this.enqueue(payload); 236 | }, 237 | 238 | /** 239 | * Enqueue the span payload 240 | * 241 | * @param {Object} payload 242 | */ 243 | enqueue(payload) { 244 | if (this.settings.batchTime > 0) { 245 | this.queue.push(payload); 246 | } else { 247 | this.send([payload]); 248 | } 249 | }, 250 | 251 | /** 252 | * Send all spans from the queue. 253 | * 254 | */ 255 | sendFromQueue() { 256 | if (this.queue.length > 0) { 257 | const payloads = this.queue; 258 | this.send(payloads); 259 | this.queue = []; 260 | } 261 | }, 262 | 263 | /** 264 | * Send multiple payloads to Zipkin server 265 | * 266 | * @param {Array} payloads 267 | */ 268 | send(payloads) { 269 | if (this.settings.baseURL) { 270 | this.axios.post(`/api/${this.settings.version}/spans`, payloads) 271 | .then(() => this.logger.debug(`${payloads.length} span(s) sent.`)) 272 | .catch(err => { 273 | /* istanbul ignore next */ 274 | const message = err.response ? err.response.data : err.message; 275 | /* istanbul ignore next */ 276 | this.logger.debug("Span sending error!", message, payloads); 277 | }); 278 | } 279 | }, 280 | 281 | /** 282 | * Add binary annotation to v1 payload 283 | * 284 | * @param {Object} payload 285 | * @param {String} key 286 | * @param {any} value 287 | * @param {String?} prefix 288 | */ 289 | addBinaryAnnotation(payload, key, value, prefix) { 290 | const name = prefix ? (prefix + "." + key) : key; 291 | if (typeof value == "object") { 292 | Object.keys(value).forEach(k => this.addBinaryAnnotation(payload, k, value[k], name)); 293 | } else { 294 | payload.binaryAnnotations.push({ 295 | key: name, 296 | value: String(value) 297 | }); 298 | } 299 | }, 300 | 301 | /** 302 | * Add tags to v2 payload 303 | * 304 | * @param {Object} payload 305 | * @param {String} key 306 | * @param {any} value 307 | * @param {String?} prefix 308 | */ 309 | addTags(payload, key, value, prefix) { 310 | const name = prefix ? `${prefix}.${key}` : key; 311 | if (value && typeof value == "object") { 312 | Object.keys(value).forEach(k => this.addTags(payload, k, value[k], name)); 313 | } else if (value !== undefined) { 314 | payload.tags[name] = String(value); 315 | } 316 | }, 317 | 318 | /** 319 | * Convert Context ID to Zipkin format 320 | * 321 | * @param {String} id 322 | * @returns {String} 323 | */ 324 | convertID(id) { 325 | return id ? id.replace(/-/g, "").substring(0, 16) : null; 326 | }, 327 | 328 | /** 329 | * Convert JS timestamp to microseconds 330 | * 331 | * @param {Number} ts 332 | * @returns {Number} 333 | */ 334 | convertTime(ts) { 335 | return Math.round(ts * 1000); 336 | }, 337 | }, 338 | 339 | /** 340 | * Service created lifecycle event handler 341 | */ 342 | created() { 343 | /* istanbul ignore next */ 344 | if (!this.settings.baseURL) { 345 | this.logger.warn("The 'baseURL' is not defined in service settings. Tracing is DISABLED!"); 346 | } 347 | 348 | this.queue = []; 349 | 350 | this.axios = axios.create({ 351 | baseURL: this.settings.baseURL, 352 | headers: { 353 | post: { 354 | "Content-Type": "application/json" 355 | } 356 | } 357 | }); 358 | }, 359 | 360 | /** 361 | * Service started lifecycle event handler 362 | */ 363 | started() { 364 | if (this.settings.batchTime > 0) { 365 | this.timer = setInterval(() => this.sendFromQueue(), this.settings.batchTime); 366 | } 367 | }, 368 | 369 | /** 370 | * Service stopped lifecycle event handler 371 | */ 372 | stopped() { 373 | if (this.timer) { 374 | if (this.queue.length > 0) 375 | this.sendFromQueue(); 376 | 377 | clearInterval(this.timer); 378 | this.timer = null; 379 | } 380 | } 381 | }; 382 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | jest.mock("polka"); 4 | const Polka = require("polka"); 5 | let pathCB; 6 | const MockPolka = { 7 | listen: jest.fn(() => Promise.resolve()), 8 | get: jest.fn((path, cb) => { 9 | pathCB = cb; 10 | }), 11 | server: { 12 | close: jest.fn() 13 | } 14 | }; 15 | Polka.mockImplementation(() => MockPolka); 16 | 17 | jest.mock("prom-client"); 18 | const Prometheus = require("prom-client"); 19 | Prometheus.collectDefaultMetrics = jest.fn(() => ({})); 20 | //Prometheus.mockImplementation(() => MockPromClient); 21 | 22 | const { ServiceBroker } = require("moleculer"); 23 | const PromService = require("../../src"); 24 | 25 | describe("Test PromService constructor", () => { 26 | const broker = new ServiceBroker({ logger: false }); 27 | const service = broker.createService(PromService); 28 | 29 | it("should be created", () => { 30 | expect(service).toBeDefined(); 31 | expect(service.server).toBeDefined(); 32 | }); 33 | }); 34 | 35 | describe("Test PromService started & stopped", () => { 36 | 37 | describe("with default settings", () => { 38 | 39 | const broker = new ServiceBroker({ logger: false }); 40 | const service = broker.createService(PromService); 41 | 42 | service.createMetrics = jest.fn(); 43 | 44 | Prometheus.collectDefaultMetrics.mockClear(); 45 | MockPolka.get.mockClear(); 46 | MockPolka.listen.mockClear(); 47 | 48 | beforeAll(() => broker.start()); 49 | 50 | it("should start service", () => { 51 | expect(service).toBeDefined(); 52 | expect(service.client).toBe(Prometheus); 53 | 54 | expect(Prometheus.collectDefaultMetrics).toHaveBeenCalledTimes(1); 55 | expect(Prometheus.collectDefaultMetrics).toHaveBeenCalledWith({ timeout: 10000, register: expect.any(Prometheus.Registry) }); 56 | 57 | expect(service.createMetrics).toHaveBeenCalledTimes(1); 58 | expect(service.createMetrics).toHaveBeenCalledWith(service.settings.metrics); 59 | 60 | expect(service.timer).toBeDefined(); 61 | 62 | expect(MockPolka.get).toHaveBeenCalledTimes(1); 63 | expect(MockPolka.get).toHaveBeenCalledWith("/metrics", jasmine.any(Function)); 64 | 65 | expect(MockPolka.listen).toHaveBeenCalledTimes(1); 66 | expect(MockPolka.listen).toHaveBeenCalledWith(3030); 67 | }); 68 | 69 | it("should set res header and content", () => { 70 | const res = { 71 | setHeader: jest.fn(), 72 | end: jest.fn() 73 | }; 74 | service.register.metrics = jest.fn(() => "data"); 75 | 76 | pathCB(null, res); 77 | 78 | expect(res.setHeader).toHaveBeenCalledTimes(1); 79 | expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; version=0.0.4; charset=utf-8"); 80 | 81 | expect(res.end).toHaveBeenCalledTimes(1); 82 | expect(res.end).toHaveBeenCalledWith("data"); 83 | expect(service.register.metrics).toHaveBeenCalledTimes(1); 84 | }); 85 | 86 | it("should destroy timer", () => { 87 | global.clearInterval = jest.fn(); 88 | const timer = service.timer; 89 | 90 | return broker.stop().then(() => { 91 | expect(clearInterval).toHaveBeenCalledTimes(1); 92 | expect(clearInterval).toHaveBeenCalledWith(timer); 93 | 94 | expect(MockPolka.server.close).toHaveBeenCalledTimes(1); 95 | expect(MockPolka.server.close).toHaveBeenCalledWith(); 96 | }); 97 | }); 98 | 99 | it("change settings", () => { 100 | service.settings = { 101 | collectDefaultMetrics: false, 102 | port: 4567 103 | }; 104 | 105 | service.createMetrics = jest.fn(); 106 | Prometheus.collectDefaultMetrics.mockClear(); 107 | MockPolka.get.mockClear(); 108 | MockPolka.listen.mockClear(); 109 | 110 | return broker.start(); 111 | }); 112 | 113 | it("should not start timer", () => { 114 | expect(service).toBeDefined(); 115 | expect(service.client).toBe(Prometheus); 116 | 117 | expect(Prometheus.collectDefaultMetrics).toHaveBeenCalledTimes(0); 118 | 119 | expect(service.createMetrics).toHaveBeenCalledTimes(1); 120 | expect(service.createMetrics).toHaveBeenCalledWith(service.settings.metrics); 121 | 122 | expect(service.timer).toBeNull(); 123 | 124 | expect(MockPolka.get).toHaveBeenCalledTimes(1); 125 | expect(MockPolka.get).toHaveBeenCalledWith("/metrics", jasmine.any(Function)); 126 | 127 | expect(MockPolka.listen).toHaveBeenCalledTimes(1); 128 | expect(MockPolka.listen).toHaveBeenCalledWith(4567); 129 | }); 130 | 131 | it("should not destroy timer", () => { 132 | MockPolka.server.close.mockClear(); 133 | global.clearInterval = jest.fn(); 134 | 135 | return broker.stop().then(() => { 136 | expect(clearInterval).toHaveBeenCalledTimes(0); 137 | 138 | expect(MockPolka.server.close).toHaveBeenCalledTimes(1); 139 | expect(MockPolka.server.close).toHaveBeenCalledWith(); 140 | }); 141 | }); 142 | }); 143 | 144 | }); 145 | 146 | describe("Test event listeners", () => { 147 | const broker = new ServiceBroker({ logger: false }); 148 | const service = broker.createService(PromService); 149 | service.update = jest.fn(); 150 | service.updateCommonValues = jest.fn(); 151 | 152 | beforeAll(() => broker.start()); 153 | afterAll(() => broker.stop()); 154 | 155 | describe("emit 'metrics.trace.span.finish'", () => { 156 | 157 | it("should update request metrics", () => { 158 | 159 | const payload = { name: "posts.find", service: "posts", nodeID: "node-2", duration: 31.56 }; 160 | broker.emit("metrics.trace.span.finish", payload); 161 | 162 | expect(service.update).toHaveBeenCalledTimes(2); 163 | expect(service.update).toHaveBeenCalledWith("moleculer_req_total", "inc", { action: "posts.find", service: "posts", nodeID: "node-2" }); 164 | expect(service.update).toHaveBeenCalledWith("moleculer_req_duration_ms", "observe", { action: "posts.find", service: "posts", nodeID: "node-2" }, payload.duration); 165 | }); 166 | 167 | it("should update request metrics", () => { 168 | service.update.mockClear(); 169 | 170 | const payload = { name: "posts.find", service: "posts", nodeID: "node-2", duration: 31.56, error: { 171 | code: 501, 172 | type: "NOT_FOUND", 173 | name: "MoleculerError" 174 | } }; 175 | broker.emit("metrics.trace.span.finish", payload); 176 | 177 | expect(service.update).toHaveBeenCalledTimes(3); 178 | expect(service.update).toHaveBeenCalledWith("moleculer_req_total", "inc", { action: "posts.find", service: "posts", nodeID: "node-2" }); 179 | expect(service.update).toHaveBeenCalledWith("moleculer_req_duration_ms", "observe", { action: "posts.find", service: "posts", nodeID: "node-2" }, payload.duration); 180 | expect(service.update).toHaveBeenCalledWith("moleculer_req_errors_total", "inc", { 181 | action: "posts.find", 182 | service: "posts", 183 | nodeID: "node-2", 184 | errorCode: 501, 185 | errorName: "MoleculerError", 186 | errorType: "NOT_FOUND" 187 | }); 188 | }); 189 | }); 190 | 191 | describe("emit 'metrics.update'", () => { 192 | 193 | it("should update metric value", () => { 194 | service.update.mockClear(); 195 | 196 | const payload = { name: "transporter.packet.count", method: "set", labels: { type: "sent" }, value: 123, timestamp: Date.now() }; 197 | broker.emit("metrics.update", payload); 198 | 199 | expect(service.update).toHaveBeenCalledTimes(1); 200 | expect(service.update).toHaveBeenCalledWith("transporter.packet.count", "set", { type: "sent" }, 123, payload.timestamp); 201 | }); 202 | 203 | }); 204 | 205 | describe("emit '$services.changed'", () => { 206 | 207 | it("should update metric value", () => { 208 | service.updateCommonValues.mockClear(); 209 | 210 | broker.emit("$services.changed"); 211 | 212 | expect(service.updateCommonValues).toHaveBeenCalledTimes(1); 213 | expect(service.updateCommonValues).toHaveBeenCalledWith(); 214 | }); 215 | 216 | }); 217 | 218 | describe("emit '$node.connected'", () => { 219 | 220 | it("should update metric value", () => { 221 | service.updateCommonValues.mockClear(); 222 | 223 | broker.emit("$node.connected"); 224 | 225 | expect(service.updateCommonValues).toHaveBeenCalledTimes(1); 226 | expect(service.updateCommonValues).toHaveBeenCalledWith(); 227 | }); 228 | 229 | }); 230 | 231 | describe("emit '$node.disconnected'", () => { 232 | 233 | it("should update metric value", () => { 234 | service.updateCommonValues.mockClear(); 235 | 236 | broker.emit("$node.disconnected"); 237 | 238 | expect(service.updateCommonValues).toHaveBeenCalledTimes(1); 239 | expect(service.updateCommonValues).toHaveBeenCalledWith(); 240 | }); 241 | 242 | }); 243 | 244 | }); 245 | 246 | describe("Test common methods", () => { 247 | const broker = new ServiceBroker({ logger: false }); 248 | const service = broker.createService(PromService); 249 | 250 | beforeEach(() => broker.start()); 251 | afterEach(() => broker.stop()); 252 | 253 | it("should give back the service name from payload", () => { 254 | expect(service.getServiceName({ service: "serviceA" })).toBe("serviceA"); 255 | expect(service.getServiceName({ action: { name: "serviceB.actionC" }})).toBe("serviceB"); 256 | expect(service.getServiceName({ action: { name: "serviceB.actionC" }})).toBe("serviceB"); 257 | expect(service.getServiceName({ action: { name: "service.nested.action" }})).toBe("service.nested"); 258 | expect(service.getServiceName({ service: { name: "serviceD", version: 3 }})).toBe("serviceD"); 259 | }); 260 | 261 | it("should give back the span name from payload", () => { 262 | expect(service.getSpanName({ name: "custom-event" })).toBe("custom-event"); 263 | expect(service.getSpanName({ action: { name: "service.action" }})).toBe("service.action"); 264 | }); 265 | 266 | }); 267 | 268 | describe("Test createMetrics method", () => { 269 | const broker = new ServiceBroker({ logger: false }); 270 | const service = broker.createService(PromService); 271 | 272 | beforeEach(() => { 273 | broker.start(); 274 | 275 | service.updateCommonValues = jest.fn(); 276 | Prometheus.Gauge.mockClear(); 277 | Prometheus.Counter.mockClear(); 278 | Prometheus.Histogram.mockClear(); 279 | }); 280 | afterEach(() => broker.stop()); 281 | 282 | it("should not create metric objects if empty", () => { 283 | service.createMetrics(); 284 | 285 | expect(service.updateCommonValues).toHaveBeenCalledTimes(0); 286 | 287 | expect(service.metrics).toEqual({}); 288 | }); 289 | 290 | it("should create metric objects according to metrics settings", () => { 291 | service.createMetrics({ 292 | "custom_val1": { type: "Gauge", labelNames: [ "nodeID", "service"], help: "Custom value 1" }, 293 | "custom_val2": { type: "Counter", labelNames: [ "nodeID" ], help: "Custom value 2" }, 294 | "custom_val3": { type: "Histogram", labelNames: [ "nodeID" ], help: "Custom value 3" }, 295 | "custom_val4": null 296 | }); 297 | 298 | expect(Prometheus.Gauge).toHaveBeenCalledTimes(1); 299 | expect(Prometheus.Gauge).toHaveBeenCalledWith({"name": "custom_val1", "labelNames": ["nodeID", "service"], "help": "Custom value 1", "type": "Gauge", "registers":[]}); 300 | 301 | expect(Prometheus.Counter).toHaveBeenCalledTimes(1); 302 | expect(Prometheus.Counter).toHaveBeenCalledWith({"name": "custom_val2", "labelNames": ["nodeID"], "help": "Custom value 2", "type": "Counter", "registers":[]}); 303 | 304 | expect(Prometheus.Histogram).toHaveBeenCalledTimes(1); 305 | expect(Prometheus.Histogram).toHaveBeenCalledWith({"name": "custom_val3", "labelNames": ["nodeID"], "help": "Custom value 3", "type": "Histogram", "registers":[]}); 306 | 307 | expect(service.metrics).toEqual({ 308 | custom_val1: jasmine.any(Prometheus.Gauge), 309 | custom_val2: jasmine.any(Prometheus.Counter), 310 | custom_val3: jasmine.any(Prometheus.Histogram) 311 | }); 312 | }); 313 | 314 | }); 315 | 316 | describe("Test updateCommonValues method", () => { 317 | const broker = new ServiceBroker({ logger: false }); 318 | const service = broker.createService(PromService); 319 | 320 | beforeAll(() => { 321 | return broker.start().then(() => { 322 | broker.mcall = jest.fn(() => Promise.resolve({ 323 | nodes: [ 324 | { id: "node-1", available: true, client: { type: "node", version: "0.12.1", langVersion: "8.10.0" } }, 325 | { id: "node-2", available: false, client: { type: "node", version: "0.12.1", langVersion: "8.10.0" } } 326 | ], 327 | services: [ 328 | { name: "posts", version: 2, nodes: [1,2] }, 329 | { name: "users", nodes: [] }, 330 | ], 331 | actions: [ 332 | { name: "posts.find", endpoints: [1,2,3] }, 333 | { name: "users.find" } 334 | ], 335 | events: [ 336 | { name: "user.created", group: "users" }, 337 | { name: "post.created", group: "posts", endpoints: [1,2,3] }, 338 | ] 339 | })); 340 | 341 | service.update = jest.fn(); 342 | }); 343 | }); 344 | afterAll(() => broker.stop()); 345 | 346 | it("should call service.update method", () => { 347 | service.metrics = {}; 348 | service.update.mockClear(); 349 | 350 | return service.updateCommonValues().then(() => { 351 | 352 | expect(broker.mcall).toHaveBeenCalledTimes(1); 353 | expect(broker.mcall).toHaveBeenCalledWith({ 354 | nodes: { action: "$node.list" }, 355 | services: { action: "$node.services", params: { withActions: false, grouping: true, skipInternal: true } }, 356 | actions: { action: "$node.actions", params: { withEndpoints: true, skipInternal: true } }, 357 | events: { action: "$node.events", params: { withEndpoints: true, skipInternal: true } } 358 | }); 359 | 360 | expect(service.update).toHaveBeenCalledTimes(12); 361 | 362 | expect(service.update).toHaveBeenCalledWith("moleculer_nodes_total", "set", null, 1); 363 | expect(service.update).toHaveBeenCalledWith("moleculer_nodes", "set", { nodeID: "node-1", type: "node", version: "0.12.1", langVersion: "8.10.0" }, 1); 364 | expect(service.update).toHaveBeenCalledWith("moleculer_nodes", "set", { nodeID: "node-2", type: "node", version: "0.12.1", langVersion: "8.10.0" }, 0); 365 | 366 | expect(service.update).toHaveBeenCalledWith("moleculer_services_total", "set", null, 2); 367 | expect(service.update).toHaveBeenCalledWith("moleculer_service_endpoints_total", "set", { service: "posts", version: 2 }, 2); 368 | expect(service.update).toHaveBeenCalledWith("moleculer_service_endpoints_total", "set", { service: "users", version: undefined }, 0); 369 | 370 | expect(service.update).toHaveBeenCalledWith("moleculer_actions_total", "set", null, 2); 371 | expect(service.update).toHaveBeenCalledWith("moleculer_action_endpoints_total", "set", { action: "posts.find" }, 3); 372 | expect(service.update).toHaveBeenCalledWith("moleculer_action_endpoints_total", "set", { action: "users.find" }, 0); 373 | 374 | expect(service.update).toHaveBeenCalledWith("moleculer_events_total", "set", null, 2); 375 | expect(service.update).toHaveBeenCalledWith("moleculer_event_endpoints_total", "set", { event: "user.created", group: "users" }, 0); 376 | expect(service.update).toHaveBeenCalledWith("moleculer_event_endpoints_total", "set", { event: "post.created", group: "posts" }, 3); 377 | 378 | }); 379 | }); 380 | 381 | }); 382 | 383 | describe("Test update method", () => { 384 | const broker = new ServiceBroker({ logger: false }); 385 | const service = broker.createService(PromService); 386 | 387 | beforeEach(() => broker.start()); 388 | afterEach(() => broker.stop()); 389 | 390 | it("should not update value if not exists", () => { 391 | service.metrics = {}; 392 | service.update("custom_data", "set", null, 5); 393 | }); 394 | 395 | it("should update metric value", () => { 396 | service.metrics = { 397 | metric_1: { 398 | set: jest.fn() 399 | }, 400 | 401 | metric_2: { 402 | inc: jest.fn() 403 | } 404 | }; 405 | 406 | service.update("metric_1", "set", { a: 5 }, 120, 123456); 407 | 408 | expect(service.metrics.metric_1.set).toHaveBeenCalledTimes(1); 409 | expect(service.metrics.metric_1.set).toHaveBeenCalledWith({ a: 5 }, 120, 123456); 410 | 411 | service.update("metric_2", "inc", null, 5, 123456); 412 | 413 | expect(service.metrics.metric_2.inc).toHaveBeenCalledTimes(1); 414 | expect(service.metrics.metric_2.inc).toHaveBeenCalledWith(5, 123456); 415 | }); 416 | 417 | }); 418 | -------------------------------------------------------------------------------- /packages/moleculer-jaeger/test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | jest.mock("jaeger-client"); 4 | jest.mock("jaeger-client/dist/src/samplers/const_sampler"); 5 | jest.mock("jaeger-client/dist/src/samplers/guaranteed_throughput_sampler"); 6 | jest.mock("jaeger-client/dist/src/samplers/remote_sampler"); 7 | jest.mock("jaeger-client/dist/src/reporters/udp_sender"); 8 | 9 | const Jaeger = require("jaeger-client"); 10 | const GuaranteedThroughputSampler = require("jaeger-client/dist/src/samplers/guaranteed_throughput_sampler").default; 11 | const RemoteControlledSampler = require("jaeger-client/dist/src/samplers/remote_sampler").default; 12 | const UDPSender = require("jaeger-client/dist/src/reporters/udp_sender").default; 13 | 14 | const { ServiceBroker } = require("moleculer"); 15 | const JaegerService = require("../../src"); 16 | 17 | describe("Test JaegerService constructor", () => { 18 | const broker = new ServiceBroker({ logger: false }); 19 | const service = broker.createService(JaegerService); 20 | 21 | it("should be created", () => { 22 | expect(service).toBeDefined(); 23 | expect(service.tracers).toBeInstanceOf(Object); 24 | }); 25 | 26 | }); 27 | 28 | describe("Test JaegerService started & stopped", () => { 29 | 30 | const broker = new ServiceBroker({ logger: false }); 31 | const service = broker.createService(JaegerService); 32 | 33 | beforeAll(() => broker.start()); 34 | 35 | it("should start timer", () => { 36 | expect(service).toBeDefined(); 37 | // No logic in `started` 38 | }); 39 | 40 | it("should destroy timer", () => { 41 | const close = jest.fn(); 42 | service.tracers.first = { close }; 43 | service.tracers.second = { close }; 44 | return broker.stop().then(() => { 45 | expect(service.tracers).toEqual({}); 46 | expect(close).toHaveBeenCalledTimes(2); 47 | expect(close).toHaveBeenCalledWith(); 48 | }); 49 | }); 50 | 51 | }); 52 | 53 | describe("Test event listener", () => { 54 | const broker = new ServiceBroker({ logger: false }); 55 | 56 | beforeAll(() => broker.start()); 57 | afterAll(() => broker.stop()); 58 | 59 | it("should call makePayload method", () => { 60 | const service = broker.createService(JaegerService); 61 | service.makePayload = jest.fn(); 62 | 63 | return broker.Promise.delay(100).then(() => { 64 | const payload = { a: 5 }; 65 | broker.emit("metrics.trace.span.finish", payload); 66 | 67 | expect(service.makePayload).toHaveBeenCalledTimes(1); 68 | expect(service.makePayload).toHaveBeenCalledWith(payload); 69 | }); 70 | }); 71 | 72 | }); 73 | 74 | describe("Test common methods", () => { 75 | const broker = new ServiceBroker({ logger: false }); 76 | const service = broker.createService(JaegerService); 77 | 78 | beforeEach(() => broker.start()); 79 | afterEach(() => broker.stop()); 80 | 81 | it("should give back the service name from payload", () => { 82 | expect(service.getServiceName({ service: "serviceA" })).toBe("serviceA"); 83 | expect(service.getServiceName({ action: { name: "serviceB.actionC" } })).toBe("serviceB"); 84 | expect(service.getServiceName({ action: { name: "serviceB.actionC" } })).toBe("serviceB"); 85 | expect(service.getServiceName({ action: { name: "service.nested.action" } })).toBe("service.nested"); 86 | expect(service.getServiceName({ service: { name: "serviceD", version: 3 } })).toBe("serviceD"); 87 | }); 88 | 89 | it("should give back the span name from payload", () => { 90 | expect(service.getSpanName({ name: "custom-event" })).toBe("custom-event"); 91 | expect(service.getSpanName({ action: { name: "service.action" } })).toBe("service.action"); 92 | }); 93 | 94 | it("should convert context ID to Zipkin ID", () => { 95 | expect(service.convertID("12345-67890-abcdef-12345")).toEqual(Buffer.from([18, 52, 86, 120, 144, 171, 205, 239])); 96 | expect(service.convertID()).toBe(null); 97 | }); 98 | 99 | }); 100 | 101 | describe("Test payload creating", () => { 102 | const broker = new ServiceBroker({ logger: false }); 103 | const service = broker.createService(JaegerService); 104 | 105 | beforeEach(() => broker.start()); 106 | afterEach(() => broker.stop()); 107 | 108 | it("test addTags method with string", () => { 109 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 110 | service.addTags(span, "first", "Hello"); 111 | 112 | expect(span).toEqual({ 113 | first: "Hello", 114 | setTag: jasmine.any(Function) 115 | }); 116 | }); 117 | 118 | it("test addTags method with number", () => { 119 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 120 | service.addTags(span, "first", 500.32); 121 | 122 | expect(span).toEqual({ 123 | first: 500.32, 124 | setTag: jasmine.any(Function) 125 | }); 126 | }); 127 | 128 | it("test addTags method with boolean", () => { 129 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 130 | service.addTags(span, "first", true); 131 | 132 | expect(span).toEqual({ 133 | first: true, 134 | setTag: jasmine.any(Function) 135 | }); 136 | }); 137 | 138 | it("test addTags method with null", () => { 139 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 140 | service.addTags(span, "first", null); 141 | 142 | expect(span).toEqual({ 143 | "first": null, 144 | setTag: jasmine.any(Function) 145 | }); 146 | }); 147 | 148 | it("test addTags method with undefined", () => { 149 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 150 | service.addTags(span, "first", undefined); 151 | 152 | expect(span).toEqual({ 153 | setTag: jasmine.any(Function) 154 | }); 155 | }); 156 | 157 | it("test addTags method with object", () => { 158 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 159 | service.addTags(span, "first", { a: 5, b: { c: "John", d: true } }); 160 | 161 | expect(span).toEqual({ 162 | "first.a": 5, 163 | "first.b.c": "John", 164 | "first.b.d": true, 165 | setTag: jasmine.any(Function) 166 | }); 167 | }); 168 | 169 | it("test addTags method with array", () => { 170 | const span = { setTag: jest.fn((name, value) => span[name] = value) }; 171 | service.addTags(span, "first", ["John", "Jane"]); 172 | 173 | expect(span).toEqual({ 174 | "first.0": "John", 175 | "first.1": "Jane", 176 | setTag: jasmine.any(Function) 177 | }); 178 | }); 179 | 180 | it("should generate v2 Zipkin span", () => { 181 | const payload = { 182 | id: "48be6b2f-0ee6-4851-a326-ff92fdd98045", 183 | requestID: "60ff2991-5c67-4a1d-8022-70a95be86039", 184 | level: 2, 185 | startTime: 1520505261078, 186 | endTime: 1520505261142.4363, 187 | duration: 64.436225, 188 | remoteCall: false, 189 | fromCache: false, 190 | params: { postID: 3 }, 191 | meta: { user: { name: "John" } }, 192 | action: { name: "votes.count" }, 193 | parent: "60ff2991-5c67-4a1d-8022-70a95be86039", 194 | nodeID: "node-100" 195 | }; 196 | 197 | const spanCtx = {}; 198 | 199 | const span = { 200 | finish: jest.fn(), 201 | context: jest.fn(() => spanCtx) 202 | }; 203 | 204 | const startSpan = jest.fn(() => span); 205 | service.getTracer = jest.fn(() => ({ startSpan })); 206 | 207 | service.addTags = jest.fn(); 208 | 209 | service.makePayload(payload); 210 | 211 | expect(service.getTracer).toHaveBeenCalledTimes(1); 212 | expect(service.getTracer).toHaveBeenCalledWith("votes"); 213 | 214 | expect(Jaeger.SpanContext).toHaveBeenCalledTimes(1); 215 | expect(Jaeger.SpanContext).toHaveBeenCalledWith( 216 | Buffer.from([96, 255, 41, 145, 92, 103, 74, 29]), 217 | Buffer.from([96, 255, 41, 145, 92, 103, 74, 29]), 218 | null, 219 | null, 220 | null, 221 | null, 222 | 1, 223 | {}, 224 | "" 225 | ); 226 | 227 | expect(startSpan).toHaveBeenCalledTimes(1); 228 | expect(startSpan).toHaveBeenCalledWith("votes.count", { 229 | startTime: 1520505261078, 230 | childOf: jasmine.any(Jaeger.SpanContext), 231 | tags: { 232 | nodeID: "node-100", 233 | level: 2, 234 | remoteCall: false 235 | } 236 | }); 237 | 238 | expect(service.addTags).toHaveBeenCalledTimes(5); 239 | expect(service.addTags).toHaveBeenCalledWith(span, "service", "votes"); 240 | expect(service.addTags).toHaveBeenCalledWith(span, "action", "votes.count"); 241 | expect(service.addTags).toHaveBeenCalledWith(span, "params", { "postID": 3 }); 242 | expect(service.addTags).toHaveBeenCalledWith(span, "meta", { "user": { "name": "John" } }); 243 | 244 | expect(span.context).toHaveBeenCalledTimes(1); 245 | expect(span.context).toHaveBeenCalledWith(); 246 | 247 | expect(spanCtx.traceId).toEqual(Buffer.from([96, 255, 41, 145, 92, 103, 74, 29])); 248 | expect(spanCtx.spanId).toEqual(Buffer.from([72, 190, 107, 47, 14, 230, 72, 81])); 249 | 250 | expect(span.finish).toHaveBeenCalledTimes(1); 251 | expect(span.finish).toHaveBeenCalledWith(1520505261142.4363); 252 | 253 | }); 254 | 255 | it("should generate v2 Zipkin span with error", () => { 256 | Jaeger.SpanContext.mockClear(); 257 | 258 | const payload = { 259 | id: "48be6b2f-0ee6-4851-a326-ff92fdd98045", 260 | requestID: "60ff2991-5c67-4a1d-8022-70a95be86039", 261 | level: 2, 262 | startTime: 1520505261078, 263 | endTime: 1520505261142.4363, 264 | duration: 64.436225, 265 | remoteCall: false, 266 | fromCache: false, 267 | meta: { user: { name: "John" } }, 268 | action: { name: "votes.count" }, 269 | parent: "60ff2991-5c67-4a1d-8022-70a95be86039", 270 | nodeID: "node-100", 271 | callerNodeID: "node-99", 272 | error: { 273 | message: "Something went wrong!", 274 | code: 401, 275 | type: "WRONG_THING", 276 | data: { 277 | a: 5 278 | }, 279 | stack: "error stack" 280 | } 281 | }; 282 | 283 | 284 | const spanCtx = {}; 285 | 286 | const span = { 287 | finish: jest.fn(), 288 | context: jest.fn(() => spanCtx) 289 | }; 290 | 291 | const startSpan = jest.fn(() => span); 292 | service.getTracer = jest.fn(() => ({ startSpan })); 293 | 294 | service.addTags = jest.fn(); 295 | 296 | service.makePayload(payload); 297 | 298 | expect(service.getTracer).toHaveBeenCalledTimes(1); 299 | expect(service.getTracer).toHaveBeenCalledWith("votes"); 300 | 301 | expect(Jaeger.SpanContext).toHaveBeenCalledTimes(1); 302 | expect(Jaeger.SpanContext).toHaveBeenCalledWith( 303 | Buffer.from([96, 255, 41, 145, 92, 103, 74, 29]), 304 | Buffer.from([96, 255, 41, 145, 92, 103, 74, 29]), 305 | null, 306 | null, 307 | null, 308 | null, 309 | 1, 310 | {}, 311 | "" 312 | ); 313 | 314 | expect(startSpan).toHaveBeenCalledTimes(1); 315 | expect(startSpan).toHaveBeenCalledWith("votes.count", { 316 | startTime: 1520505261078, 317 | childOf: jasmine.any(Jaeger.SpanContext), 318 | tags: { 319 | nodeID: "node-100", 320 | level: 2, 321 | remoteCall: false 322 | } 323 | }); 324 | 325 | expect(service.addTags).toHaveBeenCalledTimes(11); 326 | expect(service.addTags).toHaveBeenCalledWith(span, "service", "votes"); 327 | expect(service.addTags).toHaveBeenCalledWith(span, "action", "votes.count"); 328 | expect(service.addTags).toHaveBeenCalledWith(span, "meta", { "user": { "name": "John" } }); 329 | expect(service.addTags).toHaveBeenCalledWith(span, "callerNodeID", "node-99"); 330 | 331 | expect(service.addTags).toHaveBeenCalledWith(span, "error", true); 332 | expect(service.addTags).toHaveBeenCalledWith(span, "error.message", "Something went wrong!"); 333 | expect(service.addTags).toHaveBeenCalledWith(span, "error.type", "WRONG_THING"); 334 | expect(service.addTags).toHaveBeenCalledWith(span, "error.code", 401); 335 | expect(service.addTags).toHaveBeenCalledWith(span, "error.data", { a: 5 }); 336 | expect(service.addTags).toHaveBeenCalledWith(span, "error.stack", "error stack"); 337 | 338 | expect(span.context).toHaveBeenCalledTimes(1); 339 | expect(span.context).toHaveBeenCalledWith(); 340 | 341 | expect(spanCtx.traceId).toEqual(Buffer.from([96, 255, 41, 145, 92, 103, 74, 29])); 342 | expect(spanCtx.spanId).toEqual(Buffer.from([72, 190, 107, 47, 14, 230, 72, 81])); 343 | 344 | expect(span.finish).toHaveBeenCalledTimes(1); 345 | expect(span.finish).toHaveBeenCalledWith(1520505261142.4363); 346 | }); 347 | }); 348 | 349 | describe("Test getSampler method", () => { 350 | 351 | const broker = new ServiceBroker({ logger: false }); 352 | const service = broker.createService(JaegerService); 353 | 354 | beforeEach(() => broker.start()); 355 | afterEach(() => broker.stop()); 356 | 357 | it("should return sampler func", () => { 358 | service.settings.sampler = () => {}; 359 | expect(service.getSampler()).toBe(service.settings.sampler); 360 | }); 361 | 362 | it("should return RateLimitingSampler", () => { 363 | service.settings.sampler = { 364 | type: "RateLimiting", 365 | options: { 366 | maxTracesPerSecond: 123, 367 | initBalance: 22 368 | } 369 | }; 370 | 371 | service.getSampler(); 372 | 373 | expect(Jaeger.RateLimitingSampler).toHaveBeenCalledTimes(1); 374 | expect(Jaeger.RateLimitingSampler).toHaveBeenCalledWith(123, 22); 375 | }); 376 | 377 | it("should return ProbabilisticSampler", () => { 378 | service.settings.sampler = { 379 | type: "Probabilistic", 380 | options: { 381 | samplingRate: 123 382 | } 383 | }; 384 | 385 | service.getSampler(); 386 | 387 | expect(Jaeger.ProbabilisticSampler).toHaveBeenCalledTimes(1); 388 | expect(Jaeger.ProbabilisticSampler).toHaveBeenCalledWith(123); 389 | }); 390 | 391 | it("should return GuaranteedThroughputSampler", () => { 392 | service.settings.sampler = { 393 | type: "GuaranteedThroughput", 394 | options: { 395 | lowerBound: 22, 396 | samplingRate: 33 397 | } 398 | }; 399 | 400 | service.getSampler(); 401 | 402 | expect(GuaranteedThroughputSampler).toHaveBeenCalledTimes(1); 403 | expect(GuaranteedThroughputSampler).toHaveBeenCalledWith(22, 33); 404 | }); 405 | 406 | it("should return RemoteControlledSampler", () => { 407 | service.settings.sampler = { 408 | type: "RemoteControlled", 409 | options: { 410 | a: 5 411 | } 412 | }; 413 | 414 | service.getSampler("posts"); 415 | 416 | expect(RemoteControlledSampler).toHaveBeenCalledTimes(1); 417 | expect(RemoteControlledSampler).toHaveBeenCalledWith("posts", service.settings.sampler.options); 418 | }); 419 | 420 | it("should return ConstSampler with default config", () => { 421 | service.settings.sampler = {}; 422 | 423 | service.getSampler(); 424 | 425 | expect(Jaeger.ConstSampler).toHaveBeenCalledTimes(1); 426 | expect(Jaeger.ConstSampler).toHaveBeenCalledWith(1); 427 | }); 428 | 429 | it("should return ConstSampler with default config", () => { 430 | Jaeger.ConstSampler.mockClear(); 431 | 432 | service.settings.sampler = { 433 | type: "Const", 434 | options: { 435 | decision: 0 436 | } 437 | }; 438 | 439 | service.getSampler(); 440 | 441 | expect(Jaeger.ConstSampler).toHaveBeenCalledTimes(1); 442 | expect(Jaeger.ConstSampler).toHaveBeenCalledWith(0); 443 | }); 444 | 445 | }); 446 | 447 | describe("Test getReporter method", () => { 448 | 449 | const broker = new ServiceBroker({ logger: false }); 450 | const service = broker.createService(JaegerService); 451 | 452 | beforeEach(() => broker.start()); 453 | afterEach(() => broker.stop()); 454 | 455 | it("should return RemoteReporter", () => { 456 | service.settings = { 457 | host: "192.168.0.100", 458 | port: 6789 459 | }; 460 | 461 | service.getReporter(); 462 | 463 | expect(Jaeger.RemoteReporter).toHaveBeenCalledTimes(1); 464 | expect(Jaeger.RemoteReporter).toHaveBeenCalledWith(jasmine.any(UDPSender)); 465 | 466 | expect(UDPSender).toHaveBeenCalledTimes(1); 467 | expect(UDPSender).toHaveBeenCalledWith({ host: "192.168.0.100", port: 6789 }); 468 | }); 469 | }); 470 | 471 | describe("Test getTracer method", () => { 472 | 473 | const broker = new ServiceBroker({ logger: false }); 474 | const service = broker.createService(JaegerService); 475 | 476 | let tracer; 477 | 478 | beforeAll(() => broker.start()); 479 | afterAll(() => broker.stop()); 480 | 481 | it("should create a new tracer", () => { 482 | const sampler = { a: 5 }; 483 | const reporter = { b: 6 }; 484 | service.getSampler = jest.fn(() => sampler); 485 | service.getReporter = jest.fn(() => reporter); 486 | 487 | service.settings.options = { 488 | c: 7 489 | }; 490 | 491 | tracer = service.getTracer("posts"); 492 | 493 | expect(tracer).toBeInstanceOf(Jaeger.Tracer); 494 | 495 | expect(service.getSampler).toHaveBeenCalledTimes(1); 496 | expect(service.getSampler).toHaveBeenCalledWith(); 497 | 498 | expect(service.getReporter).toHaveBeenCalledTimes(1); 499 | expect(service.getReporter).toHaveBeenCalledWith(); 500 | 501 | expect(Jaeger.Tracer).toHaveBeenCalledTimes(1); 502 | expect(Jaeger.Tracer).toHaveBeenCalledWith("posts", reporter, sampler, service.settings.options); 503 | 504 | expect(service.tracers).toEqual({ 505 | "posts": tracer 506 | }); 507 | }); 508 | 509 | it("should return the previous tracer", () => { 510 | service.getSampler.mockClear(); 511 | service.getReporter.mockClear(); 512 | Jaeger.Tracer.mockClear(); 513 | 514 | const tracer2 = service.getTracer("posts"); 515 | 516 | expect(tracer2).toBe(tracer); 517 | 518 | expect(service.getSampler).toHaveBeenCalledTimes(0); 519 | 520 | expect(service.getReporter).toHaveBeenCalledTimes(0); 521 | 522 | expect(Jaeger.Tracer).toHaveBeenCalledTimes(0); 523 | 524 | expect(service.tracers).toEqual({ 525 | "posts": tracer 526 | }); 527 | }); 528 | }); 529 | -------------------------------------------------------------------------------- /packages/moleculer-zipkin/test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const axios = require("axios"); 4 | const { ServiceBroker } = require("moleculer"); 5 | const ZipkinService = require("../../src"); 6 | 7 | const lolex = require("lolex"); 8 | 9 | describe("Test ZipkinService constructor", () => { 10 | const broker = new ServiceBroker({ logger: false }); 11 | const service = broker.createService(ZipkinService); 12 | 13 | it("should be created", () => { 14 | expect(service).toBeDefined(); 15 | expect(service.queue).toBeInstanceOf(Array); 16 | }); 17 | 18 | }); 19 | 20 | describe("Test ZipkinService started & stopped", () => { 21 | 22 | describe("with batchTime", () => { 23 | const broker = new ServiceBroker({ logger: false }); 24 | const service = broker.createService(ZipkinService); 25 | 26 | beforeAll(() => broker.start()); 27 | 28 | it("should start timer", () => { 29 | expect(service).toBeDefined(); 30 | expect(service.axios).toBeDefined(); 31 | expect(service.timer).toBeDefined(); 32 | 33 | return broker.stop(); 34 | }); 35 | 36 | it("should destroy timer", () => { 37 | expect(service.timer).toBeNull(); 38 | }); 39 | 40 | }); 41 | 42 | describe("with batchTime & queued items", () => { 43 | const broker = new ServiceBroker({ logger: false }); 44 | const service = broker.createService(ZipkinService); 45 | 46 | beforeAll(() => broker.start()); 47 | 48 | it("should call sendFromQueue at stopping", () => { 49 | service.sendFromQueue = jest.fn(); 50 | 51 | service.queue.push({}); 52 | 53 | return broker.stop().then(() => { 54 | expect(service.sendFromQueue).toHaveBeenCalledTimes(1); 55 | expect(service.sendFromQueue).toHaveBeenCalledWith(); 56 | }); 57 | }); 58 | 59 | }); 60 | 61 | describe("without batchTime", () => { 62 | const broker = new ServiceBroker({ logger: false }); 63 | const service = broker.createService(ZipkinService, { 64 | settings: { 65 | batchTime: 0 66 | } 67 | }); 68 | 69 | beforeAll(() => broker.start()); 70 | afterAll(() => broker.stop()); 71 | 72 | it("should not create timer", () => { 73 | expect(service).toBeDefined(); 74 | expect(service.axios).toBeDefined(); 75 | expect(service.timer).toBeUndefined(); 76 | }); 77 | 78 | }); 79 | 80 | }); 81 | 82 | describe("Test event listener", () => { 83 | const broker = new ServiceBroker({ logger: false }); 84 | 85 | beforeAll(() => broker.start()); 86 | afterAll(() => broker.stop()); 87 | 88 | it("should call makeZipkinPayloadV1 method", () => { 89 | const service = broker.createService(ZipkinService, { 90 | settings: { 91 | version: "v1" 92 | } 93 | }); 94 | service.makeZipkinPayloadV1 = jest.fn(); 95 | service.makeZipkinPayloadV2 = jest.fn(); 96 | 97 | return broker.Promise.delay(100).then(() => { 98 | const payload = { a: 5 }; 99 | broker.emit("metrics.trace.span.finish", payload); 100 | 101 | expect(service.makeZipkinPayloadV1).toHaveBeenCalledTimes(1); 102 | expect(service.makeZipkinPayloadV1).toHaveBeenCalledWith(payload); 103 | 104 | expect(service.makeZipkinPayloadV2).toHaveBeenCalledTimes(0); 105 | }); 106 | }); 107 | 108 | it("should call makeZipkinPayloadV2 method", () => { 109 | const service = broker.createService(ZipkinService, { 110 | name: "zipkin-v2", 111 | settings: { 112 | version: "v2" 113 | } 114 | }); 115 | service.makeZipkinPayloadV1 = jest.fn(); 116 | service.makeZipkinPayloadV2 = jest.fn(); 117 | 118 | return broker.Promise.delay(100).then(() => { 119 | const payload = { a: 5 }; 120 | broker.emit("metrics.trace.span.finish", payload); 121 | 122 | expect(service.makeZipkinPayloadV2).toHaveBeenCalledTimes(1); 123 | expect(service.makeZipkinPayloadV2).toHaveBeenCalledWith(payload); 124 | 125 | expect(service.makeZipkinPayloadV1).toHaveBeenCalledTimes(0); 126 | }); 127 | }); 128 | 129 | }); 130 | 131 | describe("Test common methods", () => { 132 | const broker = new ServiceBroker({ logger: false }); 133 | const service = broker.createService(ZipkinService); 134 | 135 | beforeEach(() => broker.start()); 136 | afterEach(() => broker.stop()); 137 | 138 | it("should give back the service name from payload", () => { 139 | expect(service.getServiceName({ service: "serviceA" })).toBe("serviceA"); 140 | expect(service.getServiceName({ action: { name: "serviceB.actionC" }})).toBe("serviceB"); 141 | expect(service.getServiceName({ action: { name: "serviceB.actionC" }})).toBe("serviceB"); 142 | expect(service.getServiceName({ action: { name: "service.nested.action" }})).toBe("service.nested"); 143 | expect(service.getServiceName({ service: { name: "serviceD", version: 3 }})).toBe("serviceD"); 144 | }); 145 | 146 | it("should give back the span name from payload", () => { 147 | expect(service.getSpanName({ name: "custom-event" })).toBe("custom-event"); 148 | expect(service.getSpanName({ action: { name: "service.action" }})).toBe("service.action"); 149 | }); 150 | 151 | it("should convert context ID to Zipkin ID", () => { 152 | expect(service.convertID("12345-67890-abcdef-12345")).toBe("1234567890abcdef"); 153 | expect(service.convertID()).toBe(null); 154 | }); 155 | 156 | it("should convert timestamp to Zipkin microseconds", () => { 157 | expect(service.convertTime(1234567890)).toBe(1234567890000); 158 | expect(service.convertTime(1234567890.74289)).toBe(1234567890743); 159 | }); 160 | 161 | }); 162 | 163 | describe("Test v1 payload creating", () => { 164 | const broker = new ServiceBroker({ logger: false }); 165 | const service = broker.createService(ZipkinService, { settings: { version: "v1" }}); 166 | 167 | beforeEach(() => broker.start()); 168 | afterEach(() => broker.stop()); 169 | 170 | it("test addBinaryAnnotation method with string", () => { 171 | const payload = { binaryAnnotations: [] }; 172 | service.addBinaryAnnotation(payload, "first", "Hello"); 173 | 174 | expect(payload).toEqual({ 175 | binaryAnnotations: [ 176 | { key: "first", value: "Hello" } 177 | ] 178 | }); 179 | }); 180 | 181 | it("test addBinaryAnnotation method with number", () => { 182 | const payload = { binaryAnnotations: [] }; 183 | service.addBinaryAnnotation(payload, "first", 500.32); 184 | 185 | expect(payload).toEqual({ 186 | binaryAnnotations: [ 187 | { key: "first", value: "500.32" } 188 | ] 189 | }); 190 | }); 191 | 192 | it("test addBinaryAnnotation method with boolean", () => { 193 | const payload = { binaryAnnotations: [] }; 194 | service.addBinaryAnnotation(payload, "first", true); 195 | 196 | expect(payload).toEqual({ 197 | binaryAnnotations: [ 198 | { key: "first", value: "true" } 199 | ] 200 | }); 201 | }); 202 | 203 | it("test addBinaryAnnotation method with object", () => { 204 | const payload = { binaryAnnotations: [] }; 205 | service.addBinaryAnnotation(payload, "first", { a: 5, b: { c: "John", d: true }}); 206 | 207 | expect(payload).toEqual({ 208 | binaryAnnotations: [ 209 | { key: "first.a", value: "5"}, 210 | { key: "first.b.c", value: "John"}, 211 | { key: "first.b.d", value: "true"} 212 | ] 213 | }); 214 | }); 215 | 216 | it("test addBinaryAnnotation method with array", () => { 217 | const payload = { binaryAnnotations: [] }; 218 | service.addBinaryAnnotation(payload, "first", ["John", "Jane"]); 219 | 220 | expect(payload).toEqual({ 221 | binaryAnnotations: [ 222 | { key: "first.0", value: "John"}, 223 | { key: "first.1", value: "Jane"} 224 | ] 225 | }); 226 | }); 227 | 228 | it("should generate v1 Zipkin span", () => { 229 | const payload = { 230 | id: "48be6b2f-0ee6-4851-a326-ff92fdd98045", 231 | requestID: "60ff2991-5c67-4a1d-8022-70a95be86039", 232 | level: 2, 233 | startTime: 1520505261078, 234 | endTime: 1520505261142.4363, 235 | duration: 64.436225, 236 | remoteCall: false, 237 | fromCache: false, 238 | params: { postID: 3 }, 239 | meta: { user: { name: "John" } }, 240 | action: { name: "votes.count" }, 241 | parent: "60ff2991-5c67-4a1d-8022-70a95be86039", 242 | nodeID: "node-100" 243 | }; 244 | 245 | service.enqueue = jest.fn(); 246 | 247 | service.makeZipkinPayloadV1(payload); 248 | 249 | expect(service.enqueue).toHaveBeenCalledTimes(1); 250 | expect(service.enqueue).toHaveBeenCalledWith({ 251 | "id": "48be6b2f0ee64851", 252 | "name": "votes.count", 253 | "parentId": "60ff29915c674a1d", 254 | "timestamp": 1520505261142436, 255 | "traceId": "60ff29915c674a1d", 256 | "annotations": [ 257 | { 258 | "endpoint": { 259 | "ipv4": "", 260 | "port": 0, 261 | "serviceName": "votes" 262 | }, 263 | "timestamp": 1520505261078000, 264 | "value": "sr" 265 | }, 266 | { 267 | "endpoint": { 268 | "ipv4": "", 269 | "port": 0, 270 | "serviceName": "votes" 271 | }, 272 | "timestamp": 1520505261142436, 273 | "value": "ss" 274 | } 275 | ], 276 | "binaryAnnotations": [ 277 | { 278 | "key": "nodeID", 279 | "value": "node-100" 280 | }, 281 | { 282 | "key": "level", 283 | "value": "2" 284 | }, 285 | { 286 | "key": "remoteCall", 287 | "value": "false" 288 | }, 289 | { 290 | "key": "callerNodeID", 291 | "value": "" 292 | }, 293 | { 294 | "key": "params.postID", 295 | "value": "3" 296 | }, 297 | { 298 | "key": "meta.user.name", 299 | "value": "John" 300 | } 301 | ] 302 | }); 303 | }); 304 | 305 | it("should generate v1 Zipkin span with error", () => { 306 | const payload = { 307 | id: "48be6b2f-0ee6-4851-a326-ff92fdd98045", 308 | requestID: "60ff2991-5c67-4a1d-8022-70a95be86039", 309 | level: 2, 310 | startTime: 1520505261078, 311 | endTime: 1520505261142.4363, 312 | duration: 64.436225, 313 | remoteCall: false, 314 | fromCache: false, 315 | meta: { user: { name: "John" } }, 316 | action: { name: "votes.count" }, 317 | parent: "60ff2991-5c67-4a1d-8022-70a95be86039", 318 | nodeID: "node-100", 319 | callerNodeID: "node-99", 320 | error: { 321 | message: "Something went wrong!", 322 | code: 401, 323 | type: "WRONG_THING", 324 | data: { 325 | a: 5 326 | }, 327 | stack: "error stack" 328 | } 329 | }; 330 | 331 | service.enqueue = jest.fn(); 332 | 333 | service.makeZipkinPayloadV1(payload); 334 | 335 | expect(service.enqueue).toHaveBeenCalledTimes(1); 336 | expect(service.enqueue).toHaveBeenCalledWith({ 337 | "id": "48be6b2f0ee64851", 338 | "name": "votes.count", 339 | "parentId": "60ff29915c674a1d", 340 | "timestamp": 1520505261142436, 341 | "traceId": "60ff29915c674a1d", 342 | "annotations": [ 343 | { 344 | "endpoint": { 345 | "ipv4": "", 346 | "port": 0, 347 | "serviceName": "votes" 348 | }, 349 | "timestamp": 1520505261078000, 350 | "value": "sr" 351 | }, 352 | { 353 | "endpoint": { 354 | "ipv4": "", 355 | "port": 0, 356 | "serviceName": "votes" 357 | }, 358 | "timestamp": 1520505261142436, 359 | "value": "ss" 360 | }, 361 | { 362 | "endpoint": { 363 | "ipv4": "", 364 | "port": 0, 365 | "serviceName": "votes" 366 | }, 367 | "timestamp": 1520505261142436, 368 | "value": "error" 369 | } 370 | ], 371 | "binaryAnnotations": [ 372 | { 373 | "key": "nodeID", 374 | "value": "node-100" 375 | }, 376 | { 377 | "key": "level", 378 | "value": "2" 379 | }, 380 | { 381 | "key": "remoteCall", 382 | "value": "false" 383 | }, 384 | { 385 | "key": "callerNodeID", 386 | "value": "node-99" 387 | }, 388 | { 389 | "key": "meta.user.name", 390 | "value": "John" 391 | }, 392 | { 393 | "key": "error", 394 | "value": "Something went wrong!" 395 | }, 396 | { 397 | "key": "error.type", 398 | "value": "WRONG_THING" 399 | }, 400 | { 401 | "key": "error.code", 402 | "value": "401" 403 | }, 404 | { 405 | "key": "error.data.a", 406 | "value": "5" 407 | }, 408 | { 409 | "key": "error.stack", 410 | "value": "error stack" 411 | } 412 | ], 413 | 414 | }); 415 | }); 416 | }); 417 | 418 | describe("Test v2 payload creating", () => { 419 | const broker = new ServiceBroker({ logger: false }); 420 | const service = broker.createService(ZipkinService, { settings: { version: "v2" }}); 421 | 422 | beforeEach(() => broker.start()); 423 | afterEach(() => broker.stop()); 424 | 425 | it("test addTags method with string", () => { 426 | const payload = { tags: {} }; 427 | service.addTags(payload, "first", "Hello"); 428 | 429 | expect(payload).toEqual({ 430 | tags: { 431 | first: "Hello" 432 | } 433 | }); 434 | }); 435 | 436 | it("test addTags method with number", () => { 437 | const payload = { tags: {} }; 438 | service.addTags(payload, "first", 500.32); 439 | 440 | expect(payload).toEqual({ 441 | tags: { 442 | first: "500.32" 443 | } 444 | }); 445 | }); 446 | 447 | it("test addTags method with boolean", () => { 448 | const payload = { tags: {} }; 449 | service.addTags(payload, "first", true); 450 | 451 | expect(payload).toEqual({ 452 | tags: { 453 | first: "true" 454 | } 455 | }); 456 | }); 457 | 458 | it("test addTags method with null", () => { 459 | const payload = { tags: {} }; 460 | service.addTags(payload, "first", null); 461 | 462 | expect(payload).toEqual({ 463 | tags: { 464 | first: "null" 465 | } 466 | }); 467 | }); 468 | 469 | it("test addTags method with undefined", () => { 470 | const payload = { tags: {} }; 471 | service.addTags(payload, "first", undefined); 472 | 473 | expect(payload).toEqual({ 474 | tags: { } 475 | }); 476 | }); 477 | 478 | it("test addTags method with object", () => { 479 | const payload = { tags: {} }; 480 | service.addTags(payload, "first", { a: 5, b: { c: "John", d: true }}); 481 | 482 | expect(payload).toEqual({ 483 | tags: { 484 | "first.a": "5", 485 | "first.b.c": "John", 486 | "first.b.d": "true" 487 | } 488 | }); 489 | }); 490 | 491 | it("test addTags method with array", () => { 492 | const payload = { tags: {} }; 493 | service.addTags(payload, "first", ["John", "Jane"]); 494 | 495 | expect(payload).toEqual({ 496 | tags: { 497 | "first.0": "John", 498 | "first.1": "Jane" 499 | } 500 | }); 501 | }); 502 | 503 | it("should generate v2 Zipkin span", () => { 504 | const payload = { 505 | id: "48be6b2f-0ee6-4851-a326-ff92fdd98045", 506 | requestID: "60ff2991-5c67-4a1d-8022-70a95be86039", 507 | level: 2, 508 | startTime: 1520505261078, 509 | endTime: 1520505261142.4363, 510 | duration: 64.436225, 511 | remoteCall: false, 512 | fromCache: false, 513 | params: { postID: 3 }, 514 | meta: { user: { name: "John" } }, 515 | action: { name: "votes.count" }, 516 | parent: "60ff2991-5c67-4a1d-8022-70a95be86039", 517 | nodeID: "node-100" 518 | }; 519 | 520 | service.enqueue = jest.fn(); 521 | 522 | service.makeZipkinPayloadV2(payload); 523 | 524 | expect(service.enqueue).toHaveBeenCalledTimes(1); 525 | expect(service.enqueue).toHaveBeenCalledWith({ 526 | "id": "48be6b2f0ee64851", 527 | "kind": "CONSUMER", 528 | "name": "votes.count", 529 | "traceId": "60ff29915c674a1d", 530 | "parentId": "60ff29915c674a1d", 531 | 532 | "localEndpoint": { 533 | "serviceName": "votes" 534 | }, 535 | "remoteEndpoint": { 536 | "serviceName": "votes" 537 | }, 538 | 539 | "debug": false, 540 | "shared": false, 541 | 542 | "annotations": [ 543 | { 544 | "timestamp": 1520505261078000, 545 | "value": "sr" 546 | }, 547 | { 548 | "timestamp": 1520505261142436, 549 | "value": "ss" 550 | } 551 | ], 552 | 553 | "tags": { 554 | "callerNodeID": "", 555 | "level": "2", 556 | "nodeID": "node-100", 557 | "params.postID": "3", 558 | "meta.user.name": "John", 559 | "remoteCall": "false" 560 | }, 561 | "timestamp": 1520505261078000, 562 | "duration": 64436 563 | }); 564 | }); 565 | 566 | it("should generate v2 Zipkin span with error", () => { 567 | const payload = { 568 | id: "48be6b2f-0ee6-4851-a326-ff92fdd98045", 569 | requestID: "60ff2991-5c67-4a1d-8022-70a95be86039", 570 | level: 2, 571 | startTime: 1520505261078, 572 | endTime: 1520505261142.4363, 573 | duration: 64.436225, 574 | remoteCall: false, 575 | fromCache: false, 576 | meta: { user: { name: "John" } }, 577 | action: { name: "votes.count" }, 578 | parent: "60ff2991-5c67-4a1d-8022-70a95be86039", 579 | nodeID: "node-100", 580 | callerNodeID: "node-99", 581 | error: { 582 | message: "Something went wrong!", 583 | code: 401, 584 | type: "WRONG_THING", 585 | data: { 586 | a: 5 587 | }, 588 | stack: "error stack" 589 | } 590 | }; 591 | 592 | service.enqueue = jest.fn(); 593 | 594 | service.makeZipkinPayloadV2(payload); 595 | 596 | expect(service.enqueue).toHaveBeenCalledTimes(1); 597 | expect(service.enqueue).toHaveBeenCalledWith({ 598 | "id": "48be6b2f0ee64851", 599 | "kind": "CONSUMER", 600 | "name": "votes.count", 601 | "traceId": "60ff29915c674a1d", 602 | "parentId": "60ff29915c674a1d", 603 | 604 | "localEndpoint": { 605 | "serviceName": "votes" 606 | }, 607 | "remoteEndpoint": { 608 | "serviceName": "votes" 609 | }, 610 | 611 | "debug": false, 612 | "shared": false, 613 | 614 | "annotations": [ 615 | { 616 | "timestamp": 1520505261078000, 617 | "value": "sr" 618 | }, 619 | { 620 | "timestamp": 1520505261142436, 621 | "value": "ss" 622 | }, 623 | { 624 | "endpoint": { 625 | "ipv4": "", 626 | "port": 0, 627 | "serviceName": "votes" 628 | }, 629 | "timestamp": 1520505261142436, 630 | "value": "error" 631 | } 632 | ], 633 | 634 | "tags": { 635 | "callerNodeID": "node-99", 636 | "level": "2", 637 | "nodeID": "node-100", 638 | "meta.user.name": "John", 639 | "remoteCall": "false", 640 | "error": "Something went wrong!", 641 | "error.code": "401", 642 | "error.type": "WRONG_THING", 643 | "error.data.a": "5", 644 | "error.stack": "error stack", 645 | }, 646 | "timestamp": 1520505261078000, 647 | "duration": 64436 648 | }); 649 | }); 650 | }); 651 | 652 | describe("Test sending & queueing", () => { 653 | 654 | describe("with batching", () => { 655 | const broker = new ServiceBroker({ logger: false }); 656 | const service = broker.createService(ZipkinService, { settings: { batchTime: 1000 }}); 657 | 658 | beforeEach(() => broker.start()); 659 | afterEach(() => broker.stop()); 660 | 661 | it("should put to queue & send from queue", () => { 662 | service.send = jest.fn(); 663 | 664 | expect(service.queue).toBeDefined(); 665 | expect(service.queue.length).toBe(0); 666 | 667 | const payload1 = { a: 5 }; 668 | 669 | service.enqueue(payload1); 670 | expect(service.queue.length).toBe(1); 671 | expect(service.queue[0]).toBe(payload1); 672 | 673 | const payload2 = { b: "John" }; 674 | 675 | service.enqueue(payload2); 676 | expect(service.queue.length).toBe(2); 677 | expect(service.queue[1]).toBe(payload2); 678 | 679 | service.sendFromQueue(); 680 | expect(service.send).toHaveBeenCalledTimes(1); 681 | expect(service.send).toHaveBeenCalledWith([payload1, payload2]); 682 | expect(service.queue.length).toBe(0); 683 | }); 684 | }); 685 | 686 | describe("without batching", () => { 687 | const broker = new ServiceBroker({ logger: false }); 688 | const service = broker.createService(ZipkinService, { settings: { batchTime: 0 }}); 689 | 690 | beforeEach(() => broker.start()); 691 | afterEach(() => broker.stop()); 692 | 693 | it("shouldn't put to queue, call send instead", () => { 694 | service.send = jest.fn(); 695 | 696 | const payload1 = { a: 5 }; 697 | 698 | service.enqueue(payload1); 699 | expect(service.queue.length).toBe(0); 700 | expect(service.send).toHaveBeenCalledTimes(1); 701 | expect(service.send).toHaveBeenCalledWith([payload1]); 702 | 703 | const payload2 = { b: "John" }; 704 | 705 | service.send.mockClear(); 706 | service.enqueue(payload2); 707 | expect(service.queue.length).toBe(0); 708 | expect(service.send).toHaveBeenCalledTimes(1); 709 | expect(service.send).toHaveBeenCalledWith([payload2]); 710 | }); 711 | }); 712 | 713 | describe("test timer", () => { 714 | let broker; 715 | let service; 716 | let clock; 717 | 718 | beforeAll(() => { 719 | clock = lolex.install(); 720 | broker = new ServiceBroker({ logger: false }); 721 | service = broker.createService(ZipkinService, { settings: { batchTime: 500 }}); 722 | service.sendFromQueue = jest.fn(); 723 | 724 | return broker.start(); 725 | }); 726 | 727 | afterAll(() => { 728 | clock.uninstall(); 729 | return broker.stop(); 730 | }); 731 | it("should call sendFromQueue", () => { 732 | service.sendFromQueue.mockClear(); 733 | 734 | clock.tick(550); 735 | 736 | expect(service.sendFromQueue).toHaveBeenCalledTimes(1); 737 | expect(service.sendFromQueue).toHaveBeenCalledWith(); 738 | }); 739 | 740 | }); 741 | 742 | describe("Test sending", () => { 743 | const broker = new ServiceBroker({ logger: false }); 744 | const service = broker.createService(ZipkinService, { settings: { baseURL: "http://zipkin-server:9876" }}); 745 | 746 | beforeEach(() => broker.start()); 747 | afterEach(() => broker.stop()); 748 | 749 | it("should call axios.post with spans", () => { 750 | service.axios.post = jest.fn(() => Promise.resolve()); 751 | 752 | const payloads = [{ a: 5 }, { b: "John" }]; 753 | 754 | service.send(payloads); 755 | 756 | expect(service.axios.post).toHaveBeenCalledTimes(1); 757 | expect(service.axios.post).toHaveBeenCalledWith("/api/v2/spans", payloads); 758 | }); 759 | }); 760 | 761 | }); 762 | 763 | -------------------------------------------------------------------------------- /packages/moleculer-prometheus/grafana-dashboards/Moleculer Prometheus.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "panel", 15 | "id": "alertlist", 16 | "name": "Alert List", 17 | "version": "5.0.0" 18 | }, 19 | { 20 | "type": "grafana", 21 | "id": "grafana", 22 | "name": "Grafana", 23 | "version": "5.0.3" 24 | }, 25 | { 26 | "type": "panel", 27 | "id": "graph", 28 | "name": "Graph", 29 | "version": "5.0.0" 30 | }, 31 | { 32 | "type": "datasource", 33 | "id": "prometheus", 34 | "name": "Prometheus", 35 | "version": "5.0.0" 36 | }, 37 | { 38 | "type": "panel", 39 | "id": "singlestat", 40 | "name": "Singlestat", 41 | "version": "5.0.0" 42 | } 43 | ], 44 | "annotations": { 45 | "list": [ 46 | { 47 | "builtIn": 1, 48 | "datasource": "-- Grafana --", 49 | "enable": true, 50 | "hide": true, 51 | "iconColor": "rgba(0, 211, 255, 1)", 52 | "name": "Annotations & Alerts", 53 | "type": "dashboard" 54 | } 55 | ] 56 | }, 57 | "editable": true, 58 | "gnetId": null, 59 | "graphTooltip": 1, 60 | "id": null, 61 | "links": [], 62 | "panels": [ 63 | { 64 | "cacheTimeout": null, 65 | "colorBackground": false, 66 | "colorValue": false, 67 | "colors": [ 68 | "rgba(245, 54, 54, 0.9)", 69 | "rgba(237, 129, 40, 0.89)", 70 | "rgba(50, 172, 45, 0.97)" 71 | ], 72 | "datasource": "${DS_PROMETHEUS}", 73 | "format": "none", 74 | "gauge": { 75 | "maxValue": 100, 76 | "minValue": 0, 77 | "show": false, 78 | "thresholdLabels": false, 79 | "thresholdMarkers": true 80 | }, 81 | "gridPos": { 82 | "h": 3, 83 | "w": 6, 84 | "x": 0, 85 | "y": 0 86 | }, 87 | "hideTimeOverride": true, 88 | "id": 9, 89 | "interval": null, 90 | "links": [], 91 | "mappingType": 1, 92 | "mappingTypes": [ 93 | { 94 | "name": "value to text", 95 | "value": 1 96 | }, 97 | { 98 | "name": "range to text", 99 | "value": 2 100 | } 101 | ], 102 | "maxDataPoints": 100, 103 | "nullPointMode": "connected", 104 | "nullText": null, 105 | "postfix": "", 106 | "postfixFontSize": "50%", 107 | "prefix": "", 108 | "prefixFontSize": "50%", 109 | "rangeMaps": [ 110 | { 111 | "from": "null", 112 | "text": "N/A", 113 | "to": "null" 114 | } 115 | ], 116 | "sparkline": { 117 | "fillColor": "rgba(31, 118, 189, 0.18)", 118 | "full": false, 119 | "lineColor": "rgb(31, 120, 193)", 120 | "show": false 121 | }, 122 | "tableColumn": "", 123 | "targets": [ 124 | { 125 | "expr": "moleculer_nodes_total", 126 | "format": "time_series", 127 | "interval": "", 128 | "intervalFactor": 2, 129 | "refId": "A", 130 | "step": 2 131 | } 132 | ], 133 | "thresholds": "", 134 | "timeFrom": "1s", 135 | "title": "Nodes", 136 | "type": "singlestat", 137 | "valueFontSize": "80%", 138 | "valueMaps": [ 139 | { 140 | "op": "=", 141 | "text": "N/A", 142 | "value": "null" 143 | } 144 | ], 145 | "valueName": "avg" 146 | }, 147 | { 148 | "cacheTimeout": null, 149 | "colorBackground": false, 150 | "colorValue": false, 151 | "colors": [ 152 | "rgba(245, 54, 54, 0.9)", 153 | "rgba(237, 129, 40, 0.89)", 154 | "rgba(50, 172, 45, 0.97)" 155 | ], 156 | "datasource": "${DS_PROMETHEUS}", 157 | "format": "none", 158 | "gauge": { 159 | "maxValue": 100, 160 | "minValue": 0, 161 | "show": false, 162 | "thresholdLabels": false, 163 | "thresholdMarkers": true 164 | }, 165 | "gridPos": { 166 | "h": 3, 167 | "w": 6, 168 | "x": 6, 169 | "y": 0 170 | }, 171 | "hideTimeOverride": true, 172 | "id": 10, 173 | "interval": null, 174 | "links": [], 175 | "mappingType": 1, 176 | "mappingTypes": [ 177 | { 178 | "name": "value to text", 179 | "value": 1 180 | }, 181 | { 182 | "name": "range to text", 183 | "value": 2 184 | } 185 | ], 186 | "maxDataPoints": 100, 187 | "nullPointMode": "connected", 188 | "nullText": null, 189 | "postfix": "", 190 | "postfixFontSize": "50%", 191 | "prefix": "", 192 | "prefixFontSize": "50%", 193 | "rangeMaps": [ 194 | { 195 | "from": "null", 196 | "text": "N/A", 197 | "to": "null" 198 | } 199 | ], 200 | "sparkline": { 201 | "fillColor": "rgba(31, 118, 189, 0.18)", 202 | "full": false, 203 | "lineColor": "rgb(31, 120, 193)", 204 | "show": false 205 | }, 206 | "tableColumn": "", 207 | "targets": [ 208 | { 209 | "expr": "moleculer_services_total", 210 | "format": "time_series", 211 | "intervalFactor": 2, 212 | "refId": "A", 213 | "step": 2 214 | } 215 | ], 216 | "thresholds": "", 217 | "timeFrom": "1s", 218 | "title": "Services", 219 | "type": "singlestat", 220 | "valueFontSize": "80%", 221 | "valueMaps": [ 222 | { 223 | "op": "=", 224 | "text": "N/A", 225 | "value": "null" 226 | } 227 | ], 228 | "valueName": "avg" 229 | }, 230 | { 231 | "cacheTimeout": null, 232 | "colorBackground": false, 233 | "colorValue": false, 234 | "colors": [ 235 | "rgba(245, 54, 54, 0.9)", 236 | "rgba(237, 129, 40, 0.89)", 237 | "rgba(50, 172, 45, 0.97)" 238 | ], 239 | "datasource": "${DS_PROMETHEUS}", 240 | "format": "none", 241 | "gauge": { 242 | "maxValue": 100, 243 | "minValue": 0, 244 | "show": false, 245 | "thresholdLabels": false, 246 | "thresholdMarkers": true 247 | }, 248 | "gridPos": { 249 | "h": 3, 250 | "w": 6, 251 | "x": 12, 252 | "y": 0 253 | }, 254 | "hideTimeOverride": true, 255 | "id": 11, 256 | "interval": null, 257 | "links": [], 258 | "mappingType": 1, 259 | "mappingTypes": [ 260 | { 261 | "name": "value to text", 262 | "value": 1 263 | }, 264 | { 265 | "name": "range to text", 266 | "value": 2 267 | } 268 | ], 269 | "maxDataPoints": 100, 270 | "nullPointMode": "connected", 271 | "nullText": null, 272 | "postfix": "", 273 | "postfixFontSize": "50%", 274 | "prefix": "", 275 | "prefixFontSize": "50%", 276 | "rangeMaps": [ 277 | { 278 | "from": "null", 279 | "text": "N/A", 280 | "to": "null" 281 | } 282 | ], 283 | "sparkline": { 284 | "fillColor": "rgba(31, 118, 189, 0.18)", 285 | "full": false, 286 | "lineColor": "rgb(31, 120, 193)", 287 | "show": false 288 | }, 289 | "tableColumn": "", 290 | "targets": [ 291 | { 292 | "expr": "moleculer_actions_total", 293 | "format": "time_series", 294 | "intervalFactor": 2, 295 | "refId": "A", 296 | "step": 2 297 | } 298 | ], 299 | "thresholds": "", 300 | "timeFrom": "1s", 301 | "title": "Actions", 302 | "type": "singlestat", 303 | "valueFontSize": "80%", 304 | "valueMaps": [ 305 | { 306 | "op": "=", 307 | "text": "N/A", 308 | "value": "null" 309 | } 310 | ], 311 | "valueName": "avg" 312 | }, 313 | { 314 | "cacheTimeout": null, 315 | "colorBackground": false, 316 | "colorValue": false, 317 | "colors": [ 318 | "rgba(245, 54, 54, 0.9)", 319 | "rgba(237, 129, 40, 0.89)", 320 | "rgba(50, 172, 45, 0.97)" 321 | ], 322 | "datasource": "${DS_PROMETHEUS}", 323 | "format": "none", 324 | "gauge": { 325 | "maxValue": 100, 326 | "minValue": 0, 327 | "show": false, 328 | "thresholdLabels": false, 329 | "thresholdMarkers": true 330 | }, 331 | "gridPos": { 332 | "h": 3, 333 | "w": 6, 334 | "x": 18, 335 | "y": 0 336 | }, 337 | "hideTimeOverride": true, 338 | "id": 12, 339 | "interval": null, 340 | "links": [], 341 | "mappingType": 1, 342 | "mappingTypes": [ 343 | { 344 | "name": "value to text", 345 | "value": 1 346 | }, 347 | { 348 | "name": "range to text", 349 | "value": 2 350 | } 351 | ], 352 | "maxDataPoints": 100, 353 | "nullPointMode": "connected", 354 | "nullText": null, 355 | "postfix": "", 356 | "postfixFontSize": "50%", 357 | "prefix": "", 358 | "prefixFontSize": "50%", 359 | "rangeMaps": [ 360 | { 361 | "from": "null", 362 | "text": "N/A", 363 | "to": "null" 364 | } 365 | ], 366 | "sparkline": { 367 | "fillColor": "rgba(31, 118, 189, 0.18)", 368 | "full": false, 369 | "lineColor": "rgb(31, 120, 193)", 370 | "show": false 371 | }, 372 | "tableColumn": "", 373 | "targets": [ 374 | { 375 | "expr": "moleculer_events_total", 376 | "format": "time_series", 377 | "intervalFactor": 2, 378 | "refId": "A", 379 | "step": 2 380 | } 381 | ], 382 | "thresholds": "", 383 | "timeFrom": "1s", 384 | "title": "Events", 385 | "type": "singlestat", 386 | "valueFontSize": "80%", 387 | "valueMaps": [ 388 | { 389 | "op": "=", 390 | "text": "N/A", 391 | "value": "null" 392 | } 393 | ], 394 | "valueName": "avg" 395 | }, 396 | { 397 | "cacheTimeout": null, 398 | "colorBackground": false, 399 | "colorValue": false, 400 | "colors": [ 401 | "rgba(245, 54, 54, 0.9)", 402 | "rgba(237, 129, 40, 0.89)", 403 | "rgba(50, 172, 45, 0.97)" 404 | ], 405 | "datasource": "${DS_PROMETHEUS}", 406 | "decimals": null, 407 | "format": "short", 408 | "gauge": { 409 | "maxValue": 100, 410 | "minValue": 0, 411 | "show": false, 412 | "thresholdLabels": false, 413 | "thresholdMarkers": true 414 | }, 415 | "gridPos": { 416 | "h": 3, 417 | "w": 6, 418 | "x": 0, 419 | "y": 3 420 | }, 421 | "hideTimeOverride": true, 422 | "id": 2, 423 | "interval": null, 424 | "links": [], 425 | "mappingType": 1, 426 | "mappingTypes": [ 427 | { 428 | "name": "value to text", 429 | "value": 1 430 | }, 431 | { 432 | "name": "range to text", 433 | "value": 2 434 | } 435 | ], 436 | "maxDataPoints": 100, 437 | "nullPointMode": "connected", 438 | "nullText": null, 439 | "postfix": "", 440 | "postfixFontSize": "50%", 441 | "prefix": "", 442 | "prefixFontSize": "50%", 443 | "rangeMaps": [ 444 | { 445 | "from": "null", 446 | "text": "N/A", 447 | "to": "null" 448 | } 449 | ], 450 | "sparkline": { 451 | "fillColor": "rgba(31, 118, 189, 0.18)", 452 | "full": false, 453 | "lineColor": "rgb(31, 120, 193)", 454 | "show": false 455 | }, 456 | "tableColumn": "", 457 | "targets": [ 458 | { 459 | "expr": "sum(moleculer_req_total)", 460 | "format": "time_series", 461 | "hide": false, 462 | "intervalFactor": 2, 463 | "refId": "A", 464 | "step": 2 465 | } 466 | ], 467 | "thresholds": "", 468 | "timeFrom": "1s", 469 | "title": "Total calls", 470 | "type": "singlestat", 471 | "valueFontSize": "80%", 472 | "valueMaps": [ 473 | { 474 | "op": "=", 475 | "text": "N/A", 476 | "value": "null" 477 | } 478 | ], 479 | "valueName": "total" 480 | }, 481 | { 482 | "cacheTimeout": null, 483 | "colorBackground": false, 484 | "colorValue": false, 485 | "colors": [ 486 | "rgba(247, 55, 55, 0.9)", 487 | "rgba(237, 129, 40, 0.89)", 488 | "rgba(50, 172, 45, 0.97)" 489 | ], 490 | "datasource": "${DS_PROMETHEUS}", 491 | "format": "short", 492 | "gauge": { 493 | "maxValue": 100, 494 | "minValue": 0, 495 | "show": false, 496 | "thresholdLabels": false, 497 | "thresholdMarkers": true 498 | }, 499 | "gridPos": { 500 | "h": 3, 501 | "w": 6, 502 | "x": 6, 503 | "y": 3 504 | }, 505 | "hideTimeOverride": true, 506 | "id": 3, 507 | "interval": null, 508 | "links": [], 509 | "mappingType": 1, 510 | "mappingTypes": [ 511 | { 512 | "name": "value to text", 513 | "value": 1 514 | }, 515 | { 516 | "name": "range to text", 517 | "value": 2 518 | } 519 | ], 520 | "maxDataPoints": 100, 521 | "nullPointMode": "connected", 522 | "nullText": null, 523 | "postfix": "", 524 | "postfixFontSize": "50%", 525 | "prefix": "", 526 | "prefixFontSize": "50%", 527 | "rangeMaps": [ 528 | { 529 | "from": "null", 530 | "text": "N/A", 531 | "to": "null" 532 | } 533 | ], 534 | "sparkline": { 535 | "fillColor": "rgba(31, 118, 189, 0.18)", 536 | "full": false, 537 | "lineColor": "rgb(31, 120, 193)", 538 | "show": false 539 | }, 540 | "tableColumn": "", 541 | "targets": [ 542 | { 543 | "expr": "sum(moleculer_req_errors_total)", 544 | "format": "time_series", 545 | "intervalFactor": 2, 546 | "refId": "A", 547 | "step": 2 548 | } 549 | ], 550 | "thresholds": "", 551 | "timeFrom": "1s", 552 | "title": "Total errors", 553 | "type": "singlestat", 554 | "valueFontSize": "80%", 555 | "valueMaps": [ 556 | { 557 | "op": "=", 558 | "text": "N/A", 559 | "value": "null" 560 | } 561 | ], 562 | "valueName": "avg" 563 | }, 564 | { 565 | "cacheTimeout": null, 566 | "colorBackground": false, 567 | "colorValue": true, 568 | "colors": [ 569 | "rgba(50, 172, 45, 0.97)", 570 | "rgba(237, 129, 40, 0.89)", 571 | "rgba(245, 54, 54, 0.9)" 572 | ], 573 | "datasource": "${DS_PROMETHEUS}", 574 | "format": "ms", 575 | "gauge": { 576 | "maxValue": 100, 577 | "minValue": 0, 578 | "show": false, 579 | "thresholdLabels": false, 580 | "thresholdMarkers": true 581 | }, 582 | "gridPos": { 583 | "h": 3, 584 | "w": 6, 585 | "x": 12, 586 | "y": 3 587 | }, 588 | "hideTimeOverride": true, 589 | "id": 4, 590 | "interval": null, 591 | "links": [], 592 | "mappingType": 1, 593 | "mappingTypes": [ 594 | { 595 | "name": "value to text", 596 | "value": 1 597 | }, 598 | { 599 | "name": "range to text", 600 | "value": 2 601 | } 602 | ], 603 | "maxDataPoints": 100, 604 | "nullPointMode": "connected", 605 | "nullText": null, 606 | "postfix": "", 607 | "postfixFontSize": "50%", 608 | "prefix": "", 609 | "prefixFontSize": "50%", 610 | "rangeMaps": [ 611 | { 612 | "from": "null", 613 | "text": "N/A", 614 | "to": "null" 615 | } 616 | ], 617 | "sparkline": { 618 | "fillColor": "rgba(31, 118, 189, 0.18)", 619 | "full": false, 620 | "lineColor": "rgb(31, 120, 193)", 621 | "show": false 622 | }, 623 | "tableColumn": "", 624 | "targets": [ 625 | { 626 | "expr": "avg(irate(moleculer_req_duration_ms_sum[1m]))", 627 | "format": "time_series", 628 | "intervalFactor": 2, 629 | "refId": "A", 630 | "step": 2 631 | } 632 | ], 633 | "thresholds": "50, 100", 634 | "timeFrom": "10s", 635 | "title": "Response time", 636 | "type": "singlestat", 637 | "valueFontSize": "80%", 638 | "valueMaps": [ 639 | { 640 | "op": "=", 641 | "text": "N/A", 642 | "value": "null" 643 | } 644 | ], 645 | "valueName": "avg" 646 | }, 647 | { 648 | "cacheTimeout": null, 649 | "colorBackground": false, 650 | "colorValue": false, 651 | "colors": [ 652 | "rgba(245, 54, 54, 0.9)", 653 | "rgba(237, 129, 40, 0.89)", 654 | "rgba(50, 172, 45, 0.97)" 655 | ], 656 | "datasource": "${DS_PROMETHEUS}", 657 | "format": "none", 658 | "gauge": { 659 | "maxValue": 100, 660 | "minValue": 0, 661 | "show": false, 662 | "thresholdLabels": false, 663 | "thresholdMarkers": true 664 | }, 665 | "gridPos": { 666 | "h": 3, 667 | "w": 6, 668 | "x": 18, 669 | "y": 3 670 | }, 671 | "hideTimeOverride": true, 672 | "id": 5, 673 | "interval": null, 674 | "links": [], 675 | "mappingType": 1, 676 | "mappingTypes": [ 677 | { 678 | "name": "value to text", 679 | "value": 1 680 | }, 681 | { 682 | "name": "range to text", 683 | "value": 2 684 | } 685 | ], 686 | "maxDataPoints": 100, 687 | "nullPointMode": "connected", 688 | "nullText": null, 689 | "postfix": "", 690 | "postfixFontSize": "50%", 691 | "prefix": "", 692 | "prefixFontSize": "50%", 693 | "rangeMaps": [ 694 | { 695 | "from": "null", 696 | "text": "N/A", 697 | "to": "null" 698 | } 699 | ], 700 | "sparkline": { 701 | "fillColor": "rgba(31, 118, 189, 0.18)", 702 | "full": false, 703 | "lineColor": "rgb(31, 120, 193)", 704 | "show": false 705 | }, 706 | "tableColumn": "", 707 | "targets": [ 708 | { 709 | "expr": "sum(irate(moleculer_req_total[1m]))", 710 | "format": "time_series", 711 | "interval": "", 712 | "intervalFactor": 2, 713 | "refId": "A", 714 | "step": 2 715 | } 716 | ], 717 | "thresholds": "", 718 | "timeFrom": "10s", 719 | "title": "Rps", 720 | "type": "singlestat", 721 | "valueFontSize": "80%", 722 | "valueMaps": [ 723 | { 724 | "op": "=", 725 | "text": "N/A", 726 | "value": "null" 727 | } 728 | ], 729 | "valueName": "avg" 730 | }, 731 | { 732 | "alert": { 733 | "conditions": [ 734 | { 735 | "evaluator": { 736 | "params": [ 737 | 200 738 | ], 739 | "type": "gt" 740 | }, 741 | "operator": { 742 | "type": "and" 743 | }, 744 | "query": { 745 | "params": [ 746 | "A", 747 | "1m", 748 | "now" 749 | ] 750 | }, 751 | "reducer": { 752 | "params": [], 753 | "type": "avg" 754 | }, 755 | "type": "query" 756 | } 757 | ], 758 | "executionErrorState": "alerting", 759 | "frequency": "10s", 760 | "handler": 1, 761 | "name": "Response time ( >200ms )", 762 | "noDataState": "no_data", 763 | "notifications": [] 764 | }, 765 | "aliasColors": {}, 766 | "bars": false, 767 | "dashLength": 10, 768 | "dashes": false, 769 | "datasource": "${DS_PROMETHEUS}", 770 | "fill": 1, 771 | "gridPos": { 772 | "h": 8, 773 | "w": 12, 774 | "x": 0, 775 | "y": 6 776 | }, 777 | "id": 1, 778 | "legend": { 779 | "alignAsTable": false, 780 | "avg": false, 781 | "current": false, 782 | "hideEmpty": false, 783 | "hideZero": false, 784 | "max": false, 785 | "min": false, 786 | "rightSide": false, 787 | "show": true, 788 | "total": false, 789 | "values": false 790 | }, 791 | "lines": true, 792 | "linewidth": 1, 793 | "links": [], 794 | "nullPointMode": "null", 795 | "percentage": false, 796 | "pointradius": 5, 797 | "points": false, 798 | "renderer": "flot", 799 | "seriesOverrides": [], 800 | "spaceLength": 10, 801 | "stack": false, 802 | "steppedLine": false, 803 | "targets": [ 804 | { 805 | "expr": "avg(irate(moleculer_req_duration_ms_sum[1m]))", 806 | "format": "time_series", 807 | "hide": false, 808 | "intervalFactor": 2, 809 | "legendFormat": "Total", 810 | "refId": "A", 811 | "step": 2 812 | } 813 | ], 814 | "thresholds": [ 815 | { 816 | "colorMode": "critical", 817 | "fill": true, 818 | "line": true, 819 | "op": "gt", 820 | "value": 200 821 | } 822 | ], 823 | "timeFrom": null, 824 | "timeShift": null, 825 | "title": "Response time", 826 | "tooltip": { 827 | "shared": true, 828 | "sort": 0, 829 | "value_type": "individual" 830 | }, 831 | "type": "graph", 832 | "xaxis": { 833 | "buckets": null, 834 | "mode": "time", 835 | "name": null, 836 | "show": true, 837 | "values": [] 838 | }, 839 | "yaxes": [ 840 | { 841 | "format": "ms", 842 | "label": null, 843 | "logBase": 1, 844 | "max": null, 845 | "min": null, 846 | "show": true 847 | }, 848 | { 849 | "format": "short", 850 | "label": null, 851 | "logBase": 1, 852 | "max": null, 853 | "min": null, 854 | "show": true 855 | } 856 | ] 857 | }, 858 | { 859 | "aliasColors": {}, 860 | "bars": false, 861 | "dashLength": 10, 862 | "dashes": false, 863 | "datasource": "${DS_PROMETHEUS}", 864 | "fill": 1, 865 | "gridPos": { 866 | "h": 8, 867 | "w": 12, 868 | "x": 12, 869 | "y": 6 870 | }, 871 | "id": 6, 872 | "legend": { 873 | "avg": false, 874 | "current": false, 875 | "max": false, 876 | "min": false, 877 | "show": true, 878 | "total": false, 879 | "values": false 880 | }, 881 | "lines": true, 882 | "linewidth": 1, 883 | "links": [], 884 | "nullPointMode": "null", 885 | "percentage": false, 886 | "pointradius": 5, 887 | "points": false, 888 | "renderer": "flot", 889 | "seriesOverrides": [], 890 | "spaceLength": 10, 891 | "stack": false, 892 | "steppedLine": false, 893 | "targets": [ 894 | { 895 | "expr": "sum(irate(moleculer_req_total[1m]))", 896 | "format": "time_series", 897 | "intervalFactor": 2, 898 | "legendFormat": "Calls", 899 | "refId": "A", 900 | "step": 2 901 | }, 902 | { 903 | "expr": "sum(irate(moleculer_req_errors_total[1m]))", 904 | "format": "time_series", 905 | "intervalFactor": 2, 906 | "legendFormat": "Errors", 907 | "refId": "B", 908 | "step": 2 909 | } 910 | ], 911 | "thresholds": [], 912 | "timeFrom": null, 913 | "timeShift": null, 914 | "title": "Action calls", 915 | "tooltip": { 916 | "shared": true, 917 | "sort": 0, 918 | "value_type": "individual" 919 | }, 920 | "type": "graph", 921 | "xaxis": { 922 | "buckets": null, 923 | "mode": "time", 924 | "name": null, 925 | "show": true, 926 | "values": [] 927 | }, 928 | "yaxes": [ 929 | { 930 | "format": "short", 931 | "label": null, 932 | "logBase": 1, 933 | "max": null, 934 | "min": null, 935 | "show": true 936 | }, 937 | { 938 | "format": "short", 939 | "label": null, 940 | "logBase": 1, 941 | "max": null, 942 | "min": null, 943 | "show": true 944 | } 945 | ] 946 | }, 947 | { 948 | "aliasColors": {}, 949 | "bars": false, 950 | "dashLength": 10, 951 | "dashes": false, 952 | "datasource": "${DS_PROMETHEUS}", 953 | "fill": 1, 954 | "gridPos": { 955 | "h": 7, 956 | "w": 24, 957 | "x": 0, 958 | "y": 14 959 | }, 960 | "id": 7, 961 | "legend": { 962 | "avg": false, 963 | "current": false, 964 | "max": false, 965 | "min": false, 966 | "show": true, 967 | "total": false, 968 | "values": false 969 | }, 970 | "lines": true, 971 | "linewidth": 1, 972 | "links": [], 973 | "nullPointMode": "null", 974 | "percentage": false, 975 | "pointradius": 5, 976 | "points": false, 977 | "renderer": "flot", 978 | "seriesOverrides": [], 979 | "spaceLength": 10, 980 | "stack": false, 981 | "steppedLine": false, 982 | "targets": [ 983 | { 984 | "expr": "sum by (errorName) (irate(moleculer_req_errors_total[1m]))", 985 | "format": "time_series", 986 | "intervalFactor": 2, 987 | "legendFormat": "{{errorName}}", 988 | "refId": "A", 989 | "step": 2 990 | } 991 | ], 992 | "thresholds": [], 993 | "timeFrom": null, 994 | "timeShift": null, 995 | "title": "Error types", 996 | "tooltip": { 997 | "shared": true, 998 | "sort": 0, 999 | "value_type": "individual" 1000 | }, 1001 | "type": "graph", 1002 | "xaxis": { 1003 | "buckets": null, 1004 | "mode": "time", 1005 | "name": null, 1006 | "show": true, 1007 | "values": [] 1008 | }, 1009 | "yaxes": [ 1010 | { 1011 | "format": "short", 1012 | "label": null, 1013 | "logBase": 1, 1014 | "max": null, 1015 | "min": null, 1016 | "show": true 1017 | }, 1018 | { 1019 | "format": "short", 1020 | "label": null, 1021 | "logBase": 1, 1022 | "max": null, 1023 | "min": null, 1024 | "show": true 1025 | } 1026 | ] 1027 | }, 1028 | { 1029 | "gridPos": { 1030 | "h": 7, 1031 | "w": 12, 1032 | "x": 0, 1033 | "y": 21 1034 | }, 1035 | "id": 8, 1036 | "limit": 10, 1037 | "links": [], 1038 | "onlyAlertsOnDashboard": true, 1039 | "show": "current", 1040 | "sortOrder": 1, 1041 | "stateFilter": [], 1042 | "title": "Alerts", 1043 | "type": "alertlist" 1044 | }, 1045 | { 1046 | "aliasColors": {}, 1047 | "bars": true, 1048 | "dashLength": 10, 1049 | "dashes": false, 1050 | "datasource": "${DS_PROMETHEUS}", 1051 | "fill": 1, 1052 | "gridPos": { 1053 | "h": 7, 1054 | "w": 12, 1055 | "x": 12, 1056 | "y": 21 1057 | }, 1058 | "id": 13, 1059 | "legend": { 1060 | "avg": false, 1061 | "current": false, 1062 | "max": false, 1063 | "min": false, 1064 | "show": false, 1065 | "total": false, 1066 | "values": false 1067 | }, 1068 | "lines": false, 1069 | "linewidth": 1, 1070 | "links": [], 1071 | "nullPointMode": "null", 1072 | "percentage": false, 1073 | "pointradius": 5, 1074 | "points": false, 1075 | "renderer": "flot", 1076 | "seriesOverrides": [], 1077 | "spaceLength": 10, 1078 | "stack": false, 1079 | "steppedLine": false, 1080 | "targets": [ 1081 | { 1082 | "expr": "moleculer_nodes_total", 1083 | "format": "time_series", 1084 | "interval": "10s", 1085 | "intervalFactor": 2, 1086 | "refId": "A", 1087 | "step": 20 1088 | } 1089 | ], 1090 | "thresholds": [], 1091 | "timeFrom": null, 1092 | "timeShift": null, 1093 | "title": "Available nodes", 1094 | "tooltip": { 1095 | "shared": true, 1096 | "sort": 0, 1097 | "value_type": "individual" 1098 | }, 1099 | "type": "graph", 1100 | "xaxis": { 1101 | "buckets": null, 1102 | "mode": "time", 1103 | "name": null, 1104 | "show": true, 1105 | "values": [] 1106 | }, 1107 | "yaxes": [ 1108 | { 1109 | "format": "short", 1110 | "label": null, 1111 | "logBase": 1, 1112 | "max": null, 1113 | "min": null, 1114 | "show": true 1115 | }, 1116 | { 1117 | "format": "short", 1118 | "label": null, 1119 | "logBase": 1, 1120 | "max": null, 1121 | "min": null, 1122 | "show": true 1123 | } 1124 | ] 1125 | } 1126 | ], 1127 | "refresh": "5s", 1128 | "schemaVersion": 16, 1129 | "style": "dark", 1130 | "tags": [], 1131 | "templating": { 1132 | "list": [] 1133 | }, 1134 | "time": { 1135 | "from": "now-15m", 1136 | "to": "now" 1137 | }, 1138 | "timepicker": { 1139 | "refresh_intervals": [ 1140 | "5s", 1141 | "10s", 1142 | "30s", 1143 | "1m", 1144 | "5m", 1145 | "15m", 1146 | "30m", 1147 | "1h", 1148 | "2h", 1149 | "1d" 1150 | ], 1151 | "time_options": [ 1152 | "5m", 1153 | "15m", 1154 | "1h", 1155 | "6h", 1156 | "12h", 1157 | "24h", 1158 | "2d", 1159 | "7d", 1160 | "30d" 1161 | ] 1162 | }, 1163 | "timezone": "", 1164 | "title": "Moleculer Prometheus demo", 1165 | "uid": "xtcSMfkmz", 1166 | "version": 4 1167 | } --------------------------------------------------------------------------------