diff --git a/docs/readme.md b/docs/readme.md index d0c5ebc8..e498dadc 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -76,6 +76,53 @@ const retryingDbProfile = withRetrying(dbProfile, { const client = createClient(retryingDbProfile, 'my-awesome-program') ``` +## Logging requests + +You can use `profile.logRequest` and `profile.logResponse` to process the raw [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), respectively. + +As an example, we can implement a custom logger: + +```js +const createClient = require('hafas-client') +const dbProfile = require('hafas-client/p/db') + +const logRequest = (ctx, fetchRequest) => { + // ctx looks just like with the other profile.* hooks: + const {profile, opt} = ctx + + console.debug(fetchRequest.headers, fetchRequest.body + '') +} + +// create a client with Deutsche Bahn profile that debug-logs +const client = createClient({ + ...dbProfile, + logRequest, + logResponse, +}, 'my-awesome-program') +``` + +```js +// logRequest output: +{ + accept: 'application/json', + 'accept-encoding': 'gzip, br, deflate', + 'content-type': 'application/json', + connection: 'keep-alive', + 'user-agent': 'hafas842c51-clie842c51nt debug C842c51LI' +} {"lang":"de","svcReqL":[{"cfg":{"polyEnc":"GPA"},"meth":"LocMatch",… +// logResponse output: +{ + 'content-encoding': 'gzip', + 'content-length': '1010', + 'content-type': 'application/json; charset=utf-8', + date: 'Thu, 06 Oct 2022 12:31:09 GMT', + server: 'Apache', + vary: 'User-Agent' +} {"ver":"1.45","lang":"deu","id":"sb42zgck4mxtxm4s","err":"OK","graph"… +``` + +The default `profile.logRequest` [`console.error`](https://nodejs.org/docs/latest-v10.x/api/console.html#console_console_error_data_args)s the request body, if you have set `$DEBUG` to `hafas-client`. Likewise, `profile.logResponse` `console.error`s the response body. + ## Writing a profile Check [the guide](writing-a-profile.md). diff --git a/lib/default-profile.js b/lib/default-profile.js index 09a6e62d..973545cd 100644 --- a/lib/default-profile.js +++ b/lib/default-profile.js @@ -47,6 +47,14 @@ const formatLocation = require('../format/location') const formatRectangle = require('../format/rectangle') const filters = require('../format/filters') +const DEBUG = /(^|,)hafas-client(,|$)/.test(process.env.DEBUG || '') +const logRequest = DEBUG + ? (_, req) => console.error(req.body + '') + : () => {} +const logResponse = DEBUG + ? (_, res, body) => console.error(body) + : () => {} + const id = (ctx, x) => x const defaultProfile = { @@ -56,6 +64,8 @@ const defaultProfile = { salt: null, addChecksum: false, addMicMac: false, + logRequest, + logResponse, formatStationBoardReq, formatLocationsReq, diff --git a/lib/request.js b/lib/request.js index bd976052..edc8203b 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,7 +1,6 @@ 'use strict' const DEV = process.env.NODE_ENV === 'dev' -const DEBUG = /(^|,)hafas-client(,|$)/.test(process.env.DEBUG || '') const ProxyAgent = require('https-proxy-agent') const {isIP} = require('net') @@ -13,7 +12,7 @@ const pick = require('lodash/pick') const captureStackTrace = DEV ? require('capture-stack-trace') : () => {} const {stringify} = require('qs') const Promise = require('pinkie-promise') -const {fetch} = require('fetch-ponyfill')({Promise}) +const {Request, fetch} = require('fetch-ponyfill')({Promise}) const {parse: parseContentType} = require('content-type') const {addErrorInfo} = require('./errors') @@ -99,7 +98,6 @@ const request = (ctx, userAgent, reqData) => { redirect: 'follow', query: {} }) - if (DEBUG) console.error(req.body) if (profile.addChecksum || profile.addMicMac) { if (!Buffer.isBuffer(profile.salt) && 'string' !== typeof profile.salt) { @@ -126,6 +124,8 @@ const request = (ctx, userAgent, reqData) => { } const url = profile.endpoint + '?' + stringify(req.query) + const fetchReq = new Request(url, req) + profile.logRequest(ctx, fetchReq) // Async stack traces are not supported everywhere yet, so we create our own. const err = new Error() @@ -134,7 +134,7 @@ const request = (ctx, userAgent, reqData) => { err.url = url captureStackTrace(err) - return fetch(url, req) + return fetch(fetchReq) .then((res) => { err.statusCode = res.status if (!res.ok) { @@ -151,29 +151,31 @@ const request = (ctx, userAgent, reqData) => { throw err } } - return res.json() - }) - .then((b) => { - if (DEBUG) console.error(JSON.stringify(b)) - if (b.err && b.err !== 'OK') { - addErrorInfo(err, b.err, b.errTxt, b.id) - throw err - } - if (!b.svcResL || !b.svcResL[0]) { - err.message = 'invalid response' - throw err - } - if (b.svcResL[0].err !== 'OK') { - addErrorInfo(err, b.svcResL[0].err, b.svcResL[0].errTxt, b.id) - throw err - } + return res.text() + .then((body) => { + profile.logResponse(ctx, res, body) + const b = JSON.parse(body) - const res = b.svcResL[0].res - return { - res, - common: profile.parseCommon({...ctx, res}) - } + if (b.err && b.err !== 'OK') { + addErrorInfo(err, b.err, b.errTxt, b.id) + throw err + } + if (!b.svcResL || !b.svcResL[0]) { + err.message = 'invalid response' + throw err + } + if (b.svcResL[0].err !== 'OK') { + addErrorInfo(err, b.svcResL[0].err, b.svcResL[0].errTxt, b.id) + throw err + } + + const svcRes = b.svcResL[0].res + return { + res: svcRes, + common: profile.parseCommon({...ctx, res: svcRes}), + } + }) }) }