Skip to content

Commit

Permalink
Merge pull request #19 from pedrobranco/feature/add-request-logging
Browse files Browse the repository at this point in the history
Add logging to all requests
  • Loading branch information
ruimarinho authored Feb 9, 2017
2 parents a19a615 + 764c3bc commit f2eca4c
Show file tree
Hide file tree
Showing 7 changed files with 454 additions and 114 deletions.
59 changes: 50 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ npm install bitcoin-core --save
1. `[agentOptions]` _(Object)_: Optional `agent` [options](https://github.com/request/request#using-optionsagentoptions) to configure SSL/TLS.
2. `[headers=false]` _(boolean)_: Whether to return the response headers.
3. `[host=localhost]` _(string)_: The host to connect to.
4. `[network=mainnet]` _(string)_: The network
5. `[password]` _(string)_: The RPC server user password.
6. `[port=[network]]` _(string)_: The RPC server port.
7. `[ssl]` _(boolean|Object)_: Whether to use SSL/TLS with strict checking (_boolean_) or an expanded config (_Object_).
8. `[ssl.enabled]` _(boolean)_: Whether to use SSL/TLS.
9. `[ssl.strict]` _(boolean)_: Whether to do strict SSL/TLS checking (certificate must match host).
10. `[timeout=30000]` _(number)_: How long until the request times out (ms).
11. `[username]` _(number)_: The RPC server user name.
12. `[version]` _(string)_: Which version to check methods for ([read more](#versionchecking)).
4. `[logger=debugnyan('bitcoin-core')]` _(Function)_: Custom logger (by default, `debugnyan`).
5. `[network=mainnet]` _(string)_: The network
6. `[password]` _(string)_: The RPC server user password.
7. `[port=[network]]` _(string)_: The RPC server port.
8. `[ssl]` _(boolean|Object)_: Whether to use SSL/TLS with strict checking (_boolean_) or an expanded config (_Object_).
9. `[ssl.enabled]` _(boolean)_: Whether to use SSL/TLS.
10. `[ssl.strict]` _(boolean)_: Whether to do strict SSL/TLS checking (certificate must match host).
11. `[timeout=30000]` _(number)_: How long until the request times out (ms).
12. `[username]` _(number)_: The RPC server user name.
13. `[version]` _(string)_: Which version to check methods for ([read more](#versionchecking)).

### Examples
#### Using network mode
Expand Down Expand Up @@ -348,6 +349,46 @@ const client = new Client({
});
```

## Logging

By default, all requests made with `bitcoin-core` are logged using [seegno/debugnyan](https://github.com/seegno/debugnyan) with `bitcoin-core` as the logging namespace.

Please note that all sensitive data is obfuscated before calling the logger.

#### Example

Example output defining the environment variable `DEBUG=bitcoin-core`:

```javascript
const client = new Client();

client.getTransactionByHash('b4dd08f32be15d96b7166fd77afd18aece7480f72af6c9c7f9c5cbeb01e686fe');

// {
// "name": "bitcoin-core",
// "hostname": "localhost",
// "pid": 57908,
// "level": 20,
// "request": {
// "headers": {
// "host": "localhost:8332",
// "accept": "application/json"
// },
// "id": "82cea4e5-2c85-4284-b9ec-e5876c84e67c",
// "method": "GET",
// "type": "request",
// "uri": "http://localhost:8332/rest/tx/b4dd08f32be15d96b7166fd77afd18aece7480f72af6c9c7f9c5cbeb01e686fe.json"
// },
// "msg": "Making request 82cea4e5-2c85-4284-b9ec-e5876c84e67c to GET http://localhost:8332/rest/tx/b4dd08f32be15d96b7166fd77afd18aece7480f72af6c9c7f9c5cbeb01e686fe.json",
// "time": "2017-02-07T14:40:35.020Z",
// "v": 0
// }
```

### Custom logger

A custom logger can be passed via the `logger` option and it should implement [bunyan's log levels](https://github.com/trentm/node-bunyan#levels).

## Tests
Currently the test suite is tailored for Docker (including `docker-compose`) due to the multitude of different `bitcoind` configurations that are required in order to get the test suite passing.

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"version": "npm run changelog --future-release=$npm_package_version && npm run transpile && git add -A CHANGELOG.md dist"
},
"dependencies": {
"@uphold/request-logger": "^1.2.0",
"bluebird": "^3.4.1",
"debugnyan": "^1.0.0",
"lodash": "^4.0.0",
"request": "^2.53.0",
"semver": "^5.1.0",
Expand Down
8 changes: 6 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import Parser from './parser';
import Promise from 'bluebird';
import Requester from './requester';
import _ from 'lodash';
import debugnyan from 'debugnyan';
import methods from './methods';
import request from 'request';
import requestLogger from './logging/request-logger';
import semver from 'semver';

/**
Expand Down Expand Up @@ -47,6 +48,7 @@ class Client {
agentOptions,
headers = false,
host = 'localhost',
logger = debugnyan('bitcoin-core'),
network = 'mainnet',
password,
port,
Expand Down Expand Up @@ -76,12 +78,14 @@ class Client {

if (version) {
unsupported = _.chain(methods)
.pickBy(range => !semver.satisfies(version, range))
.pickBy(method => !semver.satisfies(version, method.version))
.keys()
.invokeMap(String.prototype.toLowerCase)
.value();
}

const request = requestLogger(logger);

this.request = Promise.promisifyAll(request.defaults({
agentOptions: this.agentOptions,
baseUrl: `${this.ssl.enabled ? 'https' : 'http'}://${this.host}:${this.port}`,
Expand Down
22 changes: 22 additions & 0 deletions src/logging/request-logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

/**
* Module dependencies.
*/

import { obfuscate } from './request-obfuscator';
import request from 'request';
import requestLogger from '@uphold/request-logger';

/**
* Exports.
*/

export default logger => requestLogger(request, (request, instance) => {
obfuscate(request, instance);

if (request.type === 'response') {
return logger.debug({ request }, `Received response for request ${request.id}`);
}

return logger.debug({ request }, `Making request ${request.id} to ${request.method} ${request.uri}`);
});
117 changes: 117 additions & 0 deletions src/logging/request-obfuscator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@

/**
* Module dependencies.
*/

import methods from '../methods';
import { defaults, get, has, isArray, isEmpty, isString, map, mapKeys } from 'lodash';

/**
* Map all methods to lowercase.
*/

const lowercaseMethods = mapKeys(methods, (value, key) => key.toLowerCase());

/**
* Obfuscate the response body.
*/

function obfuscateResponseBody(body, method) {
const fn = get(lowercaseMethods[method], 'obfuscate.response');

if (!fn || isEmpty(body.result)) {
return body;
}

return defaults({ result: fn(body.result) }, body);
}

/**
* Obfuscate the response.
*/

function obfuscateResponse(request, instance) {
if (!get(request, 'response.body')) {
return;
}

if (get(request, `response.headers['content-type']`) === 'application/octet-stream') {
request.response.body = '******';

return;
}

if (!instance.body) {
return;
}

const requestBody = JSON.parse(instance.body);

if (isArray(request.response.body)) {
const methodsById = mapKeys(requestBody, method => method.id);

request.response.body = map(request.response.body, request => obfuscateResponseBody(request, methodsById[request.id].method));

return;
}

request.response.body = obfuscateResponseBody(request.response.body, requestBody.method);
}

/**
* Obfuscate the request body.
*/

function obfuscateRequestBody(body) {
const method = get(lowercaseMethods[body.method], 'obfuscate.request');

if (!method) {
return body;
}

body.params = method(body.params);

return body;
}

/**
* Obfuscate the request.
*/

function obfuscateRequest(request) {
if (!isString(request.body)) {
return;
}

request.body = JSON.parse(request.body);

if (isArray(request.body)) {
request.body = map(request.body, obfuscateRequestBody);
} else {
request.body = obfuscateRequestBody(request.body);
}

request.body = JSON.stringify(request.body);
}

/**
* Obfuscate headers.
*/

function obfuscateHeaders(request) {
if (!has(request, 'headers.authorization')) {
return;
}

request.headers.authorization = request.headers.authorization.replace(/(Basic )(.*)/, `$1******`);
}

/**
* Export `RequestObfuscator`.
*/

export function obfuscate(request, instance) {
obfuscateHeaders(request);
obfuscateRequest(request);
obfuscateResponse(request, instance);
}
Loading

0 comments on commit f2eca4c

Please sign in to comment.