niveau
Advanced tools
Comparing version 0.0.3 to 0.1.0
@@ -29,3 +29,3 @@ 'use strict'; | ||
} catch (e) { | ||
store.emit(new VError(e, 'Failed to parse log config')); | ||
store.emit('error', new VError(e, 'Failed to parse log config')); | ||
} | ||
@@ -32,0 +32,0 @@ }); |
{ | ||
"name": "niveau", | ||
"version": "0.0.3", | ||
"version": "0.1.0", | ||
"description": "Node.js package to switch log level per request in Cloud Foundry", | ||
@@ -12,3 +12,4 @@ "main": "lib", | ||
"coverage": "nyc report --reporter=html && echo Open coverage/index.html", | ||
"redis": "docker run -d -p 6379:6379 --rm redis" | ||
"redis": "docker run -d -p 6379:6379 --rm redis", | ||
"toc": "markdown-toc -i README.md" | ||
}, | ||
@@ -53,2 +54,3 @@ "bin": { | ||
"express": "^4.16.2", | ||
"markdown-toc": "^1.2.0", | ||
"mocha": "^5.0.0", | ||
@@ -55,0 +57,0 @@ "nyc": "^11.4.1", |
102
README.md
@@ -8,2 +8,24 @@ [![Build Status](https://travis-ci.org/niveau/niveau.svg?branch=master)](https://travis-ci.org/niveau/niveau) | ||
<!-- toc --> | ||
- [Goals](#goals) | ||
- [Requirements](#requirements) | ||
- [Design](#design) | ||
- [Usage](#usage) | ||
* [In the application](#in-the-application) | ||
+ [niveau([options])](#niveauoptions) | ||
+ [Event 'error'](#event-error) | ||
+ [Event 'config'](#event-config) | ||
+ [Event 'request'](#event-request) | ||
+ [Log configuration](#log-configuration) | ||
* [Changing the log level](#changing-the-log-level) | ||
+ [Options](#options) | ||
+ [Invoke via CF task](#invoke-via-cf-task) | ||
+ [Invoke via SSH to the application](#invoke-via-ssh-to-the-application) | ||
+ [Examples](#examples) | ||
- [Contributing](#contributing) | ||
- [Future](#future) | ||
<!-- tocstop --> | ||
## Goals | ||
@@ -62,4 +84,8 @@ * Change the log level without restart - no downtime | ||
}); | ||
nv.on('config', config => { | ||
// log configuration changed | ||
}); | ||
nv.on('request', (req, config) => { | ||
// set log level for this request to config.level | ||
// request matches logging criteria | ||
// set log level for this request to config.level | ||
}); | ||
@@ -69,7 +95,48 @@ | ||
app.use(nv); | ||
app.use((req, res, next) => { | ||
// req.logLevel - the log level to be used for this request (if present) | ||
}); | ||
``` | ||
See example applications in [examples](examples) folder. | ||
#### niveau([options]) | ||
* `options` Redis connection [options](https://github.com/NodeRedis/node_redis#rediscreateclient) + additional properties: | ||
* `redisKey` name of the Redis key that stores the configuration, default is `log-config` | ||
Creates _niveau_ middleware. It matches incoming requests against the criteria in the log configuration. | ||
For matching requests it sets `logLevel` property on the request object to the log level from the configuration. | ||
The middleware also listens for log configuration changes and emits some events. | ||
#### Event 'error' | ||
Event arguments: | ||
* `error` an `Error` object | ||
Emitted in case of error, e.g. Redis connection failed. | ||
#### Event 'config' | ||
Event arguments: | ||
* `config` [log configuration](#log-configuration) object | ||
Emitted when log configuration is changed or deleted. | ||
#### Event 'request' | ||
Event arguments: | ||
* `request` [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) | ||
* `config` [log configuration](#log-configuration) object | ||
Emitted when an HTTP request matches the criteria in the log configuration. | ||
#### Log configuration | ||
Log configuration object: | ||
* `request` request matching criteria, | ||
if missing or empty, the log level should be used for all requests | ||
* `url` `RegExp` to match against the request URL | ||
* `ip` `RegExp` to match against the client IP address | ||
* `headers` an object to match against request headers, each values is a `RegExp` | ||
* `level` log level as a string to use for matching requests | ||
### Changing the log level | ||
This package provides an executable script to change the log level. | ||
The provided log level will be used only for HTTP requests that match the given options. | ||
This package provides a command line tool to change the log level. | ||
The provided log level will be used only for HTTP requests that match _all_ the given criteria. | ||
Each command invocation overwrites any previous settings. | ||
@@ -81,8 +148,9 @@ | ||
#### Options | ||
* -l, --url \<regex> - matches request URL (without protocol, host, port) | ||
* -h, --header \<name>:\<regex> - matches given request header value | ||
* -i, --ip \<regex> - matches sender IP address | ||
* -x, --expire \<value> - expiration time with `s/m/h` suffix | ||
* -r, --reset - reset log level to default (do not provide <level>) | ||
* \<level> - log level to use for matching requests, supported values depend on your log library | ||
* `-l, --url <regex>` - matches request URL (without protocol, host, port) | ||
* `-h, --header <name>:<regex>` - matches given request header value | ||
* `-i, --ip <regex>` - matches sender IP address | ||
* `-x, --expire <value>` - expiration time with `s/m/h` suffix | ||
* `-r, --reset` - reset log level to default (do not provide level) | ||
* `--help` - print usage | ||
* `<level>` - log level to use for matching requests, supported values depend on your log library | ||
@@ -114,3 +182,3 @@ #### Invoke via CF task | ||
## Test | ||
## Contributing | ||
Install all dependencies: | ||
@@ -124,3 +192,2 @@ ```sh | ||
``` | ||
Integration tests require Redis to run on localhost on default port 6379. | ||
@@ -136,8 +203,17 @@ Install [docker], unless you have it already. | ||
``` | ||
Generate test coverage report: | ||
```sh | ||
npm run coverage | ||
``` | ||
After editing README.md update its table of contents: | ||
```sh | ||
npm run toc | ||
``` | ||
## Future | ||
### CF CLI plugin to change log level | ||
Redis uses TCP not HTTP, so it requires a tunnel (cf ssh) to connect it from outside CF. This is an additional obstacle for a CF CLI plugin. | ||
Ideas for [new features](https://github.com/niveau/niveau/labels/enhancement) are tracked in GitHub issues. You are encouraged to comment, add new ideas and contribute in any way. | ||
[ssh]: https://docs.cloudfoundry.org/devguide/deploy-apps/ssh-apps.html | ||
[docker]: https://www.docker.com/community-edition |
@@ -14,72 +14,111 @@ #!/usr/bin/env node | ||
const allOptions = { | ||
url: 'l', | ||
header: 'h', | ||
ip: 'i', | ||
expire: 'x', | ||
reset: 'r', | ||
help: undefined, | ||
_: undefined | ||
}; | ||
const cmdOptions = cmdParse(process.argv.slice(2), { | ||
alias: { | ||
l: 'url', | ||
h: 'header', | ||
i: 'ip', | ||
x: 'expire', | ||
r: 'reset' | ||
} | ||
alias: _.omit(_.invert(allOptions), undefined) | ||
}); | ||
debug('Command line options:', cmdOptions); | ||
if (cmdOptions.reset) { | ||
assert( | ||
!cmdOptions._.length && | ||
!['url', 'header', 'ip', 'expire'].some(opt => opt in cmdOptions), | ||
'No other options allowed with reset' | ||
); | ||
} else { | ||
assert(cmdOptions._.length === 1, 'Provide exactly one log level'); | ||
var level = cmdOptions._[0]; | ||
try { | ||
execute(cmdOptions); | ||
} catch (err) { | ||
debug(err); | ||
console.error(err.message); | ||
process.exit(1); | ||
} | ||
let headers = _.reduce(cmdOptions.header, (result, h) => { | ||
let i = h.indexOf(':'); | ||
assert(i > 0, 'headers'); | ||
result[h.slice(0, i).trim()] = h.slice(i + 1).trim(); | ||
}, {}); | ||
function execute(cmdOptions) { | ||
function noOptions(options) { | ||
return Object.keys(options).length === 1 && options._.length === 0; | ||
} | ||
let expire; | ||
if (cmdOptions.expire) { | ||
if (!cmdOptions.expire.endsWith('r')) { | ||
expire = timeParse(cmdOptions.expire, 's'); | ||
let validOptions = new Set(_.flatten(_.entries(allOptions))); | ||
for (let opt in cmdOptions) { | ||
assert(validOptions.has(opt), | ||
`Invalid option ${opt}. Run 'set-log-level --help' to see usage.`); | ||
} | ||
} | ||
let logConfig = { | ||
request: { | ||
url: cmdOptions.url, | ||
ip: cmdOptions.ip, | ||
headers | ||
}, | ||
// requestCounterKey: 'counter-name', | ||
level | ||
}; | ||
if (cmdOptions.help || noOptions(cmdOptions)) { | ||
console.log(`Usage: set-log-level [options...] <level> | ||
Options: | ||
-l, --url <regex> - matches request URL (without protocol, host, port) | ||
-h, --header <name>:<regex> - matches given request header value | ||
-i, --ip <regex> - matches sender IP address | ||
-x, --expire <value> - expiration time with s/m/h suffix | ||
-r, --reset - reset log level to default (do not provide level) | ||
--help - print usage | ||
<level> - log level to use for matching requests, supported values depend on your log library | ||
`); | ||
process.exit(1); | ||
} | ||
const client = redis.createClient(readRedisOptions()); | ||
if (cmdOptions.reset) { | ||
assert( | ||
!cmdOptions._.length && | ||
!['url', 'header', 'ip', 'expire'].some(opt => opt in cmdOptions), | ||
'No other options allowed with reset' | ||
); | ||
} else { | ||
assert(cmdOptions._.length === 1, | ||
"Provide exactly one log level. Run 'set-log-level --help' to see usage."); | ||
var level = cmdOptions._[0]; | ||
} | ||
client.on("error", function (err) { | ||
console.error(err); | ||
}); | ||
let headers = _.reduce(cmdOptions.header, (result, h) => { | ||
let i = h.indexOf(':'); | ||
assert(i > 0, `Invalid header ${h}. Run 'set-log-level --help' to see usage.`); | ||
result[h.slice(0, i).trim()] = h.slice(i + 1).trim(); | ||
}, {}); | ||
client.config('set', 'notify-keyspace-events', 'KA', (err, reply) => { | ||
err ? console.error('notify-keyspace-events', err) : | ||
console.log('notify-keyspace-events', reply); | ||
let expire; | ||
if (cmdOptions.expire) { | ||
if (!cmdOptions.expire.endsWith('r')) { | ||
expire = timeParse(cmdOptions.expire, 's'); | ||
} | ||
} | ||
if (cmdOptions.reset) { | ||
debug('redis DEL %s', LOG_CONFIG_KEY); | ||
client.del(LOG_CONFIG_KEY, (err, reply) => { | ||
err ? console.error('redis DEL:', err) : debug('redis:', reply); | ||
client.quit(); | ||
}); | ||
} else { | ||
debug('redis SET %s', LOG_CONFIG_KEY, logConfig); | ||
let params = [LOG_CONFIG_KEY, JSON.stringify(logConfig)]; | ||
expire && params.push('EX', expire); | ||
client.set(params, (err, reply) => { | ||
err ? console.error('redis SET:', err) : debug('redis:', reply); | ||
client.quit(); | ||
}); | ||
} | ||
}); | ||
let logConfig = { | ||
request: { | ||
url: cmdOptions.url, | ||
ip: cmdOptions.ip, | ||
headers | ||
}, | ||
// requestCounterKey: 'counter-name', | ||
level | ||
}; | ||
const client = redis.createClient(readRedisOptions()); | ||
client.on("error", function (err) { | ||
console.error(err); | ||
}); | ||
client.config('set', 'notify-keyspace-events', 'KA', (err, reply) => { | ||
err ? console.error('notify-keyspace-events', err) : | ||
console.log('notify-keyspace-events', reply); | ||
if (cmdOptions.reset) { | ||
debug('redis DEL %s', LOG_CONFIG_KEY); | ||
client.del(LOG_CONFIG_KEY, (err, reply) => { | ||
err ? console.error('redis DEL:', err) : debug('redis:', reply); | ||
client.quit(); | ||
}); | ||
} else { | ||
debug('redis SET %s', LOG_CONFIG_KEY, logConfig); | ||
let params = [LOG_CONFIG_KEY, JSON.stringify(logConfig)]; | ||
expire && params.push('EX', expire); | ||
client.set(params, (err, reply) => { | ||
err ? console.error('redis SET:', err) : debug('redis:', reply); | ||
client.quit(); | ||
}); | ||
} | ||
}); | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
17777
252
213
0
9