Skip to main content

Metrics Exporter

Canary checker can export custom metrics from any check type, replacing and/or consolidating multiple standalone Prometheus Exporters into a single exporter.

In the example below, exchange rates against the USD are exported by first calling an HTTP api and then using the values from the JSON response to create the metrics:

exchange-rates-exporter.yaml
apiVersion: canaries.flanksource.com/v1
kind: Canary
metadata:
name: exchange-rates
spec:
schedule: "every 1 @hour"
http:
- name: exchange-rates
url: https://api.frankfurter.app/latest?from=USD&to=GBP,EUR,ILS
metrics:
- name: exchange_rate
type: gauge
value: result.json.rates.GBP
labels:
- name: "from"
value: "USD"
- name: to
value: GBP

- name: exchange_rate
type: gauge
value: result.json.rates.EUR
labels:
- name: "from"
value: "USD"
- name: to
value: EUR

- name: exchange_rate
type: gauge
value: result.json.rates.ILS
labels:
- name: "from"
value: "USD"
- name: to
value: ILS
- name: exchange_rate_api
type: histogram
value: result.elapsed.getMilliseconds()

Which would output:

exchange_rate{from=USD, to=GBP} 0.819
exchange_rate{from=USD, to=EUR} 0.949
exchange_rate{from=USD, to=ILS} 3.849
exchange_rate_api 260.000

Stateful Metrics

Metrics can be generated from time based data, e.g. logs per minute, logins per second by using the output of one check execution as the input to the next.

apiVersion: canaries.flanksource.com/v1
kind: Canary
metadata:
name: "container-log-counts"
namespace: observability
# The schedule can be as short or as long as you want, the query will always search for log
# since the last query
schedule: "@every 5m"
http:
- name: container_log_volume
url: "https://elasticsearch/logstash-*/_search"
headers:
- name: Content-Type
value: application/json
templateBody: true
test:
# if no logs are found, fail the health check
expr: json.?aggregations.logs.doc_count.orValue(0) > 0
# query for log counts by namespace, container and pod that have been created since the last check
body: >-
{
"size": 0,
"aggs": {
"logs": {
"filter": {
"range": {
"@timestamp" : {
{{- if last_result.results.max }}
"gte": "{{ last_result.results.max }}"
{{- else }}
"gte": "now-5m"
{{- end }}
}
}
},
"aggs": {
"age": {
"max": {
"field": "@timestamp"
}
},
"labels": {
"multi_terms": {
"terms": [
{ "field": "kubernetes_namespace_name.keyword"},
{ "field": "kubernetes_container_name.keyword"},
{ "field": "kubernetes_pod_name.keyword"}
],
"size": 1000
}
}
}
}
}
}
transform:
# Save the maximum age for usage in subsequent queries and create a metric for each pair
expr: |
json.orValue(null) != null ?
[{
'detail': { 'max': string(json.?aggregations.logs.age.value_as_string.orValue(last_result().?results.max.orValue(time.Now()))) },
'metrics': json.?aggregations.logs.labels.buckets.orValue([]).map(k, {
'name': "namespace_log_count",
'type': "counter",
'value': double(k.doc_count),
'labels': {
"namespace": k.key[0],
"container": k.key[1],
"pod": k.key[2]
}
})
}].toJSON()
: '{}'

This snippet will retrieve the last_result.results.max value from the last execution ensuring data is not duplicated or missed

 "@timestamp" : {
{{- if last_result.results.max }}
"gte": "{{ last_result.results.max }}"
{{- else }}
"gte": "now-5m"
{{- end }}
}

The max value is saved in the transform section using:

#...
'detail': { 'max': string(json.?aggregations.logs.age.value_as_string.orValue(last_result().?results.max.orValue(time.Now()))) },
#...

Result Variables

FieldDescriptionSchemeRequired
metricsMetrics to export from check resultsMetric

Metric

FieldDescriptionSchemeRequired
nameName of the metricstringYes
valueAn expression to derive the metric value fromExpressionYes
typePrometheus Metric Type (counter, gauge or histogram)stringYes
labelsLabels for prometheus metric (values can be templated)[]Label

Label

FieldDescriptionSchemeRequired
nameName of the labelstringYes
valueA static value for the headerstring
valueExprAn expression to derive the header value fromExpression
labelsLabels for prometheus metric (values can be templated)map[string]string

Expresions can make use of the following variables:

Expression Variables

FieldsDescriptionScheme
resultCheck ResultSee result variables section of the check
last_result.resultsThe last result
check.nameCheck namestring
check.descriptionCheck descriptionstring
check.labelsDynamic labels attached to the checkmap[string]string
check.endpointEndpoint (usally a URL)string
check.durationDuration in millisecondsint64
canary.nameCanary namestring
canary.namespaceCanary namespacestring
canary.labelsLabels attached to the canary CRD (if any)map[string]string