-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[example] adds example for collecting response metrics #404
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Reporting metrics on responses | ||
|
||
APIcast supports metrics on requests out of the box. However, sometimes you need to capture metrics on the responses you're returning to the user. | ||
|
||
For example, let's say you want to record how many documents a user is retrieving through an API call - it's not possible to record this through the request because we don't know how many documents are available until the request has been processed by our application. | ||
|
||
To carry this out, you'll need to capture data from the response on it's way out of the 3scale gateway. | ||
|
||
This is possible by creating a custom module and overriding the `post_action` function of `apicast`. | ||
|
||
## How it works | ||
|
||
There are two code snippets that do the following: | ||
|
||
1. `apicast_response_metrics.lua` is a custom module (see [this example](https://github.com/3scale/apicast/tree/master/examples/custom-module) for more info) that overrides the default APIcast's post_action phase handler to include the following logic: | ||
* It checks if the request was for a specific path, i.e. the path we wish to collect metrics on - in this case `/v1/documents` | ||
* It extracts a custom header `x-document-count` from the response (which was added in the application code) | ||
* It calls a custom path `/report_metric`, passing the `document_count` and `user_key` to a record the metric | ||
2. `response_metrics.conf` is a configuration file that should be added to `apicast.d` directory to be included in the configuration. | ||
* It contains `/report_metric` that is used to make the `POST` request to report custom metrics to 3scale | ||
|
||
## How it works | ||
|
||
You'll need to set up a custom metric in your 3scale Admin Portal. In this example we have a custom metric `document_count` already set up. | ||
|
||
See the 3scale documentation on how to [Create new metric](https://support.3scale.net/docs/access-control/api-definition-methods-metrics). | ||
|
||
## Adding the customization to APIcast | ||
|
||
**Note:** the example commands are supposed to be run from the root of the local copy of the `apicast` repository. | ||
|
||
### Native APIcast | ||
|
||
Place `apicast_response_metrics.lua` to `apicast/src`, and `response_metrics.conf` to `apicast/apicast.d` and start APIcast: | ||
|
||
``` | ||
THREESCALE_PORTAL_ENDPOINT=https://ACCESS-TOKEN@ACCOUNT-admin.3scale.net APICAST_MODULE=apicast_response_metrics bin/apicast | ||
``` | ||
|
||
### Docker | ||
|
||
Attach the above files as volumes to the container and set `APICAST_MODULE` environment variable. | ||
|
||
``` | ||
docker run --name apicast --rm -p 8080:8080 -v $(pwd)/examples/response-metrics/apicast_response_metrics.lua:/opt/app-root/src/src/apicast_response_metrics.lua:ro -v $(pwd)/examples/response-metrics/response_metrics.conf:/opt/app-root/src/apicast.d/response_metrics.conf:ro -e THREESCALE_PORTAL_ENDPOINT=https://ACCESS-TOKEN@ACCOUNT-admin.3scale.net -e APICAST_MODULE=apicast_response_metrics quay.io/3scale/apicast:master | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
--- Custom APICAST_MODULE which overrides post_action from original | ||
--- https://github.com/3scale/apicast/blob/3.0-stable/apicast/src/apicast.lua | ||
|
||
-- load and initialize the parent module | ||
local apicast = require('apicast').new() | ||
|
||
local _M = { _VERSION = '3.0.0', _NAME = 'APIcast with response metrics' } | ||
local mt = { __index = setmetatable(_M, { __index = apicast }) } | ||
|
||
function _M.new() | ||
return setmetatable({}, mt) | ||
end | ||
|
||
local function send_count_metrics() | ||
-- get the document count from the custom response header | ||
local document_count = ngx.resp.get_headers()['x-document-count']; | ||
local user_key = ngx.req.get_headers()['user_key']; | ||
|
||
-- only report metrics if the response was a success and the custom header exists | ||
if ngx.status == ngx.HTTP_OK and document_count then | ||
ngx.log(ngx.INFO, '[3scale-metrics] sending metric to 3scale document-count: ', document_count) | ||
local report = ngx.location.capture("/report_metric", | ||
{ | ||
args = { | ||
user_key = user_key, | ||
metric_name = 'document_count', | ||
count = document_count | ||
}, | ||
copy_all_vars = true | ||
} | ||
); | ||
|
||
if report.status == ngx.HTTP_ACCEPTED then | ||
ngx.log(ngx.INFO, '[3scale-metrics] document_count metric succeeded ', report.status) | ||
else | ||
ngx.log(ngx.WARN, '[3scale-metrics] document_count metric update failed. status: ', report.status) | ||
end | ||
end | ||
end | ||
|
||
function _M:post_action() | ||
local request_id = ngx.var.original_request_id | ||
local post_action_proxy = self.post_action_proxy | ||
|
||
if not post_action_proxy then | ||
return nil, 'not initialized' | ||
end | ||
|
||
-- send custom metrics if this is a document search | ||
if ngx.var.request_uri:match '^/v1/documents' then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better if this regex is not hardcoded. One suggestion could be to define another environment variable that consists of the regex pattern(s) to match against. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, make sense, I'll tidy this up. |
||
send_count_metrics() | ||
end | ||
|
||
local p = ngx.ctx.proxy or post_action_proxy[request_id] | ||
|
||
post_action_proxy[request_id] = nil | ||
|
||
if p then | ||
return p:post_action() | ||
else | ||
ngx.log(ngx.INFO, 'could not find proxy for request id: ', request_id) | ||
return nil, 'no proxy for request' | ||
end | ||
end | ||
|
||
return _M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
location = /report_metric { | ||
internal; | ||
resolver 8.8.8.8; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, we would not have to define a resolver here, but unlike the other conf blocks, the default resolver doesn't appear to be picked up here. |
||
|
||
set $user_key $arg_user_key; | ||
set $metric_name $arg_metric_name; | ||
set $document_count $arg_count; | ||
set $user_credentials transactions[0][user_key]=$user_key; | ||
set $metrics transactions[0][usage][$metric_name]=$document_count; | ||
set $reporting_url https://su1.3scale.net:443; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We tried using |
||
set $path /transactions.xml?$backend_authentication_type=$backend_authentication_value&service_id=$service_id&$metrics&$user_credentials; | ||
|
||
proxy_pass_request_headers off; | ||
proxy_http_version 1.1; | ||
proxy_set_header Host "$backend_host"; | ||
proxy_set_header User-Agent "$user_agent"; | ||
proxy_set_header X-3scale-User-Agent "$deployment"; | ||
proxy_set_header X-3scale-Version "$version"; | ||
proxy_set_header Connection ""; | ||
proxy_set_header Content-Type "application/x-www-form-urlencoded"; | ||
proxy_method POST; | ||
|
||
proxy_pass $reporting_url$path; | ||
} |
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.
@nburkley as @mikz pointed out it would be better to do this in lua. You could make 2 requests here to 3scale, first an authorise and then a report. This is done in the post_action phase and doesn't impact the latency on the client request. If the authorize returns a 409 you can allow the call through but if it returns a 403 then the call should be rejected, then you can report based on the authorize response. The authorize already exists in the backend_client so you would just need to add the report call there.
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.
Sure, I can do that - I was trying to avoid modifying the backend_clint code - hence the call to an nginx conf.
I didn't have any auth-checks as I as going with the assumption that if the request made it thorugh our stack successfully it was already authorized - I can add this though.