├── .gitignore ├── LICENSE ├── Readme.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | pids 5 | *.pid 6 | *.seed 7 | node_modules 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017- Nick Baugh 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.md: -------------------------------------------------------------------------------- 1 | > This package is deprecated and a better alternative has been created, please use Cabin: https://cabinjs.com 2 | 3 | # winston-raven-sentry 4 | 5 | [![node](https://img.shields.io/badge/node-4.8.4+-brightgreen.svg)][node-url] 6 | [![raven](https://img.shields.io/badge/raven-1.x+-brightgreen.svg)][raven-url] 7 | [![winston](https://img.shields.io/badge/winston-2.x+-brightgreen.svg)][winston-url] 8 | [![koa](https://img.shields.io/badge/koa-2.x+-brightgreen.svg)][koa] 9 | [![express](https://img.shields.io/badge/express-4.x+-brightgreen.svg)][express] 10 | [![license](https://img.shields.io/github/license/niftylettuce/winston-raven-sentry.svg)][license-url] 11 | 12 | The **maintained** and **well-documented** [Raven](https://github.com/getsentry/raven-node)/[Sentry](https://sentry.io) transport for the [winston](https://github.com/winstonjs/winston) logger with support for [Koa][koa]/[Express][express]/[Passport][passport]. 13 | 14 | > This was designed as a **complete** and **feature-packed** drop-in replacement for 15+ unmaintained and poorly documented packages on NPM for [sentry][npm-sentry] and [raven][npm-raven] winston loggers... such as `winston-sentry`, `winston-raven`, `sentry-logger`, etc. 15 | 16 | 17 | ## Index 18 | 19 | * [Install](#install) 20 | * [Usage](#usage) 21 | * [How to use with Koa/Express/Passport?](#how-to-use-with-koa-express-passport-) 22 | - [Koa Example](#koa-example) 23 | - [Express Example](#express-example) 24 | * [Options](#options-options) 25 | - [Default Raven Options](#default-raven-options-optionsconfig) 26 | - [Default Error Handler](#default-error-handler-optionserrorhandler) 27 | - [Uncaught Exceptions](#uncaught-exceptions) 28 | - [Unhandled Promise Rejections](#unhandled-promise-rejections) 29 | - [Log Level Mapping](#log-level-mapping) 30 | - [Custom Attributes](#custom-attributes) 31 | * [Automatic Extra Error Object](#automatic-extra-error-object) 32 | * [Recommended Logging Approach](#recommended-logging-approach) 33 | * [License](#license) 34 | 35 | 36 | ## Install 37 | 38 | ```bash 39 | npm install --save winston winston-raven-sentry 40 | ``` 41 | 42 | 43 | ## Usage 44 | 45 | You can configure `winston-raven-sentry` in two different ways. 46 | 47 | With `new winston.Logger`: 48 | 49 | ```js 50 | const winston = require('winston'); 51 | const Sentry = require('winston-raven-sentry'); 52 | 53 | const options = { 54 | dsn: 'https://******@sentry.io/12345', 55 | level: 'info' 56 | }; 57 | 58 | const logger = new winston.Logger({ 59 | transports: [ 60 | new Sentry(options) 61 | ] 62 | }); 63 | ``` 64 | 65 | Or with winston's `add` method: 66 | 67 | ```js 68 | const winston = require('winston'); 69 | const Sentry = require('winston-raven-sentry'); 70 | 71 | const logger = new winston.Logger(); 72 | 73 | logger.add(Sentry, options); 74 | ``` 75 | 76 | See [Options](#options-options) below for custom configuration. 77 | 78 | 79 | ## How to use with Koa/Express/Passport? 80 | 81 | **Do you want to log your user objects along with every log automatically?** 82 | 83 | If so, we can simply use custom middleware to bind a `logger` method on the `ctx` object in Koa, or the `req` object in Express. 84 | 85 | This example implementation assumes that you're using the standard Passport implementation and that the logged-in `user` object is set to `ctx.state.user` for Koa or `req.user` for Express. This is the standard usage, so you should have nothing to worry about! 86 | 87 | If you need to whitelist only certain fields from `ctx.state.user` or `req.user` (e.g. don't send your passwords to it) then you need to specify the option `parseUser` through `options.config.parseUser` as documented here . The default fields whitelisted are `[ 'id', 'username', 'email' ]`. If you specify `options.config.parseUser: true` then all keys will be collected. If you specify `false` then none will be collected. 88 | 89 | ### Koa Example 90 | 91 | ```js 92 | const winston = require('winston'); 93 | const Sentry = require('winston-raven-sentry'); 94 | const Koa = require('koa'); 95 | const passport = require('koa-passport'); 96 | const _ = require('lodash'); 97 | 98 | const app = new Koa(); 99 | const logger = new winston.Logger(); 100 | 101 | logger.add(Sentry, { 102 | // ... 103 | }); 104 | 105 | app.use(passport.initialize()); 106 | 107 | app.use(logger.transports.sentry.raven.requestHandler(true)); 108 | 109 | app.on('error', function(err, ctx) { 110 | logger.error(err); 111 | }); 112 | 113 | app.listen(3000); 114 | ``` 115 | 116 | > Log an error or info message with `req.logger`: 117 | 118 | ```js 119 | app.use(async function(ctx, next) { 120 | try { 121 | const post = await Post.create({ message: 'hello world' }); 122 | logger.info('post created', { extra: post }); 123 | ctx.body = post; 124 | } catch (err) { 125 | ctx.throw(err); 126 | // or you could also do `logger.error(err);`, 127 | // but it's redundant since `app.emit('error')` 128 | // will get invoked when `ctx.throw` occurs in the app 129 | } 130 | }); 131 | ``` 132 | 133 | ### Express Example 134 | 135 | ```js 136 | const winston = require('winston'); 137 | const Sentry = require('winston-raven-sentry'); 138 | const express = require('express'); 139 | const passport = require('passport'); 140 | const _ = require('lodash'); 141 | 142 | const app = new express(); 143 | const logger = new winston.Logger(); 144 | 145 | logger.add(Sentry, { 146 | // ... 147 | }); 148 | 149 | // define this first before all else 150 | app.use(logger.transports.sentry.raven.requestHandler()); 151 | 152 | app.use(passport.initialize()); 153 | 154 | app.get('/', function(req, res, next) { 155 | throw new Error('oops!'); 156 | }); 157 | 158 | // keep this before all other error handlers 159 | app.use(logger.transports.sentry.raven.errorHandler()); 160 | 161 | app.listen(3000); 162 | ``` 163 | 164 | > Log an error or info message with `req.logger`: 165 | 166 | ```js 167 | app.use(async function(req, res, next) { 168 | try { 169 | const post = await Post.create({ message: 'hello world' }); 170 | logger.info('post created', { extra: post }); 171 | res.send(post); 172 | } catch (err) { 173 | logger.error(err); 174 | next(err); 175 | } 176 | }); 177 | ``` 178 | 179 | 180 | ## Options (`options`) 181 | 182 | Per `options` variable above, here are the default options provided: 183 | 184 | Default Sentry options: 185 | 186 | * `dsn` (String) - your Sentry DSN or Data Source Name (defaults to `process.env.SENTRY_DSN`) 187 | * `config` (Object) - a Raven configuration object (see [Default Raven Options](#default-raven-options-optionsconfig) below) 188 | * `install` (Boolean) - automatically catches uncaught exceptions through `Raven.install` if set to true (defaults to `false`) 189 | * `errorHandler` (Function) - a callback function to use for logging Raven errors (e.g. an invalid DSN key). This defaults to logging the `err.message`, see [Default Error Handler](#default-error-handler-optionserrorhandler) below... but if you wish to disable this just pass `errorHandler: false`. If there is already an `error` listener then this function will not get bound. 190 | * `raven` (Object) - an optional instance of `Raven` that is already configured via `Raven.config` (if provided this will be used instead of the `config` option 191 | 192 | Transport related options: 193 | 194 | * `name` (String) - transport's name (defaults to `sentry`) 195 | * `silent` (Boolean) - suppress logging (defaults to `false`) 196 | * `level` (String) - transport's level of messages to log (defaults to `info`) 197 | * `levelsMap` (Object) - log level mapping to Sentry (see [Log Level Mapping](#log-level-mapping) below) 198 | 199 | ### Default Raven Options (`options.config`) 200 | 201 | * `logger` (String) - defaults to `winston-raven-sentry` 202 | * `captureUnhandledRejections` (Boolean) - defaults to `false` 203 | * `culprit` (String) - defaults to the module or function name 204 | * `server_name` (String) - defaults to `process.env.SENTRY_NAME` or `os.hostname()` 205 | * `release` (String) - defaults to `process.env.SENTRY_RELEASE` (see [#343][issue-343] if you'd like to have the git hash or package version as the default) 206 | * `tags` (Array or Object) - no default value 207 | * `environment` (String) - defaults to `process.env.SENTRY_ENVIRONMENT` (see [#345][issue-345] if you'd like to have this default to `process.env.NODE_ENV` instead) 208 | * `modules` (Object) - defaults to `package.json` dependencies 209 | * `extra` (Object) - no default value 210 | * `fingerprint` (Array) - no default value 211 | 212 | For a full list of Raven options, please visit . 213 | 214 | ### Default Error Handler (`options.errorHandler`) 215 | 216 | The default error handler is a function that is simply: 217 | 218 | ```js 219 | function errorHandler(err) { 220 | console.error(err.message); 221 | } 222 | ``` 223 | 224 | ... and it is binded to the event emitter: 225 | 226 | ```js 227 | Raven.on('error', this.options.errorHandler); 228 | ``` 229 | 230 | Therefore if you have specified an invalid DSN key, then you will see its output on the command line. 231 | 232 | For example: 233 | 234 | ```log 235 | raven@2.1.0 alert: failed to send exception to sentry: HTTP Error (401): Invalid api key 236 | HTTP Error (401): Invalid api key 237 | ``` 238 | 239 | If you pass `options.errorHandler: false` then no error handler will be binded. 240 | 241 | ### Uncaught Exceptions 242 | 243 | If you want to log uncaught exceptions with Sentry, then specify `install: true` in options: 244 | 245 | ```js 246 | new Sentry({ 247 | install: true 248 | }); 249 | ``` 250 | 251 | ### Unhandled Promise Rejections 252 | 253 | If you want to log unhandled promise rejections with Sentry, then specify `captureUnhandledRejections: true` in `options.config`: 254 | 255 | ```js 256 | new Sentry({ 257 | config: { 258 | captureUnhandledRejections: true 259 | } 260 | }); 261 | ``` 262 | 263 | ### Log Level Mapping 264 | 265 | Winston logging levels are mapped by default to Sentry's acceptable levels. 266 | 267 | These defaults are set as `options.levelsMap' and are: 268 | 269 | ```js 270 | { 271 | silly: 'debug', 272 | verbose: 'debug', 273 | info: 'info', 274 | debug: 'debug', 275 | warn: 'warning', 276 | error: 'error' 277 | } 278 | ``` 279 | 280 | You can customize how log levels are mapped using the `levelsMap` option: 281 | 282 | ```js 283 | new Sentry({ 284 | levelsMap: { 285 | verbose: 'info' 286 | } 287 | }); 288 | ``` 289 | 290 | If no log level mapping was found for the given `level` passed, then it will not log anything. 291 | 292 | ### Custom Attributes 293 | 294 | If you need to log custom attributes, such as `extra`, `user`, or `tags` attributes, specify them in the `meta` object. 295 | 296 | For example: 297 | 298 | ```js 299 | logger.info('Something happened', { 300 | user: { 301 | id: '123' 302 | }, 303 | extra: { 304 | foo: 'bar' 305 | }, 306 | tags: { 307 | git_commit: 'c0deb10c4' 308 | } 309 | }); 310 | ``` 311 | 312 | 313 | ## Automatic Extra Error Object 314 | 315 | By default, if you provide an `Error` instance to either the `msg` or `meta` arguments to `logger[level](msg, meta)`, then this package will automatically set `meta.extra.err` for you as follows: 316 | 317 | ```js 318 | meta.extra.err = { 319 | stack: err.stack, 320 | message: err.message 321 | } 322 | ``` 323 | 324 | This ensures that your stack trace and error message are visible and saved to Sentry. 325 | 326 | Furthermore, if `msg` was an Error object, then we will automatically set `msg = msg.message`. 327 | 328 | This will prevent you from receiving the following message in Sentry: 329 | 330 | ![example-error](https://i.imgur.com/Bzpk3hr.png) 331 | 332 | 333 | ## Recommended Logging Approach 334 | 335 | Compared to packages such as `winston-sentry` and `winston-raven`, we log messages to Sentry more accurately using `captureMessage` and `captureException` (and take into consideration `Error` instances). There was a core bug in all other similar packages on NPM that did not pass along the log level properly, therefore it was refactored and also built to the standards of `raven` itself (e.g. we utilize the defaults that they also do, see above options). 336 | 337 | Here are a few examples provided below for how we recommend logging: 338 | 339 | > Log an error with stack trace (uses `Raven.captureException`): 340 | 341 | ```js 342 | logger.error(new Error('something happened')); 343 | ``` 344 | 345 | _Note that this will automatically set `extra.err.message = "something happened"` and provide the stack trace as `extra.err.stack`._ 346 | 347 | > Log an error message (uses `Raven.captureException` - don't worry as this method automatically turns the message below into an `Error` instance for us): 348 | 349 | ```js 350 | logger.error('something happened'); 351 | ``` 352 | 353 | _Note that this will automatically set `extra.err.message = "something happened"` and provide the stack trace as `extra.err.stack`._ 354 | 355 | > Log an error with stack trace and extra data (uses `Raven.captureException`): 356 | 357 | ```js 358 | logger.error(new Error('something happened'), { 359 | extra: { 360 | foo: 'bar' 361 | } 362 | }); 363 | ``` 364 | 365 | _Note that this will automatically set `extra.err.message = "something happened"` and provide the stack trace as `extra.err.stack`._ 366 | 367 | > Log an error with stack trace, extra data, and the user that it occurred to (uses `Raven.captureException`): 368 | 369 | ```js 370 | logger.error(new Error('something happened'), { 371 | user: { 372 | id: '123', 373 | email: 'niftylettuce@gmail.com', 374 | username: 'niftylettuce' 375 | }, 376 | extra: { 377 | foo: 'bar' 378 | } 379 | }); 380 | ``` 381 | 382 | _Note that this will automatically set `extra.err.message = "something happened"` and provide the stack trace as `extra.err.stack`._ 383 | 384 | > Log a message (uses `Raven.captureMessage`): 385 | 386 | ```js 387 | logger.info('hello world'); 388 | ``` 389 | 390 | > Log a message and extra data (uses `Raven.captureMessage`): 391 | 392 | ```js 393 | logger.info('hello world', { 394 | extra: { 395 | foo: 'bar' 396 | } 397 | }); 398 | ``` 399 | 400 | > Log a message and tags (uses `Raven.captureMessage`): 401 | 402 | ```js 403 | logger.info('hello world', { 404 | tags: { 405 | component: 'api' 406 | } 407 | }); 408 | ``` 409 | 410 | 411 | ## License 412 | 413 | [MIT License][license-url] 414 | 415 | 416 | [license-url]: LICENSE 417 | [npm-sentry]: https://www.npmjs.com/search?q=sentry+winston 418 | [npm-raven]: https://www.npmjs.com/search?q=raven+winston 419 | [koa]: http://koajs.com/ 420 | [express]: https://expressjs.com 421 | [passport]: http://passportjs.org 422 | [issue-343]: https://github.com/getsentry/raven-node/issues/343 423 | [issue-345]: https://github.com/getsentry/raven-node/issues/345 424 | [node-url]: https://nodejs.org 425 | [raven-url]: https://github.com/getsentry/raven-node 426 | [winston-url]: https://github.com/winstonjs/winston 427 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | const _ = require('lodash'); 3 | const Raven = require('raven'); 4 | const winston = require('winston'); 5 | const util = require('util'); 6 | 7 | function errorHandler(err) { 8 | console.error(err.message); 9 | } 10 | 11 | function Sentry(options) { 12 | 13 | options = options || {}; 14 | options = _.defaultsDeep(options, { 15 | dsn: process.env.SENTRY_DSN || '', 16 | config: { 17 | logger: 'winston-raven-sentry', 18 | captureUnhandledRejections: false 19 | }, 20 | errorHandler, 21 | install: false, 22 | name: 'sentry', 23 | silent: false, 24 | level: 'info', 25 | levelsMap: { 26 | silly: 'debug', 27 | verbose: 'debug', 28 | info: 'info', 29 | debug: 'debug', 30 | warn: 'warning', 31 | error: 'error' 32 | } 33 | }); 34 | 35 | winston.Transport.call(this, _.omit(options, [ 36 | 'levelsMap', 37 | 'install', 38 | 'dsn', 39 | 'config', 40 | 'tags', 41 | 'globalTags', 42 | 'extra', 43 | 'errorHandler', 44 | 'raven' 45 | ])); 46 | 47 | this._levelsMap = options.levelsMap; 48 | 49 | if (options.tags) 50 | options.config.tags = options.tags; 51 | else if (options.globalTags) 52 | options.config.tags = options.globalTags; 53 | 54 | if (options.extra) { 55 | options.config.extra = options.config.extra || {}; 56 | options.config.extra = _.defaults( 57 | options.config.extra, 58 | options.extra 59 | ); 60 | } 61 | 62 | // expose the instance on the transport 63 | this.raven = options.raven || Raven.config(options.dsn, options.config); 64 | 65 | if (_.isFunction(options.errorHandler) && this.raven.listeners('error').length === 0) 66 | this.raven.on('error', options.errorHandler); 67 | 68 | // it automatically will detect if it's already installed 69 | if (options.install || options.patchGlobal) 70 | this.raven.install(); 71 | 72 | 73 | }; 74 | 75 | // Inherit from `winston.Transport` 76 | util.inherits(Sentry, winston.Transport); 77 | 78 | // Define a getter so that `winston.transports.Sentry` 79 | // is available and thus backwards compatible 80 | winston.transports.Sentry = Sentry; 81 | 82 | Sentry.prototype.log = function(level, msg, meta, fn) { 83 | 84 | if (this.silent) return fn(null, true); 85 | if (!(level in this._levelsMap)) return fn(null, true); 86 | 87 | const message = this._normalizeMessage(msg, meta); 88 | const context = _.isObject(meta) ? meta : {}; 89 | context.level = this._levelsMap[level]; 90 | context.extra = this._normalizeExtra(msg, meta); 91 | 92 | if (this._shouldCaptureException(context.level)) 93 | return this.raven.captureException(message, context, function() { 94 | fn(null, true); 95 | }); 96 | 97 | this.raven.captureMessage(message, context, function() { 98 | fn(null, true); 99 | }); 100 | 101 | } 102 | 103 | Sentry.prototype._shouldCaptureException = function(level) { 104 | return level === 'error' || level === 'fatal'; 105 | } 106 | 107 | Sentry.prototype._normalizeExtra = function(msg, meta) { 108 | 109 | const extra = _.isObject(meta) ? (meta.extra || {}) : {}; 110 | 111 | if (_.isError(msg) && !_.isObject(extra.err)) { 112 | extra.err = { stack: msg.stack, message: msg.message }; 113 | } 114 | 115 | if (_.isError(meta) && !_.isObject(extra.err)) { 116 | extra.err = { stack: meta.stack, message: meta.message }; 117 | } 118 | 119 | return extra; 120 | } 121 | 122 | Sentry.prototype._normalizeMessage = function(msg, meta) { 123 | let message = msg; 124 | 125 | if (_.isError(msg)) { 126 | message = msg.message; 127 | } 128 | 129 | if (_.isError(meta) && !_.isString(message)) { 130 | message = meta.message; 131 | } 132 | 133 | return message; 134 | } 135 | 136 | 137 | module.exports = Sentry; 138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winston-raven-sentry", 3 | "description": "The maintained and well-documented Raven/Sentry transport for the winston logger with support for Koa/Express/Passport", 4 | "version": "2.0.0", 5 | "author": "Nick Baugh", 6 | "bugs": { 7 | "url": "https://github.com/niftylettuce/winston-raven-sentry/issues" 8 | }, 9 | "dependencies": { 10 | "lodash": "^4.17.4", 11 | "raven": "^2.1.0" 12 | }, 13 | "devDependencies": { 14 | "winston": "^2.3.1" 15 | }, 16 | "engines": { 17 | "node": ">= 4.8.4" 18 | }, 19 | "main": "index.js", 20 | "homepage": "https://github.com/niftylettuce/winston-raven-sentry#readme", 21 | "keywords": [ 22 | "bunyan", 23 | "logger", 24 | "loggly", 25 | "morgan", 26 | "raven", 27 | "sentry", 28 | "transport", 29 | "winston", 30 | "koa", 31 | "express" 32 | ], 33 | "license": "MIT", 34 | "peerDependencies": { 35 | "winston": ">= 2.x" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/niftylettuce/winston-raven-sentry.git" 40 | } 41 | } 42 | --------------------------------------------------------------------------------