From 3a23034cfa419603ca14ab2e472d2a348d3bee06 Mon Sep 17 00:00:00 2001 From: Michael Nason Date: Tue, 18 Oct 2016 12:00:13 -0400 Subject: [PATCH] Initial commit --- .babelrc | 13 ++ .eslintrc | 226 ++++++++++++++++++++++++++++ .gitignore | 35 +++++ .npmignore | 8 + .npmrc | 2 + .nvmrc | 1 + .nycrc | 18 +++ .travis.yml | 22 +++ API.md | 70 +++++++++ CHANGELOG.md | 0 LICENSE | 21 +++ README.md | 101 +++++++++++++ index.js | 1 + package.json | 107 +++++++++++++ rollup.client.config.js | 36 +++++ rollup.node.config.js | 36 +++++ src/client.js | 50 ++++++ src/node.js | 52 +++++++ src/util/client/consoleLogger.js | 41 +++++ src/util/client/logentriesLogger.js | 25 +++ src/util/client/rollbarLogger.js | 25 +++ src/util/common/logger.js | 69 +++++++++ src/util/common/rollbar.js | 21 +++ src/util/server/logentriesLogger.js | 17 +++ src/util/server/rollbarLogger.js | 37 +++++ test/browser.index.js | 5 + test/karma.conf.js | 47 ++++++ test/runner.js | 10 ++ test/saucelabs-browsers.json | 41 +++++ test/specs/logger.spec.js | 138 +++++++++++++++++ test/testLogger.js | 12 ++ test/webpack.client.config.js | 37 +++++ 32 files changed, 1324 insertions(+) create mode 100644 .babelrc create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 .nycrc create mode 100644 .travis.yml create mode 100644 API.md create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 rollup.client.config.js create mode 100644 rollup.node.config.js create mode 100644 src/client.js create mode 100644 src/node.js create mode 100644 src/util/client/consoleLogger.js create mode 100644 src/util/client/logentriesLogger.js create mode 100644 src/util/client/rollbarLogger.js create mode 100644 src/util/common/logger.js create mode 100644 src/util/common/rollbar.js create mode 100644 src/util/server/logentriesLogger.js create mode 100644 src/util/server/rollbarLogger.js create mode 100644 test/browser.index.js create mode 100644 test/karma.conf.js create mode 100644 test/runner.js create mode 100644 test/saucelabs-browsers.json create mode 100644 test/specs/logger.spec.js create mode 100644 test/testLogger.js create mode 100644 test/webpack.client.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..4984fc4 --- /dev/null +++ b/.babelrc @@ -0,0 +1,13 @@ +{ + "presets": [ + "es2015", + "stage-0" + ], + plugins: [ + ], + "env": { + "test": { + "plugins": [ [ "istanbul", { "exclude": "test/**/*" } ] ] + } + } +} diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..3ed1579 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,226 @@ +env: + + ########################################################################### + # # + # ENVIRONMENT: if you write code that will be executed in one of the # + # following environments, the value for that environment should be # + # set to true. # + # # + ########################################################################### + + node: true + browser: true + es6: true + +parserOptions: + sourceType: "module" + ecmaFeatures: + destructuring: true + spread: true + arrowFunctions: true + blockBindings: true + experimentalObjectRestSpread: true + +globals: + + ########################################################################### + # # + # GLOBALS: ESLint will assume the following variables are declared # + # globally; other variables will require explicit declaration. # + # # + ########################################################################### + + require: true + +rules: + + ########################################################################### + # # + # POSSIBLE ERRORS: these rules point out areas where you might have # + # made mistakes. # + # # + ########################################################################### + + comma-dangle: 1 # disallow trailing commas in object literals + no-cond-assign: 0 # disallow assignment in conditional expressions + no-console: 0 # disallow use of console + no-constant-condition: 2 # disallow use of constant expressions in conditions + no-control-regex: 2 # disallow control characters in regular expressions + no-debugger: 2 # disallow use of debugger + no-dupe-keys: 2 # disallow duplicate keys when creating object literals + no-empty: 2 # disallow empty statements + no-empty-character-class: 2 # disallow the use of empty character classes in regular expressions + no-ex-assign: 2 # disallow assigning to the exception in a catch block + no-extra-boolean-cast: 2 # disallow double-negation boolean casts in a boolean context + no-extra-semi: 0 # disallow unnecessary semicolons + no-func-assign: 0 # disallow overwriting functions written as function declarations + no-inner-declarations: 1 # disallow function or variable declarations in nested blocks + no-invalid-regexp: 2 # disallow invalid regular expression strings in the RegExp + # constructor + no-irregular-whitespace: 2 # disallow irregular whitespace outside of strings and comments + no-negated-in-lhs: 2 # disallow negation of the left operand of an in expression + no-obj-calls: 2 # disallow the use of object properties of the global object (Math + # and JSON) as functions + no-regex-spaces: 1 # disallow multiple spaces in a regular expression literal + no-reserved-keys: 0 # disallow reserved words being used as object literal keys + no-sparse-arrays: 2 # disallow sparse arrays + no-unreachable: 2 # disallow unreachable statements after a return, throw, continue, + # or break statement + use-isnan: 2 # disallow comparisons with the value NaN + valid-jsdoc: # ensure JSDoc comments are valid + [1, { "prefer": { "return": "returns" }, "requireReturn": false, requireParamDescription: false, requireReturnDescription: false }] + valid-typeof: 2 # ensure that the results of typeof are compared against a + # valid string + + ########################################################################### + # # + # BEST PRACTICES: these rules are designed to prevent you from making # + # mistakes. They either prescribe a better way of doing something or # + # help you avoid pitfalls. # + # # + ########################################################################### + + block-scoped-var: 0 # treat var statements as if they were block scoped + complexity: [1, 250] # specify the maximum cyclomatic complexity allowed in a program + consistent-return: 0 # require return statements to either always or never specify values + curly: 0 # specify curly brace conventions for all control + # statements + default-case: 2 # require default case in switch statements + dot-notation: 1 # encourages use of dot notation whenever possible + eqeqeq: 0 # require the use of === and !== + guard-for-in: 1 # make sure for-in loops have an if statement + no-alert: 0 # disallow the use of alert, confirm, and prompt + no-caller: 2 # disallow use of arguments.caller or arguments.callee + no-div-regex: 0 # disallow division operators explicitly at beginning of regular + # expression + no-else-return: 0 # disallow else after a return in an if + no-eq-null: 0 # disallow comparisons to null without a type-checking operator + no-eval: 2 # disallow use of eval() + no-extend-native: 2 # disallow adding to native types + no-extra-bind: 2 # disallow unnecessary function binding + no-fallthrough: 2 # disallow fallthrough of case statements + no-floating-decimal: 2 # disallow the use of leading or trailing decimal points in numeric + # literals + no-implied-eval: 2 # disallow use of eval()-like methods + no-iterator: 2 # disallow usage of __iterator__ property + no-labels: 2 # disallow use of labeled statements + no-lone-blocks: 2 # disallow unnecessary nested blocks + no-loop-func: 0 # disallow creation of functions within loops + no-multi-spaces: 0 # disallow use of multiple spaces + no-multi-str: 2 # disallow use of multiline strings + no-native-reassign: 2 # disallow reassignments of native objects + no-new: 2 # disallow use of new operator when not part of the assignment or + # comparison + no-new-func: 0 # disallow use of new operator for Function object + no-new-wrappers: 2 # disallows creating new instances of String,Number, and Boolean + no-octal: 0 # disallow use of octal literals + no-octal-escape: 0 # disallow use of octal escape sequences in string literals, such as + # `var foo = "Copyright \251"` + no-process-env: 0 # disallow use of process.env + no-proto: 2 # disallow usage of __proto__ property + no-redeclare: 1 # disallow declaring the same variable more then once + no-return-assign: 0 # disallow use of assignment in return statement + no-script-url: 2 # disallow use of javascript urls. + no-self-compare: 2 # disallow comparisons where both sides are exactly the same + no-sequences: 2 # disallow use of comma operator + no-unused-expressions: 0 # disallow usage of expressions in statement position + no-void: 2 # disallow use of void operator + no-warning-comments: 0 # disallow usage of configurable warning terms in comments - e.g. + no-with: 2 # disallow use of the with statement + radix: 2 # require use of the second argument for parseInt() + vars-on-top: 0 # requires to declare all vars on top of their containing scope + wrap-iife: [2, "inside"] # require immediate function invocation to be wrapped in parentheses + + ########################################################################### + # # + # STRICT MODE: these rules relate to using strict mode. # + # # + ########################################################################### + + strict: [2, "never"] # require that all functions are run in strict mode + + ########################################################################### + # # + # VARIABLES: these rules have to do with variable declarations. # + # # + ########################################################################### + + no-catch-shadow: 2 # disallow the catch clause parameter name being the same as a + # variable in the outer scope + no-delete-var: 2 # disallow deletion of variables + no-label-var: 2 # disallow labels that share a name with a variable + no-shadow: 0 # disallow declaration of variables already declared in the + # outer scope + no-shadow-restricted-names: 2 # disallow shadowing of names such as arguments + no-undef: 0 # disallow use of undeclared variables unless mentioned in a + # /*global */ block + no-undef-init: 2 # disallow use of undefined when initializing variables + no-undefined: 0 # disallow use of undefined variable + no-unused-vars: 0 # disallow declaration of variables that are not used in the code + no-use-before-define: 0 # disallow use of variables before they are defined + + ########################################################################### + # # + # NODE: these rules relate to functionality provided in Node.js. # + # # + ########################################################################### + + handle-callback-err: 0 # enforces error handling in callbacks + no-mixed-requires: 0 # disallow mixing regular variable and require declarations + no-new-require: 2 # disallow use of new operator with the require function + no-path-concat: 2 # disallow string concatenation with __dirname and __filename + no-process-exit: 0 # disallow process.exit() + no-restricted-modules: 0 # restrict usage of specified node modules + no-sync: 0 # disallow use of synchronous methods + + ########################################################################### + # # + # STYLISTIC ISSUES: these rules are purely matters of style and, # + # while valueable to enforce consistently across a project, are # + # quite subjective. # + # # + ########################################################################### + + indent: [2, 2] # Set a specific tab width + brace-style: 0 # enforce one true brace style + camelcase: 0 # require camel case names + comma-spacing: 2 # enforce spacing before and after comma + comma-style: 2 # enforce one true comma style + consistent-this: 0 # enforces consistent naming when capturing the current execution context + eol-last: 0 # enforce newline at the end of file, with no multiple empty lines + func-names: 0 # require function expressions to have a name + func-style: 0 # enforces use of function declarations or expressions + key-spacing: 2 # enforces spacing between keys and values in object literal properties + max-nested-callbacks: [2, 4] # specify the maximum depth callbacks can be nested + new-cap: 0 # require a capital letter for constructors + new-parens: 2 # disallow the omission of parentheses when invoking a constructor with no arguments + no-array-constructor: 2 # disallow use of the Array constructor + no-lonely-if: 0 # disallow if as the only statement in an else block + no-mixed-spaces-and-tabs: 2 # disallow mixed spaces and tabs for indentation + no-nested-ternary: 0 # disallow nested ternary expressions + no-new-object: 1 # disallow use of the Object constructor + no-space-before-semi: 0 # disallow space before semicolon + no-spaced-func: 2 # disallow space between function identifier and application + no-ternary: 0 # disallow the use of ternary operators + + no-trailing-spaces: 2 # disallow trailing whitespace at the end of lines + no-multiple-empty-lines: 0 # disallow multiple empty lines + no-underscore-dangle: 0 # disallow dangling underscores in identifiers + no-extra-parens: 2 # disallow wrapping of non-IIFE statements in parens + one-var: 0 # allow just one var statement per function + padded-blocks: 0 # enforce padding within blocks + quotes: # specify whether double or single quotes should be used + [1, "single", "avoid-escape"] + quote-props: 0 # require quotes around object literal property names + semi: [2, "always"] # require or disallow use of semicolons instead of ASI + semi-spacing: 0 + sort-vars: 0 # sort variables within the same declaration block + keyword-spacing: 2 # require a space after certain keywords + space-before-blocks: 2 # require or disallow space before blocks + space-in-brackets: 0 # require or disallow spaces inside brackets + space-in-parens: 0 # require or disallow spaces inside parentheses + space-infix-ops: 0 # require spaces around operators + spaced-line-comment: 0 # require or disallow a space immediately following + # the // in a line comment + wrap-regex: 0 # require regex literals to be wrapped in parentheses + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1844eb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +dist + +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log +node_modules +coverage +.nyc_output +.DS_Store + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..ca35489 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +# Don't publish anything to npm but package.json, the dist +# directory, API.md, CHANGELOG.md, and the root index.js file + +!dist/* +!API.md +!CHANGELOG.md +!package.json +!index.js \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..3b86cfb --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +loglevel=error +save-exact=true \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..07a7b03 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +6.7.0 \ No newline at end of file diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..f87885b --- /dev/null +++ b/.nycrc @@ -0,0 +1,18 @@ +{ + "reporter": [ + "html", + "lcov", + "cobertura", + "text-summary" + ], + "report-dir": "./coverage/node", + "include": [ + "src/**/*.js" + ], + "require": [ + "babel-register" + ], + "sourceMap": false, + "instrument": false, + "show-process-tree": true +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..49dcb3b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: node_js +node_js: + - "6.*" + +branches: + only: + - master + +script: + - "npm run test:ci" + +after_success: + - SAUCELABS=true npm run test:browser + +cache: + directories: + - node_modules + +notifications: + email: false + +sudo: false diff --git a/API.md b/API.md new file mode 100644 index 0000000..216d4c3 --- /dev/null +++ b/API.md @@ -0,0 +1,70 @@ + + +# we-js-logger/client + +A logger than can be used in browsers + +# we-js-logger/util/logger + +Base logger class, used for both node and client loggers + +Uses (bunyan)[https://github.com/trentm/node-bunyan/] under the hood, which has a few quirks + +# constructor + +**Parameters** + +- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** logger configuration + - `options.name` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the logger + - `options.environment` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** application environment + - `options.codeVersion` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** a code version, preferably a SHA + - `options.level` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the level to log at + - `options.stdout` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** output to stdout + - `options.streams` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** bunyan stream configuration + - `options.serializers` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** bunyan serializer configuration + - `options.logentriesToken` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Logentries API token + - `options.rollbarToken` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Rollbar token +- `$0` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`) + - `$0.name` (optional, default `'WeWork'`) + - `$0.environment` + - `$0.codeVersion` + - `$0.level` (optional, default `'info'`) + - `$0.stdout` (optional, default `true`) + - `$0.streams` (optional, default `[]`) + - `$0.serializers` (optional, default `bunyan.stdSerializers`) + - `$0.logentriesToken` (optional, default `''`) + - `$0.rollbarToken` (optional, default `''`) + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** a configured bunyan instance + +# we-js-logger/util/client/consoleLogger + +Custom bunyan stream that writes to browser console with nice formatting + +# we-js-logger/util/client/logentriesLogger + +Custom bunyan stream that transports to logentries from a browser + +# we-js-logger/util/client/rollbarLogger + +Custom rollbar stream that transports to logentries from a browser + Note: Rollbar init is _not_ currently handled here, see + + for details on setting up Rollbar for a client app + +# we-js-logger/node + +A logger than can be used in node processes + +# we-js-logger/util/server/rollbarLogger + +Custom bunyan stream that transports to Rollbar from a node process. + Note: Rollbar initialization is handled here. + +# we-js-logger/util/common/rollbar + +Shared rollbar helpers + +# we-js-logger/util/server/logentriesLogger + +Custom bunyan stream that transports to logentries from a node process diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4d12b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 WeWork Projects + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fa93f7 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +we-js-logger +==================== + +## TODO: write out these docs! + +[![Build Status][travis-image]][travis-url] +[![Coverage Status][coveralls-image]][coveralls-url] +[![NPM version][npm-version-image]][npm-url] +[![NPM downloads][npm-downloads-image]][npm-url] +[![MIT License][license-image]][license-url] + +[![Sauce Test Status][saucelabs-image]][saucelabs-url] + +>Logger for node processes and browser applications with transports to Rollbar and Logentries + +# Introduction + + +# Usage + +```js + +``` + +## Configuration + + +## Examples + +```js + +``` + + +# Development + +In lieu of a formal style guide, please ensure PRs follow the conventions present, and have been properly linted and tested. Feel free to open issues to discuss. + +Be aware this module is tested in both browser and node runtimes. + +## Available tasks + +### Build and test +Runs all tests, static analysis, and bundle for distribution +```shell +$ npm start +``` + +### Test +Runs browser and node tests +```shell +$ npm test +``` + +Runs browser tests via PhantomJS only +```shell +$ npm run test:browser +``` + +Runs browser tests via SauceLabs only +```shell +$ SAUCELABS=true npm run test:browser +``` + +Runs node tests only +```shell +$ npm run test:node +``` + +### Docs +Regenerate `API.md` docs from JSDoc comments +```shell +$ npm run docs +``` + +### Release +We're using `np` to simplify publishing to npm. We have two targets preconfigured, for others go ahead an use `np` directly. + +```shell +$ npm run release:pre # prerelease +$ npm run release:patch # patch release +``` + + + +[npm-url]: https://npmjs.org/package/we-js-logger +[npm-version-image]: http://img.shields.io/npm/v/we-js-logger.svg?style=flat-square +[npm-downloads-image]: http://img.shields.io/npm/dm/we-js-logger.svg?style=flat-square + +[coveralls-image]:https://coveralls.io/repos/github/wework/we-js-logger/badge.svg?branch=master +[coveralls-url]:https://coveralls.io/github/wework/we-js-logger?branch=master + +[travis-url]:https://travis-ci.org/wework/we-js-logger +[travis-image]: https://travis-ci.org/wework/we-js-logger.svg?branch=master + +[saucelabs-image]:https://saucelabs.com/browser-matrix/we-js-logger.svg +[saucelabs-url]:https://saucelabs.com/u/we-js-logger + +[license-url]: LICENSE +[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square + diff --git a/index.js b/index.js new file mode 100644 index 0000000..394e831 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./dist/queryValidator'); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..725929d --- /dev/null +++ b/package.json @@ -0,0 +1,107 @@ +{ + "name": "we-js-logger", + "version": "0.0.0", + "description": "A logger for Node and Browser JS with Rollbar and Logentries transports", + "author": "WeWork Digital ", + "contributors": [ + "Mike Nason (https://github.com/nason)" + ], + "license": "MIT", + "homepage": "https://github.com/wework/we-js-logger", + "bugs": { + "url": "https://github.com/wework/we-js-logger/issues" + }, + "files": [ + "dist/", + "index.js", + "API.md", + "CHANGELOG.md" + ], + "main": "dist/node.js", + "browser": "dist/client.js", + "scripts": { + "test": "npm run test:browser && npm run test:node", + "test:ci": "npm run test:browser && npm run test:node:ci", + "test:browser": "NODE_ENV=test karma start test/karma.conf.js", + "test:node": "NODE_ENV=test nyc mocha -r test/runner test/specs/**/*.js", + "test:node:ci": "npm run test:node && nyc report --reporter=text-lcov | coveralls", + "eslint": "eslint src test", + "clean": "rimraf dist && mkdirp dist", + "bundle": "rollup -c rollup.client.config.js && rollup -c rollup.node.config.js", + "start": "npm test && npm run dist", + "dist": "npm run eslint && npm run clean && npm run bundle && npm run docs", + "docs": "documentation build src/client.js src/node.js --github --format md --output API.md", + "pretest": "npm run bundle", + "prepublish": "npm start", + "version": "auto-changelog --package; git add API.md CHANGELOG.md", + "release:patch": "np patch", + "release:pre": "np prerelease" + }, + "repository": { + "type": "git", + "url": "git@github.com:wework/we-js-logger.git" + }, + "engines": { + "node": ">=6.0" + }, + "keywords": [ + "javascript", + "es6", + "es2015", + "universal", + "isomorphic", + "logging", + "rollbar", + "logentries", + "bunyan" + ], + "peerDependencies": {}, + "devDependencies": { + "auto-changelog": "0.3.1", + "babel-core": "6.17.0", + "babel-loader": "6.2.5", + "babel-plugin-external-helpers": "6.8.0", + "babel-plugin-istanbul": "2.0.3", + "babel-plugin-transform-es2015-modules-umd": "6.12.0", + "babel-polyfill": "6.16.0", + "babel-preset-es2015": "6.16.0", + "babel-preset-stage-0": "6.16.0", + "chai": "3.5.0", + "coveralls": "2.11.14", + "documentation": "4.0.0-beta11", + "eslint": "3.8.1", + "imports-loader": "0.6.5", + "install": "0.8.1", + "json-loader": "0.5.4", + "karma": "1.3.0", + "karma-coverage": "1.1.1", + "karma-mocha": "1.2.0", + "karma-mocha-reporter": "2.2.0", + "karma-phantomjs-launcher": "1.0.2", + "karma-sauce-launcher": "1.0.0", + "karma-sourcemap-loader": "0.3.7", + "karma-webpack": "1.8.0", + "mkdirp": "0.5.1", + "mocha": "3.1.2", + "np": "2.9.0", + "npm": "3.10.8", + "nyc": "8.3.1", + "phantomjs-prebuilt": "2.1.13", + "rimraf": "2.5.4", + "rollup": "0.36.3", + "rollup-plugin-babel": "2.6.1", + "rollup-plugin-filesize": "1.0.0", + "semver": "5.3.0", + "sinon": "1.17.6", + "sinon-chai": "2.8.0", + "webpack": "1.13.2" + }, + "dependencies": { + "bunyan": "1.8.2", + "bunyan-format": "0.2.1", + "le_js": "git://github.com/logentries/le_js.git#56addf6c71c7f9454eb49b1a12d97037c3715bb9", + "le_node": "1.6.7", + "lodash": "4.16.4", + "rollbar": "0.6.2" + } +} diff --git a/rollup.client.config.js b/rollup.client.config.js new file mode 100644 index 0000000..ed65d77 --- /dev/null +++ b/rollup.client.config.js @@ -0,0 +1,36 @@ +import babel from 'rollup-plugin-babel'; +import filesize from 'rollup-plugin-filesize'; + +const pkgName = 'we-js-logger'; + +export default { + entry: 'src/client.js', + targets: [ + { + dest: 'dist/client.js', + sourceMap: 'dist/client.map.js', + format: 'cjs' + }, + ], + moduleId: pkgName, + moduleName: pkgName, + plugins: [ + filesize(), + + babel({ + exclude: './node_modules/**', + moduleIds: true, + comments: false, + + // Custom babelrc for build + babelrc: false, + presets: [ + [ 'es2015', { 'modules': false } ], + 'stage-0', + ], + plugins: [ + 'external-helpers' + ] + }) + ] +}; diff --git a/rollup.node.config.js b/rollup.node.config.js new file mode 100644 index 0000000..30302dd --- /dev/null +++ b/rollup.node.config.js @@ -0,0 +1,36 @@ +import babel from 'rollup-plugin-babel'; +import filesize from 'rollup-plugin-filesize'; + +const pkgName = 'we-js-logger'; + +export default { + entry: 'src/node.js', + targets: [ + { + dest: 'dist/node.js', + sourceMap: 'dist/node.map.js', + format: 'cjs' + }, + ], + moduleId: pkgName, + moduleName: pkgName, + plugins: [ + filesize(), + + babel({ + exclude: './node_modules/**', + moduleIds: true, + comments: false, + + // Custom babelrc for build + babelrc: false, + presets: [ + [ 'es2015', { 'modules': false } ], + 'stage-0', + ], + plugins: [ + 'external-helpers' + ] + }) + ] +}; diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..e7873c9 --- /dev/null +++ b/src/client.js @@ -0,0 +1,50 @@ +/** + * @module we-js-logger/client + * @description A logger than can be used in browsers + */ +import Logger from './util/common/logger'; +import ClientConsoleLogger from './util/client/consoleLogger'; +import ClientLogentriesLogger from './util/client/logentriesLogger'; +import ClientRollbarLogger from './util/client/rollbarLogger'; + +export default class ClientLogger extends Logger { + getStreams() { + // Any passed in streams + const streams = [...this.streams]; + + // Nice console output + if (this.stdout) { + streams.push({ + level: this.level, + stream: new ClientConsoleLogger(), + type: 'raw' + }); + } + + // Rollbar Transport + // Messages at the warn level or higher are transported to Rollbar + if (this.rollbarToken) { + streams.push({ + name: 'rollbar', + level: 'warn', + stream: new ClientRollbarLogger({ + environment: this.environment, + codeVersion: this.codeVersion + }), + type: 'raw' + }); + } + + // Transport client logs + if (this.logentriesToken) { + streams.push({ + name: 'logentries', + level: this.level, + stream: new ClientLogentriesLogger({ token: this.logentriesToken }), + type: 'raw' + }); + } + + return streams; + } +} \ No newline at end of file diff --git a/src/node.js b/src/node.js new file mode 100644 index 0000000..80be7d4 --- /dev/null +++ b/src/node.js @@ -0,0 +1,52 @@ +/** + * @module we-js-logger/node + * @description A logger than can be used in node processes + */ +import Logger from './util/common/logger'; + +import bunyanFormat from 'bunyan-format'; +import ServerRollbarLogger from './util/server/rollbarLogger'; +import ServerLogentriesLogger from './util/server/logentriesLogger'; +import RollbarLogger from './util/server/rollbarLogger'; + +export default class NodeLogger extends Logger { + getStreams() { + // Any passed in streams + const streams = [...this.streams]; + + // Nice output to stdout + if (this.stdout) { + streams.push({ + level: this.level, + stream: bunyanFormat({ outputMode: 'short' }), + type: 'stream' + }); + } + + // Rollbar Transport + // Messages at the warn level or higher are transported to Rollbar + // Messages with an `err` and/or `req` data params are handled specially + if (this.rollbarToken) { + streams.push({ + name: 'rollbar', + level: 'warn', + stream: new ServerRollbarLogger({ + token: this.rollbarToken, + environment: this.environment, + codeVersion: this.codeVersion + }), + type: 'raw' + }); + } + + // Transport server logs + if (this.logentriesToken) { + streams.push(new ServerLogentriesLogger({ + token: this.logentriesToken, + level: this.level + })); + } + + return streams; + } +} \ No newline at end of file diff --git a/src/util/client/consoleLogger.js b/src/util/client/consoleLogger.js new file mode 100644 index 0000000..bd0f6f9 --- /dev/null +++ b/src/util/client/consoleLogger.js @@ -0,0 +1,41 @@ +/** + * @module we-js-logger/util/client/consoleLogger + * @description Custom bunyan stream that writes to browser console with nice formatting + */ + +import bunyan from 'bunyan'; + +export default function ClientConsoleLogger() {} +ClientConsoleLogger.prototype.write = function (data = {}) { + const loggerName = data.component ? `${data.name}/${data.component}` : data.name; + + let levelCss; + const defaultCss = 'color: DimGray'; + const msgCss = 'color: SteelBlue'; + + if (data.level < bunyan.DEBUG) { + levelCss = 'color: DeepPink'; + } else if (data.level < bunyan.INFO) { + levelCss = 'color: GoldenRod'; + } else if (data.level < bunyan.WARN) { + levelCss = 'color: DarkTurquoise'; + } else if (data.level < bunyan.ERROR) { + levelCss = 'color: Purple'; + } else if (data.level < bunyan.FATAL) { + levelCss = 'color: Crimson'; + } else { + levelCss = 'color: Black'; + } + + console.log( // eslint-disable-line no-console + '[%s] %c%s%c: %s: %c%s', + data.time, + levelCss, bunyan.nameFromLevel[data.level], + defaultCss, loggerName, + msgCss, data.msg + ); + + if (data.err && data.err.stack) { + console.error(data.err.stack); // eslint-disable-line no-console + } +}; diff --git a/src/util/client/logentriesLogger.js b/src/util/client/logentriesLogger.js new file mode 100644 index 0000000..f137004 --- /dev/null +++ b/src/util/client/logentriesLogger.js @@ -0,0 +1,25 @@ +/** + * @module we-js-logger/util/client/logentriesLogger + * @description Custom bunyan stream that transports to logentries from a browser + */ + +import isFunction from 'lodash/isFunction'; +import bunyan from 'bunyan'; +import LE from 'le_js'; + +export default function ClientLogentriesLogger({ token }) { + LE.init({ + token, + no_format: true, + page_info: 'per-page' + }); +} + +ClientLogentriesLogger.prototype.write = function (data = {}) { + const level = bunyan.nameFromLevel[data.level]; + if (isFunction(LE[level])) { + LE[level](data); + } else { + LE.log(data); + } +}; diff --git a/src/util/client/rollbarLogger.js b/src/util/client/rollbarLogger.js new file mode 100644 index 0000000..9b2463d --- /dev/null +++ b/src/util/client/rollbarLogger.js @@ -0,0 +1,25 @@ +/** + * @module we-js-logger/util/client/rollbarLogger + * @description Custom rollbar stream that transports to logentries from a browser + * Note: Rollbar init is *not* currently handled here, see + * https://rollbar.com/docs/notifier/rollbar.js/#quick-start + * for details on setting up Rollbar for a client app + */ + +import Rollbar from 'rollbar'; +import bunyan from 'bunyan'; +import omit from 'lodash/omit'; +import { bunyanLevelToRollbarLevelName } from '../common/rollbar'; + +export default function RollbarLogger({ token, environment, codeVersion }) { + // TODO test for rollbar in head via `global.Rollbar`, + // if present update config, else init +} + +RollbarLogger.prototype.write = function (data = {}) { + const rollbarLevelName = bunyanLevelToRollbarLevelName(data.level); + const scopeData = omit(data, ['req', 'level']); + + // https://rollbar.com/docs/notifier/rollbar.js/#usage + Rollbar.scope(scopeData)[rollbarLevelName](data.msg, data.err); +}; diff --git a/src/util/common/logger.js b/src/util/common/logger.js new file mode 100644 index 0000000..ecf18de --- /dev/null +++ b/src/util/common/logger.js @@ -0,0 +1,69 @@ +/** + * @module we-js-logger/util/logger + * @description Base logger class, used for both node and client loggers + * + * Uses (bunyan)[https://github.com/trentm/node-bunyan/] under the hood, which has a few quirks + */ +import bunyan from 'bunyan'; + +export default class Logger { + /** + * @param {Object} options - logger configuration + * @param {String} options.name - the name of the logger + * @param {String} options.environment - application environment + * @param {String} options.codeVersion - a code version, preferably a SHA + * @param {String} options.level - the level to log at + * @param {Boolean} options.stdout - output to stdout + * @param {Array} options.streams - bunyan stream configuration + * @param {Object} options.serializers - bunyan serializer configuration + * @param {String} options.logentriesToken - Logentries API token + * @param {String} options.rollbarToken - Rollbar token + * @returns {Object} a configured bunyan instance + */ + constructor({ + name = 'WeWork', + environment, + codeVersion, + level = 'info', + stdout = true, + streams = [], + serializers = bunyan.stdSerializers, + logentriesToken = '', + rollbarToken = '' + } = {}) { + this.environment = environment; + this.codeVersion = codeVersion; + this.stdout = stdout; + this.streams = streams; + this.logentriesToken = logentriesToken; + this.rollbarToken = rollbarToken; + + const logger = bunyan.createLogger({ + name, + level, + serializers, + streams: this.getStreams() + }); + + if (!this.logentriesToken) { + logger.trace('Logger missing logentries token'); + } + + if (!this.rollbarToken) { + logger.trace('Logger missing rollbar token'); + } + + return logger; + } + + getStreams() { + const streams = [ + // Any passed in streams + ...this.streams + ]; + + console.warn('Base logger class should not be used directly. No transports are added by default.'); + + return streams; + } +} \ No newline at end of file diff --git a/src/util/common/rollbar.js b/src/util/common/rollbar.js new file mode 100644 index 0000000..41d3f3a --- /dev/null +++ b/src/util/common/rollbar.js @@ -0,0 +1,21 @@ +/** + * @module we-js-logger/util/common/rollbar + * @description Shared rollbar helpers + */ + +// https://github.com/trentm/node-bunyan#levels +// to +// https://rollbar.com/docs/notifier/rollbar.js/api/#rollbardebuginfowarnwarningerrorcritical +const bunyanToRollbarLevelMap = { + fatal: 'critical', + error: 'error', + warn: 'warning', + info: 'info', + debug: 'debug', + trace: 'debug' +}; + +export const bunyanLevelToRollbarLevelName = (level = bunyan.ERROR) => { + const bunyanLevelName = bunyan.nameFromLevel[level]; + return bunyanToRollbarLevelMap[bunyanLevelName] || 'error'; +}; diff --git a/src/util/server/logentriesLogger.js b/src/util/server/logentriesLogger.js new file mode 100644 index 0000000..ad4059b --- /dev/null +++ b/src/util/server/logentriesLogger.js @@ -0,0 +1,17 @@ +/** + * @module we-js-logger/util/server/logentriesLogger + * @description Custom bunyan stream that transports to logentries from a node process + */ + +import Logentries from 'le_node'; + +export default function ServerLogentriesLogger({ token, level }) { + const loggerDefinition = Logentries.bunyanStream({ + token, + secure: true, + withStack: true + }); + loggerDefinition.level = level; + + return loggerDefinition; +} diff --git a/src/util/server/rollbarLogger.js b/src/util/server/rollbarLogger.js new file mode 100644 index 0000000..68cf5e5 --- /dev/null +++ b/src/util/server/rollbarLogger.js @@ -0,0 +1,37 @@ +/** + * @module we-js-logger/util/server/rollbarLogger + * @description Custom bunyan stream that transports to Rollbar from a node process. + * Note: Rollbar initialization is handled here. + */ + +import Rollbar from 'rollbar'; +import omit from 'lodash/omit'; +import isError from 'lodash/isError'; +import bunyan from 'bunyan'; +import { bunyanLevelToRollbarLevelName } from '../common/rollbar'; + +export default function RollbarLogger({ token, codeVersion, environment }) { + // https://rollbar.com/docs/notifier/node_rollbar/#configuration-reference + Rollbar.init(token, { + handleUncaughtExceptionsAndRejections: true, + codeVersion, + environment + }); +} + +RollbarLogger.prototype.write = function (data = {}) { + const rollbarLevelName = bunyanLevelToRollbarLevelName(data.level); + const scopeData = omit(data, ['req', 'level']); + + if (data.err && isError(data.err)) { + Rollbar.handleErrorWithPayloadData(data.err, { + level: rollbarLevelName, + ...scopeData + }, data.req); + } else { + Rollbar.reportMessageWithPayloadData(data.msg, { + level: rollbarLevelName, + ...scopeData + }, data.req); + } +}; diff --git a/test/browser.index.js b/test/browser.index.js new file mode 100644 index 0000000..724e06e --- /dev/null +++ b/test/browser.index.js @@ -0,0 +1,5 @@ +require('./runner'); + +// require all `/test/specs/**/*.js` +const testsContext = require.context('./specs/', true, /\.js$/); +testsContext.keys().forEach(testsContext); \ No newline at end of file diff --git a/test/karma.conf.js b/test/karma.conf.js new file mode 100644 index 0000000..b252623 --- /dev/null +++ b/test/karma.conf.js @@ -0,0 +1,47 @@ +const webpackClientConfig = require('./webpack.client.config'); +const saucelabsBrowsers = require('./saucelabs-browsers.json').browsers; + +module.exports = function(config) { + // Default + let browsers = ['PhantomJS']; + // Saucelabs run + if (process.env.SAUCELABS === 'true') { + browsers = Object.keys(saucelabsBrowsers); + } + + // karma configuration + config.set({ + basePath: '../', + autoWatch: true, + singleRun: true, + frameworks: ['mocha'], + sauceLabs: { + testName: 'we-js-logger/client' + }, + browserNoActivityTimeout: 120000, + concurrency: 2, + customLaunchers: saucelabsBrowsers, + files: [ + 'test/browser.index.js' + ], + browsers: browsers, + reporters: process.env.SAUCELABS ? ['mocha', 'saucelabs'] : ['mocha'], + + mochaReporter: { + output: 'autowatch', + showDiff: true + }, + + preprocessors: { + 'test/browser.index.js': ['webpack'] + }, + + // webpack test configuration + webpack: webpackClientConfig, + + // webpack-dev-middleware configuration + webpackMiddleware: { + stats: 'errors-only' + } + }); +}; diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 0000000..bd4abb9 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,10 @@ +require('babel-register'); +require('babel-polyfill'); + +const chai = require('chai'); +const sinon = require('sinon'); + +global.expect = chai.expect; +global.should = chai.should; +global.assert = chai.assert; +global.sinon = sinon; diff --git a/test/saucelabs-browsers.json b/test/saucelabs-browsers.json new file mode 100644 index 0000000..2ea40fd --- /dev/null +++ b/test/saucelabs-browsers.json @@ -0,0 +1,41 @@ +{ + "browsers": { + "sl_chrome": { + "base": "SauceLabs", + "browserName": "chrome" + }, + "sl_firefox": { + "base": "SauceLabs", + "browserName": "firefox" + }, + "sl_safari": { + "base": "SauceLabs", + "browserName": "safari", + "platform": "OS X 10.10" + }, + "sl_IE_9": { + "base": "SauceLabs", + "browserName": "internet explorer", + "platform": "Windows 2008", + "version": "9" + }, + "sl_IE_10": { + "base": "SauceLabs", + "browserName": "internet explorer", + "platform": "Windows 2012", + "version": "10" + }, + "sl_IE_11": { + "base": "SauceLabs", + "browserName": "internet explorer", + "platform": "Windows 8.1", + "version": "11" + }, + "sl_iOS": { + "base": "SauceLabs", + "browserName": "iphone", + "platform": "OS X 10.10", + "version": "8.1" + } + } +} \ No newline at end of file diff --git a/test/specs/logger.spec.js b/test/specs/logger.spec.js new file mode 100644 index 0000000..9228a35 --- /dev/null +++ b/test/specs/logger.spec.js @@ -0,0 +1,138 @@ +// Require the package. For client tests, webpack should resolve to the browser version automatically. +import _ from 'lodash'; +import bunyan from 'bunyan'; + +import Logger from '../../'; +import TestLogger from '../testLogger'; + +// Logentries validates this token, this is fake one in an acceptable format +const fakeToken = '00000000-0000-0000-0000-000000000000'; + +describe('we-js-logger', () => { + it('exports a "class"', () => { + expect(Logger).to.be.a('function'); + expect(new Logger()).to.be.ok; + }); + + describe('options', () => { + it('accepts a level', () => { + const info = new Logger({ level: 'info' }); + const debug = new Logger({ level: 'debug' }); + const fatal = new Logger({ level: 'fatal' }); + + expect(info._level).to.equal(bunyan.INFO); + expect(debug._level).to.equal(bunyan.DEBUG); + expect(fatal._level).to.equal(bunyan.FATAL); + }); + + it('accepts a name', () => { + const name = 'WeTest!'; + const log = new Logger({ name }); + + expect(log.fields.name).to.equal(name); + }); + + it('accepts custom streams', () => { + const name = 'TestingCustomStream!'; + const level = 'debug'; + const msg = 'Testing!'; + const cb = sinon.stub(); + const log = new Logger({ + name, + level, + streams: [ + { + type: 'raw', + stream: new TestLogger({ cb }) + } + ] + }); + + expect(log.streams.filter((config) => { + return config.stream instanceof TestLogger; + }), 'Adds the custom stream').to.be.ok; + + expect(log.streams).to.have.length.gte(1, 'Has more than one stream'); + + log.debug({ foo: 'bar' }, msg); + expect(cb.lastCall.args[0]).to.include({ + name, + msg, + foo: 'bar' + }, 'Uses the custom stream'); + }); + + it('accepts a stdout flag', () => { + expect(new Logger({ stdout: false }).streams).to.have.length(0); + + // The actual stdout stream differs based on runtime env, just + // asserting is is there. + expect(new Logger({ stdout: true }).streams).to.have.length(1); + }); + + it('accepts a logentriesToken', () => { + expect(new Logger().streams.find((config) => { + return config.name === 'logentries'; + })).to.not.be.ok; + + const log = new Logger({ + logentriesToken: fakeToken + }); + + // The actual logentries steam differs based on runtime env, just + // asserting one is there + expect(log.streams.find((config) => { + return config.name === 'logentries'; + })).to.be.ok; + }); + + it('accepts a rollbarToken', () => { + expect(new Logger().streams.find((config) => { + return config.name === 'rollbar'; + })).to.not.be.ok; + + const log = new Logger({ + rollbarToken: fakeToken + }); + + // The actual rollbar steam differs based on runtime env, just + // asserting one is there + expect(log.streams.find((config) => { + return config.name === 'rollbar'; + })).to.be.ok; + }); + + it.skip('accepts an environment', () => {}); + it.skip('accepts a codeVersion', () => {}); + it.skip('accepts custom serializers', () => {}); + }); + + describe('instance', () => { + let log; + beforeEach(() => { + log = new Logger(); + }); + + it('has a #fatal method', () => { + expect(log.fatal).to.be.a('function'); + }); + it('has a #error method', () => { + expect(log.error).to.be.a('function'); + }); + it('has a #warn method', () => { + expect(log.warn).to.be.a('function'); + }); + it('has a #info method', () => { + expect(log.info).to.be.a('function'); + }); + it('has a #debug method', () => { + expect(log.debug).to.be.a('function'); + }); + it('has a #trace method', () => { + expect(log.trace).to.be.a('function'); + }); + }); + + describe.skip('Rollbar Transport', () => {}); + describe.skip('Logentries Transport', () => {}); +}); \ No newline at end of file diff --git a/test/testLogger.js b/test/testLogger.js new file mode 100644 index 0000000..d110d1a --- /dev/null +++ b/test/testLogger.js @@ -0,0 +1,12 @@ +/** + * @module we-js-logger/test/testLogger + * @description Custom rollbar stream for testing + */ + +export default function TestLogger(opts) { + this.cb = opts.cb; +} + +TestLogger.prototype.write = function (data = {}) { + this.cb(data); +}; diff --git a/test/webpack.client.config.js b/test/webpack.client.config.js new file mode 100644 index 0000000..1d73667 --- /dev/null +++ b/test/webpack.client.config.js @@ -0,0 +1,37 @@ +const path = require('path'); + +module.exports = { + devtool: 'inline-source-map', + module: { + noParse: [ + /node_modules(\\|\/)sinon/ + ], + loaders: [ + { + test: /\.json$/, + loader: 'json-loader' + }, + { + test: /\.js$/, + include: [ + path.resolve(process.cwd(), 'src'), + path.resolve(process.cwd(), 'test') + ], + loader: 'babel-loader' + }, + { + test: /sinon(\\|\/)pkg(\\|\/)sinon\.js/, + loader: 'imports?define=>false,require=>false' + } + ] + }, + resolve: { + alias: { + sinon: 'sinon/pkg/sinon' + } + }, + node: { + fs: 'empty', + module: 'empty', + } +}; \ No newline at end of file