feat(log): new lib to help logging (#3414)
Fixes #2414 Related to #1669
This commit is contained in:
committed by
Pierre Donias
parent
527eb0b1e6
commit
0fe70b1a91
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = require('../../@xen-orchestra/babel-config')(
|
||||||
|
require('./package.json')
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/benchmark/
|
||||||
|
/benchmarks/
|
||||||
|
*.bench.js
|
||||||
|
*.bench.js.map
|
||||||
|
|
||||||
|
/examples/
|
||||||
|
example.js
|
||||||
|
example.js.map
|
||||||
|
*.example.js
|
||||||
|
*.example.js.map
|
||||||
|
|
||||||
|
/fixture/
|
||||||
|
/fixtures/
|
||||||
|
*.fixture.js
|
||||||
|
*.fixture.js.map
|
||||||
|
*.fixtures.js
|
||||||
|
*.fixtures.js.map
|
||||||
|
|
||||||
|
/test/
|
||||||
|
/tests/
|
||||||
|
*.spec.js
|
||||||
|
*.spec.js.map
|
||||||
|
|
||||||
|
__snapshots__/
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# @xen-orchestra/log [](https://travis-ci.org/vatesfr/xen-orchestra)
|
||||||
|
|
||||||
|
> ${pkg.description}
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/log):
|
||||||
|
|
||||||
|
```
|
||||||
|
> npm install --save @xen-orchestra/log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Everywhere something should be logged:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import createLogger from '@xen-orchestra/log'
|
||||||
|
|
||||||
|
const log = createLogger('my-module')
|
||||||
|
|
||||||
|
log.debug('only useful for debugging')
|
||||||
|
log.info('this information is relevant to the user')
|
||||||
|
log.warn('something went wrong but did not prevent current action')
|
||||||
|
log.error('something went wrong')
|
||||||
|
log.fatal('service/app is going down')
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, at application level, configure the logs are handled:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { configure, catchGlobalErrors } from '@xen-orchestra/log/configure'
|
||||||
|
import transportConsole from '@xen-orchestra/log/transports/console'
|
||||||
|
import transportEmail from '@xen-orchestra/log/transports/email'
|
||||||
|
|
||||||
|
const transport = transportEmail({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: 'jane.smith@gmail.com',
|
||||||
|
pass: 'H&NbECcpXF|pyXe#%ZEb'
|
||||||
|
},
|
||||||
|
from: 'jane.smith@gmail.com',
|
||||||
|
to: [
|
||||||
|
'jane.smith@gmail.com',
|
||||||
|
'sam.doe@yahoo.com'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
configure([
|
||||||
|
{
|
||||||
|
// if filter is a string, then it is pattern
|
||||||
|
// (https://github.com/visionmedia/debug#wildcards) which is
|
||||||
|
// matched against the namespace of the logs
|
||||||
|
filter: process.env.DEBUG,
|
||||||
|
|
||||||
|
transport: transportConsole()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// only levels >= warn
|
||||||
|
level: 'warn',
|
||||||
|
|
||||||
|
transport
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// send all global errors (uncaught exceptions, warnings, unhandled rejections)
|
||||||
|
// to this transport
|
||||||
|
catchGlobalErrors(transport)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transports
|
||||||
|
|
||||||
|
#### Console
|
||||||
|
|
||||||
|
```js
|
||||||
|
import transportConsole from '@xen-orchestra/log/transports/console'
|
||||||
|
|
||||||
|
configure(transports.console())
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Email
|
||||||
|
|
||||||
|
Optional dependency:
|
||||||
|
|
||||||
|
```
|
||||||
|
> yarn add nodemailer pretty-format
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import transportEmail from '@xen-orchestra/log/transports/email'
|
||||||
|
|
||||||
|
configure(transportEmail({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: 'jane.smith@gmail.com',
|
||||||
|
pass: 'H&NbECcpXF|pyXe#%ZEb'
|
||||||
|
},
|
||||||
|
from: 'jane.smith@gmail.com',
|
||||||
|
to: [
|
||||||
|
'jane.smith@gmail.com',
|
||||||
|
'sam.doe@yahoo.com'
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Syslog
|
||||||
|
|
||||||
|
Optional dependency:
|
||||||
|
|
||||||
|
```
|
||||||
|
> yarn add split-host syslog-client
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import transportSyslog from '@xen-orchestra/log/transports/syslog'
|
||||||
|
|
||||||
|
// By default, log to udp://localhost:514
|
||||||
|
configure(transportSyslog())
|
||||||
|
|
||||||
|
// But TCP, a different host, or a different port can be used
|
||||||
|
configure(transportSyslog('tcp://syslog.company.lan'))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```
|
||||||
|
# Install dependencies
|
||||||
|
> yarn
|
||||||
|
|
||||||
|
# Run the tests
|
||||||
|
> yarn test
|
||||||
|
|
||||||
|
# Continuously compile
|
||||||
|
> yarn dev
|
||||||
|
|
||||||
|
# Continuously run the tests
|
||||||
|
> yarn dev-test
|
||||||
|
|
||||||
|
# Build for production (automatically called by npm install)
|
||||||
|
> yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
Contributions are *very* welcomed, either on the documentation or on
|
||||||
|
the code.
|
||||||
|
|
||||||
|
You may:
|
||||||
|
|
||||||
|
- report any [issue](https://github.com/vatesfr/xo-web/issues/)
|
||||||
|
you've encountered;
|
||||||
|
- fork and create a pull request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
ISC © [Vates SAS](https://vates.fr)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./dist/configure')
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "@xen-orchestra/log",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"keywords": [],
|
||||||
|
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/log",
|
||||||
|
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Julien Fontanet",
|
||||||
|
"email": "julien.fontanet@vates.fr"
|
||||||
|
},
|
||||||
|
"preferGlobal": false,
|
||||||
|
"main": "dist/",
|
||||||
|
"bin": {},
|
||||||
|
"files": [
|
||||||
|
"dist/"
|
||||||
|
],
|
||||||
|
"browserslist": [
|
||||||
|
">2%"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/polyfill": "7.0.0",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"promise-toolbox": "^0.10.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "7.0.0",
|
||||||
|
"@babel/core": "7.0.0",
|
||||||
|
"@babel/preset-env": "7.0.0",
|
||||||
|
"babel-plugin-lodash": "^3.3.2",
|
||||||
|
"cross-env": "^5.1.3",
|
||||||
|
"index-modules": "^0.3.0",
|
||||||
|
"rimraf": "^2.6.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
|
||||||
|
"clean": "rimraf dist/",
|
||||||
|
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
|
||||||
|
"prebuild": "yarn run clean",
|
||||||
|
"predev": "yarn run prebuild",
|
||||||
|
"prepublishOnly": "yarn run build"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import createConsoleTransport from './transports/console'
|
||||||
|
import LEVELS, { resolve } from './levels'
|
||||||
|
import { compileGlobPattern } from './utils'
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const createTransport = config => {
|
||||||
|
if (typeof config === 'function') {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(config)) {
|
||||||
|
const transports = config.map(createTransport)
|
||||||
|
const { length } = transports
|
||||||
|
return function () {
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
transports[i].apply(this, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let { filter, transport } = config
|
||||||
|
const level = resolve(config.level)
|
||||||
|
|
||||||
|
if (filter !== undefined) {
|
||||||
|
if (typeof filter === 'string') {
|
||||||
|
const re = compileGlobPattern(filter)
|
||||||
|
filter = log => re.test(log.namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
const orig = transport
|
||||||
|
transport = function (log) {
|
||||||
|
if ((level !== undefined && log.level >= level) || filter(log)) {
|
||||||
|
return orig.apply(this, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (level !== undefined) {
|
||||||
|
const orig = transport
|
||||||
|
transport = function (log) {
|
||||||
|
if (log.level >= level) {
|
||||||
|
return orig.apply(this, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbol =
|
||||||
|
typeof Symbol !== 'undefined'
|
||||||
|
? Symbol.for('@xen-orchestra/log')
|
||||||
|
: '@@@xen-orchestra/log'
|
||||||
|
|
||||||
|
global[symbol] = createTransport({
|
||||||
|
// display warnings or above, and all that are enabled via DEBUG or
|
||||||
|
// NODE_DEBUG env
|
||||||
|
filter: process.env.DEBUG || process.env.NODE_DEBUG,
|
||||||
|
level: LEVELS.INFO,
|
||||||
|
|
||||||
|
transport: createConsoleTransport(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const configure = config => {
|
||||||
|
global[symbol] = createTransport(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const catchGlobalErrors = logger => {
|
||||||
|
// patch process
|
||||||
|
const onUncaughtException = error => {
|
||||||
|
logger.error('uncaught exception', { error })
|
||||||
|
}
|
||||||
|
const onUnhandledRejection = error => {
|
||||||
|
logger.warn('possibly unhandled rejection', { error })
|
||||||
|
}
|
||||||
|
const onWarning = error => {
|
||||||
|
logger.warn('Node warning', { error })
|
||||||
|
}
|
||||||
|
process.on('uncaughtException', onUncaughtException)
|
||||||
|
process.on('unhandledRejection', onUnhandledRejection)
|
||||||
|
process.on('warning', onWarning)
|
||||||
|
|
||||||
|
// patch EventEmitter
|
||||||
|
const EventEmitter = require('events')
|
||||||
|
const { prototype } = EventEmitter
|
||||||
|
const { emit } = prototype
|
||||||
|
function patchedEmit (event, error) {
|
||||||
|
if (event === 'error' && !this.listenerCount(event)) {
|
||||||
|
logger.error('unhandled error event', { error })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return emit.apply(this, arguments)
|
||||||
|
}
|
||||||
|
prototype.emit = patchedEmit
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
process.removeListener('uncaughtException', onUncaughtException)
|
||||||
|
process.removeListener('unhandledRejection', onUnhandledRejection)
|
||||||
|
process.removeListener('warning', onWarning)
|
||||||
|
|
||||||
|
if (prototype.emit === patchedEmit) {
|
||||||
|
prototype.emit = emit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import createTransport from './transports/console'
|
||||||
|
import LEVELS from './levels'
|
||||||
|
|
||||||
|
const symbol =
|
||||||
|
typeof Symbol !== 'undefined'
|
||||||
|
? Symbol.for('@xen-orchestra/log')
|
||||||
|
: '@@@xen-orchestra/log'
|
||||||
|
if (!(symbol in global)) {
|
||||||
|
// the default behavior, without requiring `configure` is to avoid
|
||||||
|
// logging anything unless it's a real error
|
||||||
|
const transport = createTransport()
|
||||||
|
global[symbol] = log => log.level > LEVELS.WARN && transport(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Log (data, level, namespace, message, time) {
|
||||||
|
this.data = data
|
||||||
|
this.level = level
|
||||||
|
this.namespace = namespace
|
||||||
|
this.message = message
|
||||||
|
this.time = time
|
||||||
|
}
|
||||||
|
|
||||||
|
function Logger (namespace) {
|
||||||
|
this._namespace = namespace
|
||||||
|
|
||||||
|
// bind all logging methods
|
||||||
|
for (const name in LEVELS) {
|
||||||
|
const lowerCase = name.toLowerCase()
|
||||||
|
this[lowerCase] = this[lowerCase].bind(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { prototype } = Logger
|
||||||
|
|
||||||
|
for (const name in LEVELS) {
|
||||||
|
const level = LEVELS[name]
|
||||||
|
|
||||||
|
prototype[name.toLowerCase()] = function (message, data) {
|
||||||
|
global[symbol](new Log(data, level, this._namespace, message, new Date()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prototype.wrap = function (message, fn) {
|
||||||
|
const logger = this
|
||||||
|
const warnAndRethrow = error => {
|
||||||
|
logger.warn(message, { error })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
return function () {
|
||||||
|
try {
|
||||||
|
const result = fn.apply(this, arguments)
|
||||||
|
const then = result != null && result.then
|
||||||
|
return typeof then === 'function'
|
||||||
|
? then.call(result, warnAndRethrow)
|
||||||
|
: result
|
||||||
|
} catch (error) {
|
||||||
|
warnAndRethrow(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLogger = namespace => new Logger(namespace)
|
||||||
|
export { createLogger as default }
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const LEVELS = Object.create(null)
|
||||||
|
export { LEVELS as default }
|
||||||
|
|
||||||
|
// https://github.com/trentm/node-bunyan#levels
|
||||||
|
LEVELS.FATAL = 60 // service/app is going down
|
||||||
|
LEVELS.ERROR = 50 // fatal for current action
|
||||||
|
LEVELS.WARN = 40 // something went wrong but it's not fatal
|
||||||
|
LEVELS.INFO = 30 // detail on unusual but normal operation
|
||||||
|
LEVELS.DEBUG = 20
|
||||||
|
|
||||||
|
export const NAMES = Object.create(null)
|
||||||
|
for (const name in LEVELS) {
|
||||||
|
NAMES[LEVELS[name]] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resolve = level => {
|
||||||
|
if (typeof level === 'string') {
|
||||||
|
level = LEVELS[level.toUpperCase()]
|
||||||
|
}
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.freeze(LEVELS)
|
||||||
|
Object.freeze(NAMES)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
import { forEach, isInteger } from 'lodash'
|
||||||
|
|
||||||
|
import LEVELS, { NAMES, resolve } from './levels'
|
||||||
|
|
||||||
|
describe('LEVELS', () => {
|
||||||
|
it('maps level names to their integer values', () => {
|
||||||
|
forEach(LEVELS, (value, name) => {
|
||||||
|
expect(isInteger(value)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('NAMES', () => {
|
||||||
|
it('maps level values to their names', () => {
|
||||||
|
forEach(LEVELS, (value, name) => {
|
||||||
|
expect(NAMES[value]).toBe(name)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolve()', () => {
|
||||||
|
it('returns level values either from values or names', () => {
|
||||||
|
forEach(LEVELS, value => {
|
||||||
|
expect(resolve(value)).toBe(value)
|
||||||
|
})
|
||||||
|
forEach(NAMES, (name, value) => {
|
||||||
|
expect(resolve(name)).toBe(+value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import LEVELS, { NAMES } from '../levels'
|
||||||
|
|
||||||
|
// Bind console methods (necessary for browsers)
|
||||||
|
const debugConsole = console.log.bind(console)
|
||||||
|
const infoConsole = console.info.bind(console)
|
||||||
|
const warnConsole = console.warn.bind(console)
|
||||||
|
const errorConsole = console.error.bind(console)
|
||||||
|
|
||||||
|
const { ERROR, INFO, WARN } = LEVELS
|
||||||
|
|
||||||
|
const consoleTransport = ({ data, level, namespace, message, time }) => {
|
||||||
|
const fn =
|
||||||
|
level < INFO
|
||||||
|
? debugConsole
|
||||||
|
: level < WARN
|
||||||
|
? infoConsole
|
||||||
|
: level < ERROR
|
||||||
|
? warnConsole
|
||||||
|
: errorConsole
|
||||||
|
|
||||||
|
fn('%s - %s - [%s] %s', time.toISOString(), namespace, NAMES[level], message)
|
||||||
|
data != null && fn(data)
|
||||||
|
}
|
||||||
|
export default () => consoleTransport
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import fromCallback from 'promise-toolbox/fromCallback'
|
||||||
|
import prettyFormat from 'pretty-format' // eslint-disable-line node/no-extraneous-import
|
||||||
|
import { createTransport } from 'nodemailer' // eslint-disable-line node/no-extraneous-import
|
||||||
|
|
||||||
|
import { evalTemplate, required } from '../utils'
|
||||||
|
import { NAMES } from '../levels'
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
// transport options (https://nodemailer.com/smtp/)
|
||||||
|
auth,
|
||||||
|
authMethod,
|
||||||
|
host,
|
||||||
|
ignoreTLS,
|
||||||
|
port,
|
||||||
|
proxy,
|
||||||
|
requireTLS,
|
||||||
|
secure,
|
||||||
|
service,
|
||||||
|
tls,
|
||||||
|
|
||||||
|
// message options (https://nodemailer.com/message/)
|
||||||
|
bcc,
|
||||||
|
cc,
|
||||||
|
from = required('from'),
|
||||||
|
to = required('to'),
|
||||||
|
subject = '[{{level}} - {{namespace}}] {{time}} {{message}}',
|
||||||
|
}) => {
|
||||||
|
const transporter = createTransport(
|
||||||
|
{
|
||||||
|
auth,
|
||||||
|
authMethod,
|
||||||
|
host,
|
||||||
|
ignoreTLS,
|
||||||
|
port,
|
||||||
|
proxy,
|
||||||
|
requireTLS,
|
||||||
|
secure,
|
||||||
|
service,
|
||||||
|
tls,
|
||||||
|
|
||||||
|
disableFileAccess: true,
|
||||||
|
disableUrlAccess: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bcc,
|
||||||
|
cc,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return log =>
|
||||||
|
fromCallback(cb =>
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
subject: evalTemplate(
|
||||||
|
subject,
|
||||||
|
key =>
|
||||||
|
key === 'level'
|
||||||
|
? NAMES[log.level]
|
||||||
|
: key === 'time'
|
||||||
|
? log.time.toISOString()
|
||||||
|
: log[key]
|
||||||
|
),
|
||||||
|
text: prettyFormat(log.data),
|
||||||
|
},
|
||||||
|
cb
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export default () => {
|
||||||
|
const memoryLogger = log => {
|
||||||
|
logs.push(log)
|
||||||
|
}
|
||||||
|
const logs = (memoryLogger.logs = [])
|
||||||
|
return memoryLogger
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import fromCallback from 'promise-toolbox/fromCallback'
|
||||||
|
import splitHost from 'split-host' // eslint-disable-line node/no-extraneous-import node/no-missing-import
|
||||||
|
import startsWith from 'lodash/startsWith'
|
||||||
|
import { createClient, Facility, Severity, Transport } from 'syslog-client' // eslint-disable-line node/no-extraneous-import node/no-missing-import
|
||||||
|
|
||||||
|
import LEVELS from '../levels'
|
||||||
|
|
||||||
|
// https://github.com/paulgrove/node-syslog-client#syslogseverity
|
||||||
|
const LEVEL_TO_SEVERITY = {
|
||||||
|
[LEVELS.FATAL]: Severity.Critical,
|
||||||
|
[LEVELS.ERROR]: Severity.Error,
|
||||||
|
[LEVELS.WARN]: Severity.Warning,
|
||||||
|
[LEVELS.INFO]: Severity.Informational,
|
||||||
|
[LEVELS.DEBUG]: Severity.Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
const facility = Facility.User
|
||||||
|
|
||||||
|
export default target => {
|
||||||
|
const opts = {}
|
||||||
|
if (target !== undefined) {
|
||||||
|
if (startsWith(target, 'tcp://')) {
|
||||||
|
target = target.slice(6)
|
||||||
|
opts.transport = Transport.Tcp
|
||||||
|
} else if (startsWith(target, 'udp://')) {
|
||||||
|
target = target.slice(6)
|
||||||
|
opts.transport = Transport.Udp
|
||||||
|
}
|
||||||
|
|
||||||
|
;({ host: target, port: opts.port } = splitHost(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createClient(target, opts)
|
||||||
|
|
||||||
|
return log =>
|
||||||
|
fromCallback(cb =>
|
||||||
|
client.log(log.message, {
|
||||||
|
facility,
|
||||||
|
severity: LEVEL_TO_SEVERITY[log.level],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import escapeRegExp from 'lodash/escapeRegExp'
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const TPL_RE = /\{\{(.+?)\}\}/g
|
||||||
|
export const evalTemplate = (tpl, data) => {
|
||||||
|
const getData =
|
||||||
|
typeof data === 'function' ? (_, key) => data(key) : (_, key) => data[key]
|
||||||
|
|
||||||
|
return tpl.replace(TPL_RE, getData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
const compileGlobPatternFragment = pattern =>
|
||||||
|
pattern
|
||||||
|
.split('*')
|
||||||
|
.map(escapeRegExp)
|
||||||
|
.join('.*')
|
||||||
|
|
||||||
|
export const compileGlobPattern = pattern => {
|
||||||
|
const no = []
|
||||||
|
const yes = []
|
||||||
|
pattern.split(/[\s,]+/).forEach(pattern => {
|
||||||
|
if (pattern[0] === '-') {
|
||||||
|
no.push(pattern.slice(1))
|
||||||
|
} else {
|
||||||
|
yes.push(pattern)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const raw = ['^']
|
||||||
|
|
||||||
|
if (no.length !== 0) {
|
||||||
|
raw.push('(?!', no.map(compileGlobPatternFragment).join('|'), ')')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yes.length !== 0) {
|
||||||
|
raw.push('(?:', yes.map(compileGlobPatternFragment).join('|'), ')')
|
||||||
|
} else {
|
||||||
|
raw.push('.*')
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.push('$')
|
||||||
|
|
||||||
|
return new RegExp(raw.join(''))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const required = name => {
|
||||||
|
throw new Error(`missing required arg ${name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const serializeError = error => ({
|
||||||
|
...error,
|
||||||
|
message: error.message,
|
||||||
|
name: error.name,
|
||||||
|
stack: error.stack,
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
import { compileGlobPattern } from './utils'
|
||||||
|
|
||||||
|
describe('compileGlobPattern()', () => {
|
||||||
|
it('works', () => {
|
||||||
|
const re = compileGlobPattern('foo, ba*, -bar')
|
||||||
|
expect(re.test('foo')).toBe(true)
|
||||||
|
expect(re.test('bar')).toBe(false)
|
||||||
|
expect(re.test('baz')).toBe(true)
|
||||||
|
expect(re.test('qux')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
dist/transports
|
||||||
Reference in New Issue
Block a user