kong.metrics exposes three metric constructors. Each one returns a metric handle that you keep and record values against:
|
Constructor
|
Metric kind
|
Record methods
|
Typical use
|
Recording semantics
|
kong.metrics.counter(name, opts)
|
Monotonic sum
|
:add()
|
Counts that only go up, like requests or errors.
|
A cumulative, monotonic sum. Exported with is_monotonic = true and cumulative aggregation temporality.
|
kong.metrics.gauge(name, opts)
|
Settable value
|
:record(), :add()
|
Current values that go up and down, like queue depth.
|
A last-write value. :record() sets it. :add() applies a delta. Exported with cumulative aggregation temporality.
|
kong.metrics.histogram(name, opts)
|
Distribution
|
:record()
|
Value distributions, like latencies or sizes.
|
Tracks count, sum, min, max, and per-bucket counts. Exported with cumulative aggregation temporality.
|
Metrics are stored in the kong_metrics shared dictionary and exported as OTLP data points by the OpenTelemetry plugin.
-
Shared dictionary: Metrics are stored in the lua_shared_dict kong_metrics shared dictionary (stream_kong_metrics for the stream subsystem).
The dictionary is sized by the metrics_mem_size configuration parameter, which defaults to 10m.
Registering many custom metrics, or using many distinct attribute-value combinations, multiplies the number of keys stored. Increase metrics_mem_size if either applies to your plugin.
-
OpenTelemetry plugin: Custom metrics leave Kong Gateway only through the OpenTelemetry plugin, which merges your metrics into its OTLP export batch.
Without the OpenTelemetry plugin configured with metrics enabled and an OTLP endpoint set, values still accumulate in the shared dictionary but are never exported.
The Metrics PDK validates every registration and record call against the following rules:
|
Rule
|
Description
|
|
Metric and attribute name pattern
|
^[a-z_][a-z0-9_.]*$. Metric and attribute names use lowercase letters, digits, _, and ., and must start with a letter or underscore.
|
|
Reserved names
|
You can’t register a name that matches a built-in Kong Gateway metric, for example http.server.request.count or kong.nginx.connection.count.
|
|
Attribute count
|
Capped at 15 per :add() or :record() call, to protect the Gateway against cardinality blow-ups.
A call that exceeds this limit drops the data point and logs an error.
|
|
Attribute value constraints
|
Values must be strings or numbers, must not be NaN, and strings must not contain ,, {, or }.
|
|
Duplicate registration
|
Not supported. If you attempt to register a metric name that’s already in use, the metric is rejected and logs an error.
|
|
Value constraints
|
Counter and histogram values must be finite numbers greater than or equal to 0.
Gauge values must be finite, and may be negative. Non-finite values (NaN, ±inf) are rejected everywhere.
|
The OpenTelemetry plugin appends custom metrics to its OTLP export batch on its push interval:
attributes, the table passed at record time, becomes the OTLP data-point’s attributes.
value_type becomes as_int or as_double for counter and gauge points.
- Histogram points export
count, sum, min, max, bucket_counts, and explicit_bounds.
description and unit are carried on the exported metric.
Kong Gateway’s built-in metrics and custom PDK metrics are exported together.
If the OpenTelemetry plugin hasn’t produced any built-in metrics on a given cycle, custom metrics are still exported on their own.
The Metrics PDK is designed to never disrupt the request path:
- If the
kong_metrics shared dictionary isn’t available, or the metrics subsystem fails to initialize, kong.metrics returns no-op handles. In this situation, :add() and :record() do nothing.
- Invalid registration input, like a bad name, a reserved name, malformed options, or a conflicting duplicate, logs an error and returns a no-op handle. The calling code continues to work.
- Invalid record-time input, like the wrong attribute count (exceeding the maximum of 15), a non-finite value, a negative value where it isn’t allowed, or the wrong method for the metric kind, logs an error and drops that single observation.
Because failures degrade to no-ops, check Kong Gateway’s error log when a custom metric doesn’t appear as expected.
Register each metric once, for example as a plugin-module-level local variable or in your plugin’s init_worker handler, and keep the returned handle to record values against later, typically from the access, response, or log phase:
-- Registered when the handler module loads at Gateway startup
local requests_total = kong.metrics.counter("my_plugin.requests", {
description = "Number of requests processed by my_plugin",
unit = "{request}",
})
local MyPluginHandler = { PRIORITY = 1000, VERSION = "1.0.0" }
function MyPluginHandler:log(conf)
requests_total:add(1, { ["kong.service.name"] = "example-service", status = "success" })
end
return MyPluginHandler
Each record call takes its own attributes table as its second argument, so different call sites for the same metric can report different attributes:
requests_total:add(1, { status = "success" })
requests_total:add(1, { consumer = consumer.id })
Here’s an example of a complete plugin handler that registers a counter, a histogram, and a gauge, and records against each of them per request:
local requests_total = kong.metrics.counter("my_plugin.requests.total", {
description = "Total requests processed by my_plugin",
unit = "{request}",
})
local requests_duration = kong.metrics.histogram("my_plugin.requests.duration", {
description = "Duration of requests processed by my_plugin",
unit = "s",
explicit_bounds = { 0.01, 0.05, 0.1, 0.5, 1, 5 },
})
local body_size = kong.metrics.gauge("my_plugin.body.size", {
description = "The size of the request body processed by my_plugin",
unit = "{By}",
value_type = kong.metrics.VALUE_TYPE.AS_DOUBLE,
})
-- Attribute values must be strings/numbers, keyed by attribute name.
-- Fall back to "unknown" for missing/unnamed entities.
local function get_attributes()
local service = kong.router.get_service()
local route = kong.router.get_route()
local service_name = (service and type(service.name) == "string") and service.name or "unknown"
local route_name = (route and type(route.name) == "string") and route.name or "unknown"
return {
["kong.service.name"] = service_name,
["kong.route.name"] = route_name,
}
end
local MyPluginHandler = {
PRIORITY = 1000,
VERSION = "1.0.0",
}
function MyPluginHandler:access(conf)
ngx.ctx.start_time = ngx.now()
requests_total:add(1, get_attributes())
body_size:record(2.3, get_attributes())
end
function MyPluginHandler:response(conf)
local start_time = ngx.ctx.start_time
if not start_time then
return
end
requests_duration:record(ngx.now() - start_time, get_attributes())
end
return MyPluginHandler
Warning: The Metrics PDK doesn’t mask or redact attribute values. You’re responsible for what your plugin puts into an attributes table: don’t record sensitive data, like personally identifiable information, credentials, or tokens, as an attribute value.
See the kong.metrics PDK reference for the full parameter and return value details for counter(), gauge(), and histogram().