Skip to content

Commit

Permalink
[plugin/datadog] Logging to statsd server
Browse files Browse the repository at this point in the history
Compiles metrics like Request count, size, Response status and latency and send it to
Datadog statsd server
  • Loading branch information
Shashi Ranjan committed Dec 23, 2015
1 parent c064af8 commit ee6f3e7
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 2 deletions.
7 changes: 6 additions & 1 deletion kong-0.5.4-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,12 @@ build = {

["kong.plugins.loggly.handler"] = "kong/plugins/loggly/handler.lua",
["kong.plugins.loggly.log"] = "kong/plugins/loggly/log.lua",
["kong.plugins.loggly.schema"] = "kong/plugins/loggly/schema.lua"
["kong.plugins.loggly.schema"] = "kong/plugins/loggly/schema.lua",

["kong.plugins.datadog.handler"] = "kong/plugins/datadog/handler.lua",
["kong.plugins.datadog.schema"] = "kong/plugins/datadog/schema.lua",
["kong.plugins.datadog.statsd_logger"] = "kong/plugins/datadog/statsd_logger.lua"

},
install = {
conf = { "kong.yml" },
Expand Down
77 changes: 77 additions & 0 deletions kong/plugins/datadog/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
local BasePlugin = require "kong.plugins.base_plugin"
local basic_serializer = require "kong.plugins.log-serializers.basic"
local statsd_logger = require "kong.plugins.datadog.statsd_logger"

local ngx_log = ngx.log
local ngx_timer_at = ngx.timer.at
local string_gsub = string.gsub
local pairs = pairs
local NGX_ERR = ngx.ERR

local function request_counter(api_name, logger)
local stat = api_name..".request.count"
logger:counter(stat, 1, 1)
end

local function status_counter(api_name, message, logger)
local stat = api_name..".request.status."..message.response.status
logger:counter(stat, 1, 1)
end

local function request_size_gauge(api_name, message, logger)
local stat = api_name..".request.size"
logger:gauge(stat, message.request.size, 1)
end

local function latency_gauge(api_name, message, logger)
local stat = api_name..".latency"
logger:gauge(stat, message.latencies.request, 1)
end

local function log(premature, conf, message)
if premature then return end

local logger, err = statsd_logger:new(conf)
if err then
ngx_log(NGX_ERR, "failed to create Statsd logger: ", err)
return
end

local api_name = string_gsub(message.api.name, "%.", "_")
for _, metric in pairs(conf.metrics) do
if metric == "request_size" then
request_size_gauge(api_name, message, logger)
end
if metric == "status_count" then
status_counter(api_name, message, logger)
end
if metric == "latency" then
latency_gauge(api_name, message, logger)
end
if metric == "request_count" then
request_counter(api_name, logger)
end
end

logger:close_socket()
end

local DatadogHandler = BasePlugin:extend()

function DatadogHandler:new()
DatadogHandler.super.new(self, "datadog")
end

function DatadogHandler:log(conf)
DatadogHandler.super.log(self)
local message = basic_serializer.serialize(ngx)

local ok, err = ngx_timer_at(0, log, conf, message)
if not ok then
ngx_log(NGX_ERR, "failed to create timer: ", err)
end
end

DatadogHandler.PRIORITY = 1

return DatadogHandler
8 changes: 8 additions & 0 deletions kong/plugins/datadog/schema.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
return {
fields = {
host = {required = true, type = "string", default = "localhost"},
port = {required = true, type = "number", default = 8125},
metrics = {required = true, type = "array", enum = {"request_count", "latency", "request_size", "status_count"}, default = {"request_count", "latency", "request_size", "status_count"}},
timeout = {type = "number", default = 10000}
}
}
85 changes: 85 additions & 0 deletions kong/plugins/datadog/statsd_logger.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
local setmetatable = setmetatable
local ngx_socket_udp = ngx.socket.udp
local ngx_log = ngx.log
local table_concat = table.concat
local setmetatable = setmetatable
local NGX_ERR = ngx.ERR

local statsd_mt = {}
statsd_mt.__index = statsd_mt

function statsd_mt:new(conf)
local sock = ngx_socket_udp()
sock:settimeout(conf.timeout)
local _, err = sock:setpeername(conf.host, conf.port)
if err then
return nil, "failed to connect to "..conf.host..":"..tostring(conf.port)..": "..err
end

local statsd = {
host = conf.host,
port = conf.port,
socket = sock,
}
return setmetatable(statsd, statsd_mt)
end

function statsd_mt:create_statsd_message(stat, delta, kind, sample_rate)
local rate = ""
if sample_rate and sample_rate ~= 1 then
rate = "|@"..sample_rate
end

local message = {
"kong.",
stat,
":",
delta,
"|",
kind,
rate
}
return table_concat(message, "")
end

function statsd_mt:close_socket()
local ok, err = self.socket:close()
if not ok then
ngx_log(NGX_ERR, "failed to close connection from "..self.host..":"..tostring(self.port)..": ", err)
return
end
end

function statsd_mt:send_statsd(stat, delta, kind, sample_rate)
local udp_message = self:create_statsd_message(stat, delta, kind, sample_rate)
local ok, err = self.socket:send(udp_message)
if not ok then
ngx_log(NGX_ERR, "failed to send data to "..self.host..":"..tostring(self.port)..": ", err)
end
end

function statsd_mt:gauge(stat, value, sample_rate)
return self:send_statsd(stat, value, "g", sample_rate)
end

function statsd_mt:counter(stat, value, sample_rate)
return self:send_statsd(stat, value, "c", sample_rate)
end

function statsd_mt:timer(stat, ms)
return self:send_statsd(stat, ms, "ms")
end

function statsd_mt:histogram(stat, value)
return self:send_statsd(stat, value, "h")
end

function statsd_mt:meter(stat, value)
return self:send_statsd(stat, value, "m")
end

function statsd_mt:set(stat, value)
return self:send_statsd(stat, value, "s")
end

return statsd_mt
2 changes: 1 addition & 1 deletion kong/tools/config_defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ return {
default = {"ssl", "jwt", "acl", "cors", "oauth2", "tcp-log", "udp-log", "file-log",
"http-log", "key-auth", "hmac-auth", "basic-auth", "ip-restriction",
"mashape-analytics", "request-transformer", "response-transformer",
"request-size-limiting", "rate-limiting", "response-ratelimiting", "syslog", "loggly"}
"request-size-limiting", "rate-limiting", "response-ratelimiting", "syslog", "loggly", "datadog"}
},
["nginx_working_dir"] = {type = "string", default = "/usr/local/kong"},
["proxy_port"] = {type = "number", default = 8000},
Expand Down
100 changes: 100 additions & 0 deletions spec/plugins/datadog/log_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
local spec_helper = require "spec.spec_helpers"
local http_client = require "kong.tools.http_client"

