-
Notifications
You must be signed in to change notification settings - Fork 15
Log http(s) requests for non-client library #51
Conversation
Fixes https://trello.com/c/uUo9ZCoU/193-cli-track-all-outgoing-requests-whatever-library-is-used This also adds a "user-agent: Zapier" header, which is used to differentiate from client requests and non-client ones. It also adds the ability to test logging locally in other apps, via the `process.env.LOGGING_TOKEN`
@@ -17,6 +17,7 @@ const sugarBody = (req) => { | |||
// move into the body as raw, set headers for coerce, merge to work | |||
|
|||
req.headers = req.headers || {}; | |||
req.headers['user-agent'] = 'Zapier'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user is able to override this right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I'll add that to the headers merging test just to make sure.
src/tools/create-lambda-handler.js
Outdated
// Adds logging for _all_ kinds of http(s) requests, no matter the library | ||
const httpPatch = createHttpPatch(event); | ||
httpPatch(require('http')); | ||
// httpPatch(require('https')); // NOTE: This was double-logging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, uses http
under the hood.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guess just nuke it then!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting take on this @BrunoBernardino, I like it!
As part of this effort, it might make sense to add some documentation on how devs can get logging working on their own. Basically show off what tooling we provide and how they might hook into it. My thought is that this is a great start, and could end up covering lots of use cases. I could see some though where it's not an http protocol, or doesn't use the node standard lib, or maybe a dev wants to wire in great logging to an SDK and overwrite our default one. I think it would only take a code snippet or two and a bit of explanation to show what logging call to make to do that.
request_url: requestUrl, | ||
request_method: options.method || 'GET', | ||
request_headers: requestHeaders.join('\n'), | ||
request_data: options.body || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might make sense to check if this was a file and do something like instead of the full body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to do it here because the createLogger
takes care of truncating and similar "cleaning" (like obfuscating sensitive data), so it'd effectively be almost duplicating work in a very quick sequential way.
If you think it's still a nice improvement, I'm happy to add it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, if it is covered elsewhere that is fine
src/tools/create-lambda-handler.js
Outdated
// Adds logging for _all_ kinds of http(s) requests, no matter the library | ||
const httpPatch = createHttpPatch(event); | ||
httpPatch(require('http')); | ||
// httpPatch(require('https')); // NOTE: This was double-logging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guess just nuke it then!
Thanks @bcooksey ! Can you please elaborate on that logging thought? As a dev, I can require a logging lib and call it before/after requests, or at specific parts of the app. We don't expose our logger (especially because it's specific to our needs and logging tools), and if we did, I can see how that'd be more confusing (to create a logger instance with some Or are you asking for me to add such snippets to the CLI README (with an external logging tool like papertrail, not using/overriding our own logging)? |
@BrunoBernardino why wouldn't we log these requests to Graylog? |
@FokkeZB to avoid circular/infinite loops 😄 |
@BrunoBernardino I was actually thinking about exposing our logger. So essentially, there would be three options:
Would that third option make sense? |
I see... How is that different from |
@BrunoBernardino Ah right. I was thinking we blocked those logs for public apps, but they get through, so that would be enough! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some things to consider with how we tap into the response.
request_url: requestUrl, | ||
request_method: options.method || 'GET', | ||
request_headers: requestHeaders.join('\n'), | ||
request_data: options.body || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, if it is covered elsewhere that is fine
src/tools/create-http-patch.js
Outdated
response.setEncoding('utf8'); | ||
response.on('data', (chunk) => responseBody.push(chunk)); | ||
response.on('end', logResponse); | ||
response.on('error', logResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can safely attach all of these. I wrote a gist to demonstrate some behaviors and found this:
setEncoding()
swaps the incoming values to the data listener from buffers to strings, which is probably not a nice thing to do to devssetEncoding()
will, obviously, encode the data, so if binary data being transferred, devs would have to decode it to get back to what they want- Adding a data handler swaps the stream mode from paused to flowing. This can cause
.read()
to return no data. As far as how much we should care, seems like an app will end up doing a.pipe()
or something and get it into flow mode anyway, but it is a nasty edge to force on folks. If we knew consuming data via.read()
was rare, I'd be fine ignoring this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, nice catches! I'll see if I can tap into that without changing data (will use your gist).
If I can't, I'll remove that and we'll log without the response content (still better than nothing). If it becomes something we really want, we can then try to look into ways of tapping into it more safely, maybe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may also want to look into how NewRelic wraps http.request
and friends. Pretty sure they've made sure not to break it: https://github.com/newrelic/node-newrelic/blob/master/lib/instrumentation/core/http.js#L579-L606
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @FokkeZB. After looking through it and trying it out for a while, it seems their wrapper libraries/methods are very complex and generic (and the request/response parts use the agent and eventemitters, which, while probably better, seem like a major overkill for what we're going for here).
I was able to confirm no app in CLI is currently calling anything .read()
, so I think we're safe on @bcooksey 's third concern. I ditched the setEncoding
and that seems to still be working fine (worst case scenario we might have some encoding issues on GL, and we can try to tweak that if so, later).
Let me know if that works fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I think this is looking good now. I'm still a bit unsure on the use of read()
. It's not in app code, but what about sdk's? However, Node docs push folks to use .pipe
. If we do run into an issue, we may be able to have an app declare it wants to disable the http patch some way.
logger(`${logData.response_status_code} ${logData.request_method} ${logData.request_url}`, logData); | ||
}; | ||
|
||
response.on('data', (chunk) => responseBody.push(chunk.toString())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, this way we'll still get utf8 strings being logged, but it won't mess with any other listeners on the 'data'
event.
This provides more logging even if
z.request
isn't used (like when SDKs are used), by patching thehttp
library in node.This was tested with Dropbox and the interpreter "full test" app (with different non-client http/request libraries).
Relevant Trello Card.
What this changes
z.request
.user-agent: Zapier
http header to differentiate from client requests and non-client ones.process.env.LOGGING_TOKEN
.request_via_client: Boolean
data property for GL, to differentiate from the different logs.user-agent
headerLog
Log http(s) requests for non-client library (f9111da)
Fixes https://trello.com/c/uUo9ZCoU/193-cli-track-all-outgoing-requests-whatever-library-is-used
This also adds a "user-agent: Zapier" header, which is used to differentiate from client requests and non-client ones.
It also adds the ability to test logging locally in other apps, via the
process.env.LOGGING_TOKEN
Removing memory leak and adding more tests (c84f6d9)
Don't set encoding. (f846d6e)