From 3376934c46f69badc047bc70a58bfe5a1f0567eb Mon Sep 17 00:00:00 2001 From: Niall Burkley Date: Thu, 24 Aug 2017 17:02:46 +0200 Subject: [PATCH] [example] adds example for collecting response metrics --- examples/response-metrics/README.md | 46 +++++++++++++ .../apicast_response_metrics.lua | 66 +++++++++++++++++++ .../response-metrics/response_metrics.conf | 24 +++++++ 3 files changed, 136 insertions(+) create mode 100644 examples/response-metrics/README.md create mode 100644 examples/response-metrics/apicast_response_metrics.lua create mode 100644 examples/response-metrics/response_metrics.conf diff --git a/examples/response-metrics/README.md b/examples/response-metrics/README.md new file mode 100644 index 000000000..f6f58ff69 --- /dev/null +++ b/examples/response-metrics/README.md @@ -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 +``` diff --git a/examples/response-metrics/apicast_response_metrics.lua b/examples/response-metrics/apicast_response_metrics.lua new file mode 100644 index 000000000..8ae41b3e4 --- /dev/null +++ b/examples/response-metrics/apicast_response_metrics.lua @@ -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 + 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 diff --git a/examples/response-metrics/response_metrics.conf b/examples/response-metrics/response_metrics.conf new file mode 100644 index 000000000..d811690c8 --- /dev/null +++ b/examples/response-metrics/response_metrics.conf @@ -0,0 +1,24 @@ +location = /report_metric { + internal; + resolver 8.8.8.8; + + 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; + 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; +}