Browse Source

convert this module to the new policy framework

apicast-new-policy-framework
Nicolas Massé 8 years ago
parent
commit
6bac383817
  1. 79
      README.md
  2. 38
      config.json
  3. 11
      resolver.conf
  4. 108
      verbose.lua

79
README.md

@ -18,6 +18,9 @@ This projects requires :
- a syslog server (such as [rsyslog](https://github.com/rsyslog/rsyslog)) - a syslog server (such as [rsyslog](https://github.com/rsyslog/rsyslog))
- the [lua-resty-logger-socket](https://github.com/cloudflare/lua-resty-logger-socket) module - the [lua-resty-logger-socket](https://github.com/cloudflare/lua-resty-logger-socket) module
This project has been tested with Apicast v3.2. It may work with newer or older version
but it may require some minor changes.
## Installation ## Installation
If not already done, start your syslog server and configure it to listen If not already done, start your syslog server and configure it to listen
@ -81,7 +84,7 @@ Once, you get it to work on `apicast-staging`, you can do the same on `apicast-p
The following section tries to evaluate the overhead of this module on apicast The following section tries to evaluate the overhead of this module on apicast
performances. performances.
Performance tests have been run on a vanilla apicast and an apicast with this Performance tests have been run on a vanilla apicast 3.0 and an apicast with this
module. Both tests have been run with 1k requests and responses and 10k requests module. Both tests have been run with 1k requests and responses and 10k requests
and responses. and responses.
@ -158,6 +161,40 @@ The requests and responses are serialized as follow:
} }
``` ```
## Configuration
The following excerpt shows a sample configuration for this module, when used
as a service policy.
```javascript
{
"services":[
{
"id":42,
"proxy":{
"policy_chain":[
{
"name":"custom.logger.verbose", // the verbose policy that lays in the ./gateway/custom/logger/ directory
"configuration": {
"syslog_host": "syslog.acme.test", // the hostname of the syslog server
"syslog_port": 1601, // the port of the syslog server
"syslog_protocol": "tcp", // the protocol to use to connect to the syslog server (tcp or udp)
"syslog_flush_limit": "0", // the minimum number of bytes in the buffer before sending logs to the syslog server
"syslog_drop_limit": "1048576", // the maximum number of bytes in the buffer before starting to drop messages
"syslog_periodic_flush": "5", // the number of seconds between each log flush (0 to disable)
"payload_encoding": "base64" // the algorithm used to encode the payload ('base64' or 'none')
}
},
{
"name":"apicast.policy.apicast" // also keep the default apicast behavior
}
]
}
}
]
}
```
## Development ## Development
First of all, setup your development environment as explained [here](https://github.com/3scale/apicast/tree/master#development--testing). First of all, setup your development environment as explained [here](https://github.com/3scale/apicast/tree/master#development--testing).
@ -166,41 +203,26 @@ Then, issue the following commands:
``` ```
git clone https://github.com/nmasse-itix/apicast-logger.git git clone https://github.com/nmasse-itix/apicast-logger.git
git clone https://github.com/3scale/apicast.git git clone https://github.com/3scale/apicast.git
git clone https://github.com/cloudflare/lua-resty-logger-socket.git
export GIT_ROOT=$PWD
cd apicast cd apicast
luarocks make apicast/*.rockspec --local luarocks make apicast/*.rockspec --local
ln -s ../apicast-logger custom mkdir gateway/src/custom
ln -s $GIT_ROOT/apicast-logger/ gateway/src/custom/logger/
cd gateway/src/resty
ln -s $GIT_ROOT/lua-resty-logger-socket/lib/resty/logger/ logger
cd -
``` ```
Configure your apicast as explained [here](https://github.com/3scale/apicast/blob/master/doc/parameters.md). Configure your apicast with a local configuration:
``` ```
export THREESCALE_DEPLOYMENT_ENV=sandbox export THREESCALE_CONFIG_FILE=$GIT_ROOT/apicast-logger/config.json
export THREESCALE_PORTAL_ENDPOINT=https://<YOUR-TOKEN-HERE>@<YOUR-TENANT-HERE>-admin.3scale.net
export APICAST_LOG_LEVEL=debug export APICAST_LOG_LEVEL=debug
``` ```
Configure the module:
```
export SYSLOG_HOST=127.0.0.1.xip.io
export SYSLOG_PORT=1601
export SYSLOG_PROTOCOL=tcp
export APICAST_MODULE=custom/verbose
```
Plain text logging of payload without base64 encoding:
```
export APICAST_PAYLOAD_BASE64=false
```
Then, you need to register a resolver in the nginx configuration (example using the Google DNS):
```
cat <<EOF > apicast/apicast.d/resolver.conf
resolver 8.8.8.8
EOF
```
Finally, launch apicast: Finally, launch apicast:
``` ```
bin/apicast -i 0 -m off bin/apicast --dev
``` ```
And in another terminal, launch netcat so that you can simulate a syslog server: And in another terminal, launch netcat so that you can simulate a syslog server:
@ -208,6 +230,11 @@ And in another terminal, launch netcat so that you can simulate a syslog server:
nc -l 1601 nc -l 1601
``` ```
## References
The following reading is recommended if you plan to develop on this module:
- [How to develop policies for Apicast](https://github.com/3scale/apicast/blob/master/doc/policies.md)
## Troubleshooting ## Troubleshooting
When troubleshooting, keep in mind that the underlying `lua-resty-logger-socket` When troubleshooting, keep in mind that the underlying `lua-resty-logger-socket`

38
config.json

@ -18,14 +18,52 @@
}, },
"auth_user_key": "user_key", "auth_user_key": "user_key",
"credentials_location": "query", "credentials_location": "query",
"policy_chain": [
{
"name": "apicast.policy.apicast"
},
{
"name": "apicast.policy.phase_logger"
},
{
"name": "custom.logger.verbose",
"configuration": {
"syslog_host": "127.0.0.1.xip.io",
"syslog_port": "1601",
"syslog_protocol": "tcp",
"payload_encoding": "base64"
}
}
],
"proxy_rules": [ "proxy_rules": [
{ {
"http_method": "GET",
"pattern": "/",
"metric_system_name": "hits",
"delta": 1,
"parameters": [],
"querystring_parameters": {}
},{
"http_method": "POST", "http_method": "POST",
"pattern": "/", "pattern": "/",
"metric_system_name": "hits", "metric_system_name": "hits",
"delta": 1, "delta": 1,
"parameters": [], "parameters": [],
"querystring_parameters": {} "querystring_parameters": {}
},{
"http_method": "PUT",
"pattern": "/",
"metric_system_name": "hits",
"delta": 1,
"parameters": [],
"querystring_parameters": {}
},{
"http_method": "DELETE",
"pattern": "/",
"metric_system_name": "hits",
"delta": 1,
"parameters": [],
"querystring_parameters": {}
} }
] ]
} }

11
resolver.conf

@ -1,11 +0,0 @@
# Nginx Configuration - To be placed in the 'apicast.d' folder.
#
# We need to add a resolver stanza since the underlying 'lua-resty-logger-socket'
# module use the nginx resolver and not ours.
#
# See https://github.com/openresty/lua-nginx-module#tcpsockconnect
#
# On Apicast Docker images, a built-in dnsmasq resolver is embedded
# and available on localhost port 5353
#
resolver 127.0.0.1:5353;

108
verbose.lua

@ -1,73 +1,68 @@
local apicast = require('apicast').new() local policy = require('apicast.policy')
local _M = policy.new('Verbose Logger Policy')
local cjson = require('cjson') local cjson = require('cjson')
local logger = require("resty.logger.socket") local logger = require("resty.logger.socket")
local _M = { _VERSION = '0.0' }
local mt = { __index = setmetatable(_M, { __index = apicast }) }
function _M.new()
return setmetatable({}, mt)
end
local host
local port
local proto
local flush_limit
local drop_limit
-- Parse and validate the parameters: -- Parse and validate the parameters:
-- SYSLOG_HOST => the hostname of the syslog server -- syslog_host => the hostname of the syslog server
-- SYSLOG_PORT => the port of the syslog server -- syslog_port => the port of the syslog server
-- SYSLOG_PROTOCOL => the protocol to use to connect to the syslog server (tcp or udp) -- syslog_protocol => the protocol to use to connect to the syslog server (tcp or udp)
-- SYSLOG_FLUSH_LIMIT => the minimum number of bytes in the buffer before sending logs to the syslog server -- syslog_flush_limit => the minimum number of bytes in the buffer before sending logs to the syslog server
-- SYSLOG_DROP_LIMIT => the maximum number of bytes in the buffer before starting to drop messages -- syslog_drop_limit => the maximum number of bytes in the buffer before starting to drop messages
-- SYSLOG_PERIODIC_FLUSH => the number of seconds between each log flush (0 to disable) -- syslog_periodic_flush => the number of seconds between each log flush (0 to disable)
-- payload_encoding => the algorithm used to encode the payload ('base64' or 'none')
-- --
function _M:init() local new = _M.new
host = os.getenv('SYSLOG_HOST') function _M.new(config)
port = os.getenv('SYSLOG_PORT') local self = new()
proto = os.getenv('SYSLOG_PROTOCOL') or 'tcp'
base64_flag = os.getenv('APICAST_PAYLOAD_BASE64') or 'true' -- Optional parameters
flush_limit = os.getenv('SYSLOG_FLUSH_LIMIT') or '0' self.proto = config.syslog_protocol or 'tcp'
periodic_flush = os.getenv('SYSLOG_PERIODIC_FLUSH') or '5' self.base64_flag = config.payload_encoding and (config.payload_encoding == 'base64')
drop_limit = os.getenv('SYSLOG_DROP_LIMIT') or '1048576' self.flush_limit = config.syslog_flush_limit or 0
self.periodic_flush = config.syslog_periodic_flush or 5
self.drop_limit = config.syslog_drop_limit or 1048576
if (host == nil or host == "") then -- Required parameters
ngx.log(ngx.ERR, "The environment SYSLOG_HOST is NOT defined !") if (config.syslog_host == nil or config.syslog_host == "") then
ngx.log(ngx.ERR, "The configuration option syslog_host is NOT defined !")
end end
if (port == nil or port == "") then if (config.syslog_port == nil or config.syslog_port == "") then
ngx.log(ngx.ERR, "The environment SYSLOG_PORT is NOT defined !") ngx.log(ngx.ERR, "The configuration option syslog_port is NOT defined !")
end end
port = tonumber(port) self.host = config.syslog_host
flush_limit = tonumber(flush_limit) self.port = tonumber(config.syslog_port)
drop_limit = tonumber(drop_limit)
periodic_flush = tonumber(periodic_flush)
ngx.log(ngx.WARN, "Sending custom logs to " .. proto .. "://" .. (host or "") .. ":" .. (port or "") .. " with flush_limit = " .. flush_limit .. " bytes, periodic_flush = " .. periodic_flush .. " sec. and drop_limit = " .. drop_limit .. " bytes") ngx.log(ngx.WARN, "Sending custom logs to " .. self.proto .. "://" .. (self.host or "") .. ":" .. (self.port or "") .. " with flush_limit = " .. self.flush_limit .. " bytes, periodic_flush = " .. self.periodic_flush .. " sec. and drop_limit = " .. self.drop_limit .. " bytes")
return apicast:init() return self
end end
-- Initialize the underlying logging module. Since the module calls 'timer_at' -- Initialize the underlying logging module. Since the module calls 'timer_at'
-- during initialization, we need to call it from a init_worker_by_lua block. -- during initialization, we need to call it from a init_worker_by_lua block.
-- --
function _M:init_worker() function _M:init_worker()
ngx.log(ngx.INFO, "Initializing the underlying logger") ensure_logger_is_initted(self)
end
local function ensure_logger_is_initted(self)
if not logger.initted() then if not logger.initted() then
ngx.log(ngx.INFO, "Initializing the underlying logger")
-- default parameters -- default parameters
local params = { local params = {
host = host, host = self.host,
port = port, port = self.port,
sock_type = proto, sock_type = self.proto,
flush_limit = flush_limit, flush_limit = self.flush_limit,
drop_limit = drop_limit drop_limit = self.drop_limit
} }
-- periodic_flush == 0 means 'disable this feature' -- periodic_flush == 0 means 'disable this feature'
if periodic_flush > 0 then if self.periodic_flush > 0 then
params["periodic_flush"] = periodic_flush params["periodic_flush"] = self.periodic_flush
end end
-- initialize the logger -- initialize the logger
@ -76,12 +71,9 @@ function _M:init_worker()
ngx.log(ngx.ERR, "failed to initialize the logger: ", err) ngx.log(ngx.ERR, "failed to initialize the logger: ", err)
end end
end end
return apicast:init_worker()
end end
local function do_log(payload)
function do_log(payload)
-- construct the custom access log message in -- construct the custom access log message in
-- the Lua variable "msg" -- the Lua variable "msg"
-- --
@ -95,8 +87,8 @@ end
-- This function is called for each chunk of response received from upstream server -- This function is called for each chunk of response received from upstream server
-- when the last chunk is received, ngx.arg[2] is true. -- when the last chunk is received, ngx.arg[2] is true.
function _M.body_filter() function _M:body_filter(context)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. ngx.arg[1] context.buffered = (context.buffered or "") .. ngx.arg[1]
if ngx.arg[2] then -- EOF if ngx.arg[2] then -- EOF
local dict = {} local dict = {}
@ -104,7 +96,7 @@ function _M.body_filter()
-- Gather information of the request -- Gather information of the request
local request = {} local request = {}
if ngx.var.request_body then if ngx.var.request_body then
if (base64_flag == 'true') then if (self.base64_flag) then
request["body"] = ngx.encode_base64(ngx.var.request_body) request["body"] = ngx.encode_base64(ngx.var.request_body)
else else
request["body"] = ngx.var.request_body request["body"] = ngx.var.request_body
@ -126,11 +118,11 @@ function _M.body_filter()
-- Gather information of the response -- Gather information of the response
local response = {} local response = {}
if ngx.ctx.buffered then if context.buffered then
if (base64_flag == 'true') then if (self.base64_flag) then
response["body"] = ngx.encode_base64(ngx.ctx.buffered) response["body"] = ngx.encode_base64(context.buffered)
else else
response["body"] = ngx.ctx.buffered response["body"] = context.buffered
end end
end end
response["headers"] = ngx.resp.get_headers() response["headers"] = ngx.resp.get_headers()
@ -149,9 +141,9 @@ function _M.body_filter()
upstream["status"] = ngx.var.upstream_status upstream["status"] = ngx.var.upstream_status
dict["upstream"] = upstream dict["upstream"] = upstream
ensure_logger_is_initted(self)
do_log(cjson.encode(dict)) do_log(cjson.encode(dict))
end end
return apicast:body_filter()
end end
return _M return _M

Loading…
Cancel
Save