local STUB_GET_URL = spec_helper.STUB_GET_URL

local UDP_PORT = spec_helper.find_port()

describe("Datadog Plugin", function()

setup(function()
spec_helper.prepare_db()
spec_helper.insert_fixtures {
api = {
{request_host = "logging1.com", upstream_url = "http://mockbin.com"},
{request_host = "logging2.com", upstream_url = "http://mockbin.com"},
{request_host = "logging3.com", upstream_url = "http://mockbin.com"},
{request_host = "logging4.com", upstream_url = "http://mockbin.com"},
{request_host = "logging5.com", upstream_url = "http://mockbin.com"}
},
plugin = {
{name = "datadog", config = {host = "127.0.0.1", port = UDP_PORT, metrics = {"request_count"}}, __api = 1},
{name = "datadog", config = {host = "127.0.0.1", port = UDP_PORT, metrics = {"latency"}}, __api = 2},
{name = "datadog", config = {host = "127.0.0.1", port = UDP_PORT, metrics = {"status_count"}}, __api = 3},
{name = "datadog", config = {host = "127.0.0.1", port = UDP_PORT, metrics = {"request_size"}}, __api = 4},
{name = "datadog", config = {host = "127.0.0.1", port = UDP_PORT}, __api = 5}
}
}
spec_helper.start_kong()
end)

teardown(function()
spec_helper.stop_kong()
end)

it("should log to UDP when metrics is request_count", function()
local thread = spec_helper.start_udp_server(UDP_PORT) -- Starting the mock UDP server

local _, status = http_client.get(STUB_GET_URL, nil, {host = "logging1.com"})
assert.equal(200, status)

local ok, res = thread:join()
assert.True(ok)
assert.truthy(res)
assert.equal("kong.logging1_com.request.count:1|c", res)
end)

it("should log to UDP when metrics is status_count", function()
local thread = spec_helper.start_udp_server(UDP_PORT) -- Starting the mock UDP server

local _, status = http_client.get(STUB_GET_URL, nil, {host = "logging3.com"})
assert.equal(200, status)

local ok, res = thread:join()
assert.True(ok)
assert.truthy(res)
assert.equal("kong.logging3_com.request.status.200:1|c", res)
end)

it("should log to UDP when metrics is request_size", function()
local thread = spec_helper.start_udp_server(UDP_PORT) -- Starting the mock UDP server

local _, status = http_client.get(STUB_GET_URL, nil, {host = "logging4.com"})
assert.equal(200, status)

local ok, res = thread:join()
assert.True(ok)
assert.truthy(res)
assert.equal("kong.logging4_com.request.size:111|g", res)
end)

it("should log to UDP when metrics is latency", function()
local thread = spec_helper.start_udp_server(UDP_PORT) -- Starting the mock UDP server

local _, status = http_client.get(STUB_GET_URL, nil, {host = "logging2.com"})
assert.equal(200, status)

local ok, res = thread:join()
assert.True(ok)
assert.truthy(res)

local message = {}
for w in string.gmatch(res,"kong.logging2_com.latency:.*|g") do
table.insert(message, w)
end

assert.equal(1, #message)
end)

it("should log to UDP when metrics is request_count", function()
local thread = spec_helper.start_udp_server(UDP_PORT) -- Starting the mock UDP server

local _, status = http_client.get(STUB_GET_URL, nil, {host = "logging5.com"})
assert.equal(200, status)

local ok, res = thread:join()
assert.True(ok)
assert.truthy(res)
assert.equal("kong.logging5_com.request.count:1|c", res)
end)
end)

0 comments on commit ee6f3e7

Please sign in to comment.