feat(serializers.nowmetric): Add option for JSONv2 format (#13722)

This commit is contained in:
Joshua Powers 2023-08-08 14:19:31 -06:00 committed by GitHub
parent 8bc6822e28
commit 93bf2becce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 39 deletions

View File

@ -1,16 +1,22 @@
# ServiceNow Metrics serializer
The ServiceNow Metrics serializer outputs metrics in the [ServiceNow Operational Intelligence format][ServiceNow-format].
The ServiceNow Metrics serializer outputs metrics in the
[ServiceNow Operational Intelligence format][ServiceNow-format] or optionally
with the [ServiceNow JSONv2 format][ServiceNow-jsonv2]
It can be used to write to a file using the file output, or for sending metrics to a MID Server with Enable REST endpoint activated using the standard telegraf HTTP output.
If you're using the HTTP output, this serializer knows how to batch the metrics so you don't end up with an HTTP POST per metric.
It can be used to write to a file using the file output, or for sending metrics
to a MID Server with Enable REST endpoint activated using the standard telegraf
HTTP output. If you are using the HTTP output, this serializer knows how to
batch the metrics so you do not end up with an HTTP POST per metric.
[ServiceNow-format]: https://docs.servicenow.com/bundle/london-it-operations-management/page/product/event-management/reference/mid-POST-metrics.html
[ServiceNow-jsonv2]: https://docs.servicenow.com/bundle/tokyo-application-development/page/integrate/inbound-other-web-services/concept/c_JSONv2WebService.html
An example event looks like:
An example Operational Intelligence format event looks like:
```javascript
[{
```json
[
{
"metric_type": "Disk C: % Free Space",
"resource": "C:\\",
"node": "lnux100",
@ -19,8 +25,29 @@ An example event looks like:
"ci2metric_id": {
"node": "lnux100"
},
"source": “Telegraf”
}]
"source": "Telegraf"
}
]
```
An example of the JSONv2 format even looks like:
```json
{
"records": [
{
"metric_type": "Disk C: % Free Space",
"resource": "C:\\",
"node": "lnux100",
"value": 50,
"timestamp": 1473183012000,
"ci2metric_id": {
"node": "lnux100"
},
"source": "Telegraf"
}
]
}
```
## Using with the HTTP output
@ -55,6 +82,13 @@ To send this data to a ServiceNow MID Server with Web Server extension activated
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
data_format = "nowmetric"
## Format Type
## By default, the serializer returns an array of metrics matching the
## Now Metric Operational Intelligence format or with the option set to 'oi'.
## Optionally, if set to 'jsonv2' the output format will involve the newer
## JSON object based format.
# nowmetric_format = "oi"
## Additional HTTP headers
[outputs.http.headers]
# # Should be set manually to "application/json" for json data_format
@ -66,6 +100,23 @@ Starting with the [London release](https://docs.servicenow.com/bundle/london-it-
),
you also need to explicitly create event rule to allow binding of metric events to host CIs.
## Metric Format
The following describes the two options of the `nowmetric_format` option:
The Operational Intelligence format is used along with the
`/api/mid/sa/metrics` API endpoint. The payload is requires a JSON array full
of metrics. This is the default settings and used when set to `oi`. See the
[ServiceNow KB0853084][KB0853084] for more details on this format.
Another option is the use of the [JSONv2 web service][jsonv2]. This service
requires a different format that is [JSON object based][jsonv2_format]. This
option is used when set to `jsonv2`.
[KB0853084]: https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0853084
[jsonv2]: https://docs.servicenow.com/bundle/tokyo-application-development/page/integrate/inbound-other-web-services/concept/c_JSONv2WebService.html
[jsonv2_format]: https://docs.servicenow.com/bundle/tokyo-application-development/page/integrate/inbound-other-web-services/concept/c_JSONObjectFormat.html
## Using with the File output
You can use the file output to output the payload in a file.

View File

@ -1,7 +1,6 @@
package nowmetric
import (
"bytes"
"encoding/json"
"fmt"
"time"
@ -10,20 +9,9 @@ import (
"github.com/influxdata/telegraf/plugins/serializers"
)
type Serializer struct{}
/*
Example for the JSON generated and pushed to the MID
{
"metric_type":"cpu_usage_system",
"resource":"",
"node":"ASGARD",
"value": 0.89,
"timestamp":1487365430,
"ci2metric_id":{"node":"ASGARD"},
"source":"Telegraf"
type Serializer struct {
Format string `toml:"nowmetric_format"`
}
*/
type OIMetric struct {
Metric string `json:"metric_type"`
@ -36,30 +24,47 @@ type OIMetric struct {
}
type OIMetrics []OIMetric
type OIMetricsObj struct {
Records []OIMetric `json:"records"`
}
func (s *Serializer) Init() error {
switch s.Format {
case "":
s.Format = "oi"
case "oi", "jsonv2":
default:
return fmt.Errorf("invalid format %q", s.Format)
}
return nil
}
func (s *Serializer) Serialize(metric telegraf.Metric) (out []byte, err error) {
serialized, err := s.createObject(metric)
if err != nil {
return []byte{}, err
m := s.createObject(metric)
if s.Format == "jsonv2" {
obj := OIMetricsObj{Records: m}
return json.Marshal(obj)
}
return serialized, nil
return json.Marshal(m)
}
func (s *Serializer) SerializeBatch(metrics []telegraf.Metric) (out []byte, err error) {
objects := make([]byte, 0)
objects := make([]OIMetric, 0)
for _, metric := range metrics {
m, err := s.createObject(metric)
if err != nil {
return nil, fmt.Errorf("dropping invalid metric: %s", metric.Name())
} else if m != nil {
objects = append(objects, m...)
}
}
replaced := bytes.Replace(objects, []byte("]["), []byte(","), -1)
return replaced, nil
objects = append(objects, s.createObject(metric)...)
}
func (s *Serializer) createObject(metric telegraf.Metric) ([]byte, error) {
if s.Format == "jsonv2" {
obj := OIMetricsObj{Records: objects}
return json.Marshal(obj)
}
return json.Marshal(objects)
}
func (s *Serializer) createObject(metric telegraf.Metric) OIMetrics {
/* ServiceNow Operational Intelligence supports an array of JSON objects.
** Following elements accepted in the request body:
** metric_type: The name of the metric
@ -117,9 +122,7 @@ func (s *Serializer) createObject(metric telegraf.Metric) ([]byte, error) {
allmetrics = append(allmetrics, oimetric)
}
metricsJSON, err := json.Marshal(allmetrics)
return metricsJSON, err
return allmetrics
}
func verifyValue(v interface{}) bool {

View File

@ -199,3 +199,52 @@ func TestSerializeBatch(t *testing.T) {
buf,
)
}
func TestSerializeJSONv2Format(t *testing.T) {
m := metric.New(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
)
s := &Serializer{Format: "jsonv2"}
buf, err := s.Serialize(m)
require.NoError(t, err)
require.Equal(
t,
[]byte(`{"records":[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"}]}`),
buf,
)
}
func TestSerializeJSONv2FormatBatch(t *testing.T) {
m := metric.New(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
)
s := &Serializer{Format: "jsonv2"}
metrics := []telegraf.Metric{m, m}
buf, err := s.SerializeBatch(metrics)
require.NoError(t, err)
require.Equal(
t,
[]byte(
`{"records":[`+
`{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"},`+
`{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"}`+
`]}`,
),
buf,
)
}
func TestSerializeInvalidFormat(t *testing.T) {
s := &Serializer{Format: "foo"}
require.Error(t, s.Init())
}