You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2021/11/09 01:35:37 UTC

[apisix] branch master updated: feat(plugins): Datadog for metrics collection (#5372)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new da691b9  feat(plugins): Datadog for metrics collection (#5372)
da691b9 is described below

commit da691b92b819d40ffa1df6b1b76a0d4012606fc8
Author: Bisakh <bi...@gmail.com>
AuthorDate: Tue Nov 9 07:05:30 2021 +0530

    feat(plugins): Datadog for metrics collection (#5372)
---
 apisix/plugins/datadog.lua        | 277 ++++++++++++++++++++++++++
 conf/config-default.yaml          |   1 +
 docs/en/latest/config.json        |   3 +-
 docs/en/latest/plugins/datadog.md | 131 +++++++++++++
 t/admin/plugins.t                 |   2 +-
 t/lib/mock_dogstatsd.lua          |  45 +++++
 t/plugin/datadog.t                | 397 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 854 insertions(+), 2 deletions(-)

diff --git a/apisix/plugins/datadog.lua b/apisix/plugins/datadog.lua
new file mode 100644
index 0000000..7fe7d3f
--- /dev/null
+++ b/apisix/plugins/datadog.lua
@@ -0,0 +1,277 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+local core = require("apisix.core")
+local plugin = require("apisix.plugin")
+local batch_processor = require("apisix.utils.batch-processor")
+local fetch_log = require("apisix.utils.log-util").get_full_log
+local ngx = ngx
+local udp = ngx.socket.udp
+local format = string.format
+local concat = table.concat
+local buffers = {}
+local ipairs = ipairs
+local tostring = tostring
+local stale_timer_running = false
+local timer_at = ngx.timer.at
+
+local plugin_name = "datadog"
+local defaults = {
+    host = "127.0.0.1",
+    port = 8125,
+    namespace = "apisix",
+    constant_tags = {"source:apisix"}
+}
+
+local schema = {
+    type = "object",
+    properties = {
+        buffer_duration = {type = "integer", minimum = 1, default = 60},
+        inactive_timeout = {type = "integer", minimum = 1, default = 5},
+        batch_max_size = {type = "integer", minimum = 1, default = 5000},
+        max_retry_count = {type = "integer", minimum = 1, default = 1},
+    }
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        host = {type = "string", default= defaults.host},
+        port = {type = "integer", minimum = 0, default = defaults.port},
+        namespace = {type = "string", default = defaults.namespace},
+        constant_tags = {
+            type = "array",
+            items = {type = "string"},
+            default = defaults.constant_tags
+        }
+    },
+}
+
+local _M = {
+    version = 0.1,
+    priority = 495,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema,
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    return core.schema.check(schema, conf)
+end
+
+local function generate_tag(entry, const_tags)
+    local tags
+    if const_tags and #const_tags > 0 then
+        tags = core.table.clone(const_tags)
+    else
+        tags = {}
+    end
+
+    -- priority on route name, if not found using the route id.
+    if entry.route_name ~= "" then
+        core.table.insert(tags, "route_name:" .. entry.route_name)
+    elseif entry.route_id and entry.route_id ~= "" then
+        core.table.insert(tags, "route_name:" .. entry.route_id)
+    end
+
+    if entry.service_id and entry.service_id ~= "" then
+        core.table.insert(tags, "service_id:" .. entry.service_id)
+    end
+
+    if entry.consumer and entry.consumer ~= "" then
+        core.table.insert(tags, "consumer:" .. entry.consumer)
+    end
+    if entry.balancer_ip ~= "" then
+        core.table.insert(tags, "balancer_ip:" .. entry.balancer_ip)
+    end
+    if entry.response.status then
+        core.table.insert(tags, "response_status:" .. entry.response.status)
+    end
+    if entry.scheme ~= "" then
+        core.table.insert(tags, "scheme:" .. entry.scheme)
+    end
+
+    if #tags > 0 then
+        return "|#" .. concat(tags, ',')
+    end
+
+    return ""
+end
+
+-- remove stale objects from the memory after timer expires
+local function remove_stale_objects(premature)
+    if premature then
+        return
+    end
+
+    for key, batch in ipairs(buffers) do
+        if #batch.entry_buffer.entries == 0 and #batch.batch_to_process == 0 then
+            core.log.warn("removing batch processor stale object, conf: ",
+                          core.json.delay_encode(key))
+            buffers[key] = nil
+        end
+    end
+
+    stale_timer_running = false
+end
+
+function _M.log(conf, ctx)
+
+    if not stale_timer_running then
+        -- run the timer every 30 mins if any log is present
+        timer_at(1800, remove_stale_objects)
+        stale_timer_running = true
+    end
+
+    local entry = fetch_log(ngx, {})
+    entry.upstream_latency = ctx.var.upstream_response_time * 1000
+    entry.balancer_ip = ctx.balancer_ip or ""
+    entry.route_name = ctx.route_name or ""
+    entry.scheme = ctx.upstream_scheme or ""
+
+    local log_buffer = buffers[conf]
+    if log_buffer then
+        log_buffer:push(entry)
+        return
+    end
+
+    -- Generate a function to be executed by the batch processor
+    local func = function(entries, batch_max_size)
+        -- Fetching metadata details
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if not metadata then
+            core.log.info("received nil metadata: using metadata defaults: ",
+                                core.json.delay_encode(defaults, true))
+            metadata = {}
+            metadata.value = defaults
+        end
+
+        -- Creating a udp socket
+        local sock = udp()
+        local host, port = metadata.value.host, metadata.value.port
+        core.log.info("sending batch metrics to dogstatsd: ", host, ":", port)
+
+        local ok, err = sock:setpeername(host, port)
+
+        if not ok then
+            return false, "failed to connect to UDP server: host[" .. host
+                        .. "] port[" .. tostring(port) .. "] err: " .. err
+        end
+
+        -- Generate prefix & suffix according dogstatsd udp data format.
+        local prefix = metadata.value.namespace
+        if prefix ~= "" then
+            prefix = prefix .. "."
+        end
+
+        core.log.info("datadog batch_entry: ", core.json.delay_encode(entries, true))
+        for _, entry in ipairs(entries) do
+            local suffix = generate_tag(entry, metadata.value.constant_tags)
+
+            -- request counter
+            local ok, err = sock:send(format("%s:%s|%s%s", prefix ..
+                                            "request.counter", 1, "c", suffix))
+            if not ok then
+                core.log.error("failed to report request count to dogstatsd server: host[" .. host
+                        .. "] port[" .. tostring(port) .. "] err: " .. err)
+            end
+
+
+            -- request latency histogram
+            local ok, err = sock:send(format("%s:%s|%s%s", prefix ..
+                                        "request.latency", entry.latency, "h", suffix))
+            if not ok then
+                core.log.error("failed to report request latency to dogstatsd server: host["
+                        .. host .. "] port[" .. tostring(port) .. "] err: " .. err)
+            end
+
+            -- upstream latency
+            local apisix_latency = entry.latency
+            if entry.upstream_latency then
+                local ok, err = sock:send(format("%s:%s|%s%s", prefix ..
+                                        "upstream.latency", entry.upstream_latency, "h", suffix))
+                if not ok then
+                    core.log.error("failed to report upstream latency to dogstatsd server: host["
+                                .. host .. "] port[" .. tostring(port) .. "] err: " .. err)
+                end
+                apisix_latency =  apisix_latency - entry.upstream_latency
+                if apisix_latency < 0 then
+                    apisix_latency = 0
+                end
+            end
+
+            -- apisix_latency
+            local ok, err = sock:send(format("%s:%s|%s%s", prefix ..
+                                            "apisix.latency", apisix_latency, "h", suffix))
+            if not ok then
+                core.log.error("failed to report apisix latency to dogstatsd server: host[" .. host
+                        .. "] port[" .. tostring(port) .. "] err: " .. err)
+            end
+
+            -- request body size timer
+            local ok, err = sock:send(format("%s:%s|%s%s", prefix ..
+                                            "ingress.size", entry.request.size, "ms", suffix))
+            if not ok then
+                core.log.error("failed to report req body size to dogstatsd server: host[" .. host
+                        .. "] port[" .. tostring(port) .. "] err: " .. err)
+            end
+
+            -- response body size timer
+            local ok, err = sock:send(format("%s:%s|%s%s", prefix ..
+                                            "egress.size", entry.response.size, "ms", suffix))
+            if not ok then
+                core.log.error("failed to report response body size to dogstatsd server: host["
+                        .. host .. "] port[" .. tostring(port) .. "] err: " .. err)
+            end
+        end
+
+        -- Releasing the UDP socket desciptor
+        ok, err = sock:close()
+        if not ok then
+            core.log.error("failed to close the UDP connection, host[",
+                            host, "] port[", port, "] ", err)
+        end
+
+        -- Returning at the end and ensuring the resource has been released.
+        return true
+    end
+    local config = {
+        name = plugin_name,
+        retry_delay = conf.retry_delay,
+        batch_max_size = conf.batch_max_size,
+        max_retry_count = conf.max_retry_count,
+        buffer_duration = conf.buffer_duration,
+        inactive_timeout = conf.inactive_timeout,
+        route_id = ctx.var.route_id,
+        server_addr = ctx.var.server_addr,
+    }
+
+    local err
+    log_buffer, err = batch_processor:new(func, config)
+
+    if not log_buffer then
+        core.log.error("error when creating the batch processor: ", err)
+        return
+    end
+
+    buffers[conf] = log_buffer
+    log_buffer:push(entry)
+end
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 236d50b..922a91f 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -341,6 +341,7 @@ plugins:                          # plugin list (sorted by priority)
   #- dubbo-proxy                   # priority: 507
   - grpc-transcode                 # priority: 506
   - prometheus                     # priority: 500
+  - datadog                        # priority: 495
   - echo                           # priority: 412
   - http-logger                    # priority: 410
   - sls-logger                     # priority: 406
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 7c88b9c..11a284b 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -105,7 +105,8 @@
             "plugins/prometheus",
             "plugins/zipkin",
             "plugins/skywalking",
-            "plugins/node-status"
+            "plugins/node-status",
+            "plugins/datadog"
           ]
         },
         {
diff --git a/docs/en/latest/plugins/datadog.md b/docs/en/latest/plugins/datadog.md
new file mode 100644
index 0000000..508ae1c
--- /dev/null
+++ b/docs/en/latest/plugins/datadog.md
@@ -0,0 +1,131 @@
+---
+title: datadog
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+## Summary
+
+- [Summary](#summary)
+- [Name](#name)
+- [Attributes](#attributes)
+- [Metadata](#metadata)
+- [Exported Metrics](#exported-metrics)
+- [How To Enable](#how-to-enable)
+- [Disable Plugin](#disable-plugin)
+
+## Name
+
+`datadog` is a monitoring plugin built into Apache APISIX for seamless integration with [Datadog](https://www.datadoghq.com/), one of the most used monitoring and observability platform for cloud applications. If enabled, this plugin supports multiple powerful types of metrics capture for every request and response cycle that essentially reflects the behaviour and health of the system.
+
+This plugin pushes its custom metrics to the DogStatsD server, comes bundled with Datadog agent (to learn more about how to install a datadog agent, please visit [here](https://docs.datadoghq.com/agent/) ), over the UDP protocol. DogStatsD basically is an implementation of StatsD protocol which collects the custom metrics for Apache APISIX agent, aggregates it into a single data point and sends it to the configured Datadog server.
+To learn more about DogStatsD, please visit [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/?tab=hostagent) documentation.
+
+This plugin provides the ability to push metrics as a batch to the external Datadog agent, reusing the same datagram socket. In case if you did not receive the log data, don't worry give it some time. It will automatically send the logs after the timer function expires in our Batch Processor.
+
+For more info on Batch-Processor in Apache APISIX please refer.
+[Batch-Processor](../batch-processor.md)
+
+## Attributes
+
+| Name             | Type   | Requirement  | Default      | Valid | Description                                                                                |
+| -----------      | ------ | -----------  | -------      | ----- | ------------------------------------------------------------                               |
+| batch_max_size   | integer | optional    | 5000         | [1,...] | Max buffer size of each batch                                                            |
+| inactive_timeout | integer | optional    | 5            | [1,...] | Maximum age in seconds when the buffer will be flushed if inactive                       |
+| buffer_duration  | integer | optional    | 60           | [1,...] | Maximum age in seconds of the oldest entry in a batch before the batch must be processed |
+| max_retry_count  | integer | optional    | 1            | [1,...] | Maximum number of retries if one entry fails to reach dogstatsd server                   |
+
+## Metadata
+
+| Name        | Type    | Requirement |     Default        | Valid         | Description                                                            |
+| ----------- | ------  | ----------- |      -------       | -----         | ---------------------------------------------------------------------- |
+| host        | string  | optional    |  "127.0.0.1"       |               | The DogStatsD server host address                                      |
+| port        | integer | optional    |    8125            |               | The DogStatsD server host port                                         |
+| namespace   | string  | optional    |    "apisix"        |               | Prefix for all the custom metrics sent by APISIX agent. Useful for finding entities for metric graph. e.g. (apisix.request.counter)                                        |
+| constant_tags | array | optional    | [ "source:apisix" ] |              | Static tags embedded into generated metrics. Useful for grouping metric over certain signals. |
+
+To know more about how to effectively write tags, please visit [here](https://docs.datadoghq.com/getting_started/tagging/#defining-tags)
+
+## Exported Metrics
+
+Apache APISIX agent, for every request response cycle, export the following metrics to DogStatsD server if the datadog plugin is enabled:
+
+| Metric Name               | StatsD Type   | Description               |
+| -----------               | -----------   | -------                   |
+| Request Counter           | Counter       | No of requests received.   |
+| Request Latency           | Histogram     | Time taken to process the request (in milliseconds). |
+| Upstream latency          | Histogram     | Time taken to proxy the request to the upstream server till a response is received (in milliseconds). |
+| APISIX Latency            | Histogram     | Time taken by APISIX agent to process the request (in milliseconds). |
+| Ingress Size              | Timer         | Request body size in bytes. |
+| Egress Size               | Timer         | Response body size in bytes. |
+
+The metrics will be sent to the DogStatsD agent with the following tags:
+
+> If there is no suitable value for any particular tag, the tag will simply be omitted.
+
+- **route_name**: Name specified in the route schema definition. If not present, it will fall back to the route id value.
+  - Note: If multiple routes have the same name duplicated, we suggest you to visualize graphs on the Datadog dashboard over multiple tags that could compositely pinpoint a particular route/service. If it's still insufficient for your needs, feel free to drop a feature request at [apisix/issues](https://github.com/apache/apisix/issues).
+- **service_id**: If a route has been created with the abstraction of service, the particular service id will be used.
+- **consumer**: If the route has a linked consumer, the consumer Username will be added as a tag.
+- **balancer_ip**: IP of the Upstream balancer that has processed the current request.
+- **response_status**: HTTP response status code.
+- **scheme**: Scheme that has been used to make the request. e.g. HTTP, gRPC, gRPCs etc.
+
+## How To Enable
+
+The following is an example on how to enable the datadog plugin for a specific route. We are assumming your datadog agent is already up an running.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+      "plugins": {
+            "datadog": {}
+       },
+      "upstream": {
+           "type": "roundrobin",
+           "nodes": {
+               "127.0.0.1:1980": 1
+           }
+      },
+      "uri": "/hello"
+}'
+```
+
+Now any requests to uri `/hello` will generate aforesaid metrics and push it to DogStatsD server of the datadog agent.
+
+## Disable Plugin
+
+Remove the corresponding json configuration in the plugin configuration to disable the `datadog`.
+APISIX plugins are hot-reloaded, therefore no need to restart APISIX.
+
+```shell
+$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/hello",
+    "plugins": {},
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 2c67ef3..cc95a64 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -40,7 +40,7 @@ __DATA__
 --- request
 GET /apisix/admin/plugins/list
 --- response_body_like eval
-qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","ldap-auth","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-in [...]
+qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","ldap-auth","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-in [...]
 --- no_error_log
 [error]
 
diff --git a/t/lib/mock_dogstatsd.lua b/t/lib/mock_dogstatsd.lua
new file mode 100644
index 0000000..f4ee675
--- /dev/null
+++ b/t/lib/mock_dogstatsd.lua
@@ -0,0 +1,45 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local socket = ngx.req.socket
+
+local _M = {}
+
+function _M.go()
+    local sock, err = socket()
+    if not sock then
+        core.log.error("failed to get the request socket: ", err)
+        return
+     end
+
+    while true do
+        local data, err = sock:receive()
+
+        if not data then
+            if err and err ~= "no more data" then
+                core.log.error("socket error, returning: ", err)
+            end
+
+            return
+        else
+            core.log.warn("message received: ", data)
+        end
+    end
+end
+
+return _M
diff --git a/t/plugin/datadog.t b/t/plugin/datadog.t
new file mode 100644
index 0000000..b307ee7
--- /dev/null
+++ b/t/plugin/datadog.t
@@ -0,0 +1,397 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    $block->set_value("stream_conf_enable", 1);
+
+    if (!defined $block->extra_stream_config) {
+        my $stream_config = <<_EOC_;
+    server {
+        listen 8125 udp;
+        content_by_lua_block {
+            require("lib.mock_dogstatsd").go()
+        }
+    }
+_EOC_
+        $block->set_value("extra_stream_config", $stream_config);
+    }
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+    if (!$block->no_error_log && !$block->error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity check metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local plugin = require("apisix.plugins.datadog")
+            local ok, err = plugin.check_schema({host = "127.0.0.1", port = 8125}, core.schema.TYPE_METADATA)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: add plugin
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            -- setting the metadata
+            local code, meta_body = t('/apisix/admin/plugin_metadata/datadog',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "host":"127.0.0.1",
+                        "port": 8125
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "namespace": "apisix",
+                            "host": "127.0.0.1",
+                            "constant_tags": [
+                                "source:apisix"
+                            ],
+                            "port": 8125
+                        },
+                        "key": "/apisix/plugin_metadata/datadog"
+                    },
+                    "action": "set"
+                }]])
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "datadog": {
+                                "batch_max_size" : 1,
+                                "max_retry_count": 1
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "name": "datadog",
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "datadog": {
+                                    "batch_max_size": 1,
+                                    "max_retry_count": 1
+                                }
+                            },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1982": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing",
+                            "name": "datadog"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+
+            ngx.say(meta_body)
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+passed
+
+
+
+=== TEST 3: testing behaviour with mock suite
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, _, body = t("/opentracing", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- wait: 0.5
+--- response_body
+opentracing
+--- grep_error_log eval
+qr/message received: apisix(.+?(?=, ))/
+--- grep_error_log_out eval
+qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+/
+
+
+
+=== TEST 4: testing behaviour with multiple requests
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, _, body = t("/opentracing", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.print(body)
+
+            -- request 2
+            local code, _, body = t("/opentracing", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- wait: 0.5
+--- response_body
+opentracing
+opentracing
+--- grep_error_log eval
+qr/message received: apisix(.+?(?=, ))/
+--- grep_error_log_out eval
+qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+/
+
+
+
+=== TEST 5: testing behaviour with different namespace
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            -- Change the metadata
+            local code, meta_body = t('/apisix/admin/plugin_metadata/datadog',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "host":"127.0.0.1",
+                        "port": 8125,
+                        "namespace": "mycompany"
+                }]])
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.say(meta_body)
+
+            local code, _, body = t("/opentracing", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- wait: 0.5
+--- response_body
+passed
+opentracing
+--- grep_error_log eval
+qr/message received: mycompany(.+?(?=, ))/
+--- grep_error_log_out eval
+qr/message received: mycompany\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: mycompany\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: mycompany\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: mycompany\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: mycompany\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: mycompany\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+/
+
+
+
+=== TEST 6: testing behaviour with different constant tags
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            -- Change the metadata
+            local code, meta_body = t('/apisix/admin/plugin_metadata/datadog',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "host":"127.0.0.1",
+                        "port": 8125,
+                        "constant_tags": [
+                                "source:apisix",
+                                "new_tag:must"
+                            ]
+                }]])
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.say(meta_body)
+
+            local code, _, body = t("/opentracing", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- wait: 0.5
+--- response_body
+passed
+opentracing
+--- grep_error_log eval
+qr/message received: apisix(.+?(?=, ))/
+--- grep_error_log_out eval
+qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
+/
+
+
+
+=== TEST 7: testing behaviour when route_name is missing - must fallback to route_id
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "datadog": {
+                                "batch_max_size" : 1
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+            ngx.say(body)
+
+            -- making a request to the route
+            local code, _, body = t("/opentracing", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+            ngx.print(body)
+        }
+    }
+--- response_body
+passed
+opentracing
+--- wait: 0.5
+--- grep_error_log eval
+qr/message received: apisix(.+?(?=, ))/
+--- grep_error_log_out eval
+qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
+message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
+/