├── .gitignore ├── .travis.yml ├── changelog.md ├── lib └── winston-mail.js ├── package.json ├── readme.md └── test └── winston-mail-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 8 5 | - 10 6 | - 12 7 | sudo: false 8 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 2.0.0 / 2019-06-26 2 | 3 | - Update 'emailjs' dependency 4 | - Drop support for legacy Node versions (4.0 and 0.12) 5 | 6 | # 1.5.2 / 2019-01-03 7 | 8 | - Fix legacy warnings for Winston 3.0 [ericdrobinson] 9 | - Update import statement on README [dickenwong] 10 | 11 | # 1.5.1 / 2018-03-07 12 | 13 | - Fix peer dependency for Winston 3.0 14 | 15 | # 1.5.0 / 2018-03-07 16 | 17 | - Support for Winston 3.0 [bcosma] 18 | 19 | # 1.4.0 / 2018-01-26 20 | 21 | - Add filtration [eugeny-dementev] 22 | 23 | # 1.3.0 / 2016-09-18 24 | 25 | - Bump emailjs dependency to latest (1.0.8). 26 | - Fix "Callback was already called" [falxcerebri] 27 | 28 | # 1.2.0 / 2016-04-29 29 | 30 | - Added custom formatter [drahnieR] 31 | 32 | # 1.1.0 / 2016-03-14 33 | 34 | - Added authentication param for SMTP server [rafalszemraj] 35 | 36 | # 1.0.1 / 2015-12-11 37 | 38 | - Update emailjs and underscore dependency 39 | 40 | # 1.0.0 / 2015-11-06 41 | 42 | - No breaking changes, just locking in a stable semver 43 | - Testing for Node 4/5 44 | - Support Winston >=0.5.0 <3.0.0 45 | - Add timeout option for setting SMTP timeout 46 | 47 | # 0.5.0 / 2015-09-16 48 | 49 | - Handle Error meta [perrin4869] 50 | - Enable boolean to log only the selected level and none above [jamie-ez] 51 | - Docs for subject templating 52 | 53 | # 0.4.0 / 2015-04-13 54 | 55 | - add; option to send html mails [Thelmos] 56 | - mod; support winston 1.0.0 57 | - mod; update license info 58 | 59 | # 0.3.2 / 2015-03-06 60 | 61 | - dep; emailjs@0.3.13 to fix multiple callback issue 62 | 63 | # 0.3.1 / 2015-02-11 64 | 65 | - fix; emailjs [msecs must be header] issue [ivan-kleshnin] 66 | 67 | # 0.3.0 / 2014-12-08 68 | 69 | - mod; support node >= 0.10 70 | - mod; allow override of name [shannonmpoole] 71 | 72 | # 0.2.9 / 2014-09-18 73 | 74 | - mod; bump peer dep to <1.0.0 75 | 76 | # 0.2.8 / 2014-08-27 77 | 78 | - mod; throw Error instance instead of string [jabclab] 79 | - add; license to readme 80 | 81 | # 0.2.7 / 2013-07-09 82 | 83 | - fixed; Do not add meta into the email body if the object is empty (has not properties) [lobodpav] 84 | 85 | # 0.2.5 / 2013-03-03 86 | 87 | - added; peer deps 88 | - language fixes [eitanpo] 89 | 90 | # 0.2.4 / 2013-02-12 91 | 92 | - fixed; allow only single line messages in the subject [emergence] 93 | - fixed; changing global underscore templateSettings breaks underscore templating [shawnburke] 94 | 95 | # 0.2.3 / 2012-07-09 96 | 97 | - removed; support for node 0.4.x 98 | - added; travis for 0.8.x 99 | 100 | # 0.2.2 / 2012-06-14 101 | 102 | - updated; test suite 103 | - adding; underscore templating to subject line [danielschwartz] 104 | 105 | # 0.2.1 / 2012-02-27 106 | 107 | - updated; test suite 108 | 109 | # 0.2.0 / 2012-02-24 110 | 111 | - updated; changed email module to emailjs 112 | 113 | # 0.1.3 / 2012-01-13 114 | 115 | - added; dummy SMTP server for tests 116 | - updated; test config 117 | - added; Travis CI integration 118 | 119 | # 0.1.2 / 2011-11-30 120 | 121 | - added; pretty json printing 122 | 123 | # 0.1.1 / 2011-11-30 124 | 125 | - fixed; uncaughtException not firing 126 | 127 | # 0.1.0 / 2011-11-29 128 | 129 | - updated; readme docs 130 | - updated; changed default from to winston@[server-host] 131 | - updated; tests 132 | 133 | # 0.0.1 / 2011-11-28 134 | 135 | - initial release 136 | -------------------------------------------------------------------------------- /lib/winston-mail.js: -------------------------------------------------------------------------------- 1 | /* 2 | * winston-mail.js: Transport for outputting logs to email. 3 | * 4 | * (C) 2016 Marc Harter 5 | * MIT LICENCE 6 | */ 7 | 8 | var util = require('util') 9 | var os = require('os') 10 | var email = require('emailjs') 11 | var winston = require('winston') 12 | var mustache = require('mustache') 13 | 14 | /** 15 | * @constructs Mail 16 | * @param {object} options Hash of options. 17 | */ 18 | var Mail = exports.Mail = function(options) { 19 | options = options || {} 20 | 21 | winston.Transport.call(this, options); 22 | 23 | if (!options.to) { 24 | throw new Error("winston-mail requires 'to' property") 25 | } 26 | 27 | this.name = options.name || 'mail' 28 | this.to = options.to 29 | this.from = options.from || 'winston@' + os.hostname() 30 | this.level = options.level || 'info' 31 | this.unique = options.unique || false 32 | this.silent = options.silent || false 33 | this.filter = options.filter || false 34 | this.subject = options.subject || 'winston: {{level}} {{msg}}' 35 | this.html = options.html || false // Send mail in html format 36 | this.formatter = options.formatter || false 37 | 38 | this.handleExceptions = options.handleExceptions || false 39 | this.server = email.server.connect({ 40 | user : options.username, 41 | password : options.password, 42 | port : options.port, 43 | host : options.host, 44 | ssl : options.ssl || options.secure, 45 | tls : options.tls, 46 | timeout : options.timeout, 47 | authentication : options.authentication, 48 | }) 49 | } 50 | 51 | /** @extends winston.Transport */ 52 | util.inherits(Mail, winston.Transport) 53 | 54 | /** 55 | * Define a getter so that `winston.transports.Mail` 56 | * is available and thus backwards compatible. 57 | */ 58 | winston.transports.Mail = Mail 59 | 60 | /** 61 | * Core logging method exposed to Winston. Metadata is optional. 62 | * @function log 63 | * @member Mail 64 | * @param arg0 winston < 3 {string} Level at which to log the message. 65 | winston >= 3 {Object} {level, message, meta} Info about log 66 | * @param arg1 winston < 3 {string} Message to log. 67 | winston >= 3 {function} Continuation to respond to when complete. 68 | * @param arg2 winston < 3 {?Object} Additional metadata to attach. 69 | winston >= 3 undefined 70 | * @param arg3 winston < 3 {function} Continuation to respond to when complete. 71 | winston >= 3 undefined 72 | */ 73 | Mail.prototype.log = function(arg0, arg1) { 74 | // get winston version to create tests accordingly 75 | var winstonVersion = winston.version, 76 | majorWVersion = winstonVersion.split('.')[0], 77 | self = this; 78 | 79 | var callback, level, msg, meta, info; 80 | 81 | if (majorWVersion >= 3 ) { 82 | // in version above 3 we have only 2 parameters: info and callback; 83 | info = arg0; 84 | 85 | callback = arg1; 86 | level = info.level; 87 | msg = info.message || ''; 88 | meta = info.meta; 89 | } else { 90 | level = arg0; 91 | msg = arg1; 92 | meta = arguments[2]; 93 | callback = arguments[3]; 94 | info = { 95 | level: level, 96 | message: msg, 97 | meta: meta, 98 | }; 99 | } 100 | 101 | if (this.silent) return callback(null, true) 102 | if (this.unique && this.level != level) return callback(null, true) 103 | if (this.filter && !this.filter(info)) return callback(null, true) 104 | 105 | if (this.formatter) { 106 | var body = this.formatter(info) 107 | } else { 108 | var body = msg 109 | 110 | // Convert meta to string if it is an error. 111 | if (meta instanceof Error) { 112 | meta = { 113 | message: meta.message, 114 | name: meta.name, 115 | stack: meta.stack, 116 | } 117 | } 118 | 119 | // Add meta info into the body if not empty. 120 | if (meta !== null && meta !== undefined && (typeof meta !== 'object' || Object.keys(meta).length > 0)) 121 | body += '\n\n' + util.inspect(meta, {depth: 5}) // Add some pretty printing. 122 | } 123 | 124 | var msgOptions = { 125 | from: this.from, 126 | to: this.to, 127 | subject: mustache.render(this.subject, {level: level, msg: msg.split('\n')[0]}), 128 | text: body, 129 | } 130 | 131 | // Send mail as html. 132 | if (this.html) { 133 | msgOptions.attachment = [{data: body , alternative:true}] 134 | } 135 | 136 | var message = email.message.create(msgOptions) 137 | 138 | this.server.send(message, function(err) { 139 | if (err) self.emit('error', err) 140 | self.emit('logged') 141 | 142 | try { 143 | callback(null, true) 144 | } catch (e) { 145 | console.error(e) 146 | } 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winston-mail", 3 | "description": "A mail transport for winston", 4 | "version": "2.0.0", 5 | "author": "Marc Harter ", 6 | "contributors": [ 7 | "Ilya Volodarskyi " 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "http://github.com/wavded/winston-mail.git" 12 | }, 13 | "keywords": [ 14 | "logging", 15 | "sysadmin", 16 | "tools", 17 | "winston", 18 | "email" 19 | ], 20 | "dependencies": { 21 | "emailjs": "^2.2.0", 22 | "mustache": "^2.2.1" 23 | }, 24 | "devDependencies": { 25 | "smtp-server": "^1.14.2", 26 | "tape": "^4.6.0", 27 | "winston": "^2.2.0" 28 | }, 29 | "peerDependencies": { 30 | "winston": ">=0.5.0" 31 | }, 32 | "main": "./lib/winston-mail", 33 | "scripts": { 34 | "test": "tape test/*-test.js" 35 | }, 36 | "engines": { 37 | "node": ">= 0.6.0" 38 | }, 39 | "license": "MIT" 40 | } 41 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![NPM](https://img.shields.io/npm/v/winston-mail.svg) [![Build Status](https://travis-ci.org/wavded/winston-mail.svg?branch=master)](https://travis-ci.org/wavded/winston-mail) ![Downloads](https://img.shields.io/npm/dt/winston-mail.svg) 2 | 3 | A email transport for [winston][0]. 4 | 5 | ## Installation 6 | 7 | ``` sh 8 | $ npm install winston 9 | $ npm install winston-mail 10 | ``` 11 | 12 | ## Usage 13 | ``` js 14 | var winston = require('winston'); 15 | 16 | /* 17 | * Requiring `winston-mail` will expose 18 | * `winston.transports.Mail` 19 | */ 20 | require('winston-mail'); 21 | 22 | winston.add(winston.transports.Mail, options); 23 | ``` 24 | 25 | The Mail transport uses [emailjs](https://github.com/eleith/emailjs) behind the scenes. Options are the following: 26 | 27 | * __to:__ The address(es) you want to send to. *[required]* 28 | * __from:__ The address you want to send from. (default: `winston@[server-host-name]`) 29 | * __host:__ SMTP server hostname (default: localhost) 30 | * __port:__ SMTP port (default: 587 or 25) 31 | * __username__ User for server auth 32 | * __password__ Password for server auth 33 | * __subject__ Subject for email (default: winston: {{level}} {{msg}}) 34 | * __ssl:__ Use SSL (boolean or object { key, ca, cert }) 35 | * __tls:__ Boolean (if true, use starttls) 36 | * __level:__ Level of messages that this transport should log. 37 | * __unique:__ Boolean flag indicating whether to log only the declared level and none above. 38 | * __silent:__ Boolean flag indicating whether to suppress output. 39 | * __filter:__ Filter function with signature `function({level, message, meta})`. If specified, should return `true` for log messages that need to send. 40 | * __html:__ Boolean flag indicating whether to send mail body as html attach. 41 | * __timeout:__ Maximum number of milliseconds to wait for smtp responses (optional, defaults to emailjs defaults - 5000) 42 | * __authentication:__ Preffered SMTP auth methods (optional, defaults to emailjs defaults - ['PLAIN', 'CRAM-MD5', 'LOGIN', 'XOAUTH2']) 43 | * __formatter:__ Custom mail body formatter with signature `function({level, message, meta})`. If specified, the return value will be used as mail body. 44 | 45 | ## Subject templating 46 | 47 | The __subject__ option takes a mustache template string that exposes the following fields: 48 | - `{{level}}` - Log level 49 | - `{{msg}}` - First line of the error message 50 | 51 | ## License 52 | The MIT License (MIT) 53 | 54 | Copyright (C) 2016-2018 Marc Harter 55 | 56 | 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: 57 | 58 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 59 | 60 | 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. 61 | 62 | [0]: https://github.com/flatiron/winston 63 | -------------------------------------------------------------------------------- /test/winston-mail-test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var winston = require('winston') 3 | var SMTPServer = require('smtp-server').SMTPServer 4 | var Mail = require('../lib/winston-mail').Mail 5 | 6 | var testFn = function() {} 7 | var smtp = new SMTPServer({ 8 | disabledCommands: ['AUTH'], 9 | onData: function(stream, session, cb) { 10 | var data = '' 11 | stream.on('data', function(chunk) { data += chunk }) 12 | stream.on('error', cb) 13 | stream.on('end', function() { testFn(data); cb() }) 14 | }, 15 | }) 16 | 17 | test('set up email server', function(t) { 18 | smtp.listen(2500, '0.0.0.0', function(er) { 19 | t.error(er) 20 | t.end() 21 | }) 22 | }) 23 | 24 | test('winston-mail', function(t) { 25 | // get winston version to create tests accordingly 26 | var winstonVersion = winston.version, 27 | majorWVersion = winstonVersion.split('.')[0]; 28 | var table = [ 29 | {level: 'info', subject: '{{level}}', test: 'info'}, 30 | {message: 'goodbye', level: 'error', test: 'goodbye'}, 31 | {message: 'hello', level: 'info', subject: '{{message}}', test: 'hello'}, 32 | {level: 'warn', formatter: function(d) { return '!' + d.level + '!' }, test: '!warn!'}, 33 | ]; 34 | 35 | t.plan(table.length) 36 | 37 | function run(tt) { 38 | if (!tt) return 39 | 40 | var transport = new Mail({ 41 | to: 'dev@server.com', 42 | from: 'dev@server.com', 43 | port: 2500, 44 | subject: tt.subject, 45 | formatter: tt.formatter, 46 | }) 47 | var logger; 48 | 49 | if (majorWVersion >= 3) { 50 | logger = new winston.createLogger({ 51 | transports: [transport]}); 52 | } else { 53 | logger = new winston.Logger({ 54 | transports: [transport]}); 55 | } 56 | 57 | testFn = function(data) { 58 | t.ok(RegExp(tt.test).test(data)) 59 | run(table.shift()) 60 | } 61 | if (majorWVersion >= 3) { 62 | // for version 3 and above use the new log function with info object as a parameter 63 | logger.log({level:tt.level, message:tt.message}); 64 | } else { 65 | // for version bellow 3 use the old log function with 3 parameters: level, message, meta 66 | logger[tt.level](tt.message); 67 | } 68 | } 69 | run(table.shift()) 70 | }) 71 | 72 | test(function(t) { 73 | smtp.close(function() { t.end() }) 74 | }) 75 | --------------------------------------------------------------------------------