local helpers = require "spec.helpers" local cjson = require "cjson" local splitn = require("kong.tools.string").splitn describe("Plugin: datadog (log)", function() local DEFAULT_METRICS_COUNT = 6 lazy_setup(function() helpers.setenv('KONG_DATADOG_AGENT_HOST', 'localhost') helpers.setenv('KONG_DATADOG_AGENT_HOST_TEST', 'localhost') helpers.setenv('KONG_DATADOG_AGENT_PORT', '9999') end) lazy_teardown(function() helpers.unsetenv('KONG_DATADOG_AGENT_HOST') helpers.unsetenv('KONG_DATADOG_AGENT_HOST_TEST') helpers.unsetenv('KONG_DATADOG_AGENT_PORT') end) for _, strategy in helpers.each_strategy() do describe("Plugin: datadog (log) [#" .. strategy .. "]", function() local proxy_client lazy_setup(function() local bp = helpers.get_db_utils(strategy, { "routes", "services", "plugins", "consumers", "keyauth_credentials", }) local consumer = bp.consumers:insert { username = "foo", custom_id = "bar" } bp.keyauth_credentials:insert({ key = "kong", consumer = { id = consumer.id }, }) local route1 = bp.routes:insert { hosts = { "datadog1.test" }, service = bp.services:insert { name = "dd1" } } local route2 = bp.routes:insert { hosts = { "datadog2.test" }, service = bp.services:insert { name = "dd2" } } local route3 = bp.routes:insert { hosts = { "datadog3.test" }, service = bp.services:insert { name = "dd3" } } local route4 = bp.routes:insert { hosts = { "datadog4.test" }, service = bp.services:insert { name = "dd4" } } local route5 = bp.routes:insert { hosts = { "datadog5.test" }, service = bp.services:insert { name = "dd5" } } local route_grpc = assert(bp.routes:insert { protocols = { "grpc" }, paths = { "/hello.HelloService/" }, service = assert(bp.services:insert { name = "grpc", url = helpers.grpcbin_url, }), }) local route6 = bp.routes:insert { hosts = { "datadog6.test" }, service = bp.services:insert { name = "dd6" } } local route7 = bp.routes:insert { hosts = { "datadog7.test" }, service = bp.services:insert { name = "dd7" } } local route8 = bp.routes:insert { hosts = { "datadog8.test" }, paths = { "/test_schema" }, service = bp.services:insert { name = "dd8", protocol = "http", url = helpers.mock_upstream_url, } } local route9 = bp.routes:insert { paths = { "/serviceless" }, no_service = true, } bp.plugins:insert { name = "key-auth", route = { id = route1.id }, } bp.plugins:insert { name = "datadog", route = { id = route1.id }, config = { host = "127.0.0.1", port = 9999, }, } bp.plugins:insert { name = "datadog", route = { id = route2.id }, config = { host = "127.0.0.1", port = 9999, metrics = { { name = "request_count", stat_type = "counter", sample_rate = 1, }, }, }, } bp.plugins:insert { name = "datadog", route = { id = route3.id }, config = { host = "127.0.0.1", port = 9999, metrics = { { name = "request_count", stat_type = "counter", sample_rate = 1, tags = {"T2:V2", "T3:V3", "T4"}, }, { name = "latency", stat_type = "gauge", sample_rate = 1, tags = {"T2:V2:V3", "T4"}, }, { name = "request_size", stat_type = "distribution", sample_rate = 1, tags = {}, }, }, }, } bp.plugins:insert { name = "key-auth", route = { id = route4.id }, } bp.plugins:insert { name = "datadog", route = { id = route4.id }, config = { host = "127.0.0.1", port = 9999, prefix = "prefix", }, } bp.plugins:insert { name = "key-auth", route = { id = route5.id }, } bp.plugins:insert { name = "datadog", route = { id = route5.id }, config = { host = ngx.null, -- plugin takes above env var value, if set to null port = ngx.null, -- plugin takes above env var value, if set to null }, } bp.plugins:insert { name = "key-auth", route = { id = route_grpc.id }, } bp.plugins:insert { name = "datadog", route = { id = route_grpc.id }, config = { host = "127.0.0.1", port = 9999, }, } bp.plugins:insert { name = "key-auth", route = { id = route6.id }, } bp.plugins:insert { name = "datadog", route = { id = route6.id }, config = { host = "127.0.0.1", port = 9999, service_name_tag = "upstream", status_tag = "http_status", consumer_tag = "user", }, } bp.plugins:insert { name = "key-auth", route = { id = route7.id }, } bp.plugins:insert { name = "datadog", route = { id = route7.id }, config = { host = "127.0.0.1", port = 9999, queue_size = 2, }, } bp.plugins:insert { name = "datadog", route = { id = route8.id }, config = { host = "{vault://env/kong-datadog-agent-host-test}", port = 9999, queue_size = 2, }, } bp.plugins:insert { name = "datadog", route = { id = route9.id }, config = { host = "127.0.0.1", port = 9999, queue_size = 2, }, } bp.plugins:insert { name = "request-termination", route = { id = route9.id }, config = { status_code = 200, message = "OK", } } assert(helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", vaults = "env", })) proxy_client = helpers.proxy_client() end) lazy_teardown(function() helpers.stop_kong() end) before_each(function() proxy_client = helpers.proxy_client() end) after_each(function() if proxy_client then proxy_client:close() end end) it("logs metrics over UDP", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local res = assert(proxy_client:send { method = "GET", path = "/status/200?apikey=kong", headers = { ["Host"] = "datadog1.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) assert.contains("kong.request.count:1|c|#name:dd1,status:200,consumer:bar,app:kong" , gauges) assert.contains("kong.latency:%d+|ms|#name:dd1,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.request.size:%d+|ms|#name:dd1,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.response.size:%d+|ms|#name:dd1,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.upstream_latency:%d+|ms|#name:dd1,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.kong_latency:%d*|ms|#name:dd1,status:200,consumer:bar,app:kong", gauges, true) end) it("logs metrics over UDP #grpc", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local grpc_cleint = assert(helpers.proxy_client_grpc()) local ok, res = grpc_cleint{ service = "hello.HelloService.SayHello", opts = { ["-H"] = "'apikey: kong'", }, } assert.truthy(ok, res) assert.same({ reply = "hello noname" }, cjson.decode(res)) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) assert.contains("kong.request.count:1|c|#name:grpc,status:200,consumer:bar,app:kong" , gauges) assert.contains("kong.latency:%d+|ms|#name:grpc,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.request.size:%d+|ms|#name:grpc,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.response.size:%d+|ms|#name:grpc,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.upstream_latency:%d+|ms|#name:grpc,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.kong_latency:%d*|ms|#name:grpc,status:200,consumer:bar,app:kong", gauges, true) end) it("logs metrics over UDP with custom prefix", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local res = assert(proxy_client:send { method = "GET", path = "/status/200?apikey=kong", headers = { ["Host"] = "datadog4.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) assert.contains("prefix.request.count:1|c|#name:dd4,status:200,consumer:bar,app:kong",gauges) assert.contains("prefix.latency:%d+|ms|#name:dd4,status:200,consumer:bar,app:kong", gauges, true) assert.contains("prefix.request.size:%d+|ms|#name:dd4,status:200,consumer:bar,app:kong", gauges, true) assert.contains("prefix.response.size:%d+|ms|#name:dd4,status:200,consumer:bar,app:kong", gauges, true) assert.contains("prefix.upstream_latency:%d+|ms|#name:dd4,status:200,consumer:bar,app:kong", gauges, true) assert.contains("prefix.kong_latency:%d*|ms|#name:dd4,status:200,consumer:bar,app:kong", gauges, true) end) it("logs metrics over UDP with custom tag names", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local res = assert(proxy_client:send { method = "GET", path = "/status/200?apikey=kong", headers = { ["Host"] = "datadog6.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) assert.contains("kong.request.count:1|c|#upstream:dd6,http_status:200,user:bar,app:kong",gauges) assert.contains("kong.latency:%d+|ms|#upstream:dd6,http_status:200,user:bar,app:kong", gauges, true) assert.contains("kong.request.size:%d+|ms|#upstream:dd6,http_status:200,user:bar,app:kong", gauges, true) assert.contains("kong.response.size:%d+|ms|#upstream:dd6,http_status:200,user:bar,app:kong", gauges, true) assert.contains("kong.upstream_latency:%d+|ms|#upstream:dd6,http_status:200,user:bar,app:kong", gauges, true) assert.contains("kong.kong_latency:%d*|ms|#upstream:dd6,http_status:200,user:bar,app:kong", gauges, true) end) it("logs only given metrics", function() local thread = helpers.udp_server(9999, 1) local res = assert(proxy_client:send { method = "GET", path = "/status/200", headers = { ["Host"] = "datadog2.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) gauges = { gauges } -- as thread:join() returns a string in case of 1 assert.equal(1, #gauges) assert.contains("kong.request.count:1|c|#name:dd2,status:200", gauges) end) it("logs metrics with tags", function() local thread = helpers.udp_server(9999, 3) local res = assert(proxy_client:send { method = "GET", path = "/status/200", headers = { ["Host"] = "datadog3.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.contains("kong.request.count:1|c|#name:dd3,status:200,T2:V2,T3:V3,T4", gauges) assert.contains("kong.latency:%d+|g|#name:dd3,status:200,T2:V2:V3,T4", gauges, true) assert.contains("kong.request.size:%d+|d|#name:dd3,status:200", gauges, true) end) it("logs metrics to host/port defined via environment variables", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local res = assert(proxy_client:send { method = "GET", path = "/status/200?apikey=kong", headers = { ["Host"] = "datadog5.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) assert.contains("kong.request.count:1|c|#name:dd5,status:200,consumer:bar,app:kong" , gauges) assert.contains("kong.latency:%d+|ms|#name:dd5,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.request.size:%d+|ms|#name:dd5,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.response.size:%d+|ms|#name:dd5,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.upstream_latency:%d+|ms|#name:dd5,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.kong_latency:%d*|ms|#name:dd5,status:200,consumer:bar,app:kong", gauges, true) end) it("logs metrics in several batches", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local res = assert(proxy_client:send { method = "GET", path = "/status/200?apikey=kong", headers = { ["Host"] = "datadog7.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) assert.contains("kong.request.count:1|c|#name:dd7,status:200,consumer:bar,app:kong" , gauges) assert.contains("kong.latency:%d+|ms|#name:dd7,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.request.size:%d+|ms|#name:dd7,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.response.size:%d+|ms|#name:dd7,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.upstream_latency:%d+|ms|#name:dd7,status:200,consumer:bar,app:kong", gauges, true) assert.contains("kong.kong_latency:%d*|ms|#name:dd7,status:200,consumer:bar,app:kong", gauges, true) end) -- the purpose of this test case is to test the batch queue -- finish processing messages in one time(no retries) it("no more messages than expected", function() local thread = helpers.udp_server(9999, 10, 10) local res = assert(proxy_client:send { method = "GET", path = "/status/200?apikey=kong", headers = { ["Host"] = "datadog7.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) end) it("should not return a runtime error (regression)", function() local thread = helpers.udp_server(9999, 1, 1) local res = assert(proxy_client:send { method = "GET", path = "/NonMatch", headers = { ["Host"] = "fakedns.test" } }) assert.res_status(404, res) assert.logfile().has.no.line("attempt to index field 'api' (a nil value)", true) -- make a valid request to make thread end assert(proxy_client:send { method = "GET", path = "/status/200", headers = { ["Host"] = "datadog3.test" } }) thread:join() end) it("referenceable fields works", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT, 6) local another_proxy_client = helpers.proxy_client() local res = assert(another_proxy_client:send { method = "GET", path = "/test_schema", headers = { ["Host"] = "datadog8.test" } }) assert.res_status(200, res) local ok, gauges = thread:join() assert.True(ok) assert.equal(DEFAULT_METRICS_COUNT, #gauges) end) it("datadog plugin is triggered for serviceless routes", function() local thread = helpers.udp_server(9999, DEFAULT_METRICS_COUNT) local res = assert(proxy_client:send { method = "GET", path = "/serviceless", }) local body = assert.res_status(200, res) assert.equals(body, '{"message":"OK"}') local ok, gauges = thread:join() assert.True(ok) assert.equals(DEFAULT_METRICS_COUNT, #gauges) for _, g in ipairs(gauges) do -- tags start with `#` local tmp, tag_idx = splitn(g, '#') assert(tag_idx == 2, "Error: missing tags") local tags = tmp[tag_idx] assert(tags, "Error: missing tags") assert(string.match(tags, "name:,"), "Error: the value of `name` must be an empty string for serviceless routes") end end) end) end end)