grok_exporter
grok_exporter copied to clipboard
Feature Request: User expandable template functions
Currently the go-templates that are evaluated for value and labels fields have the following template functions: gsub, base, add, subtract, multiply and divide, but no easy way to add new functions dynamically.
I suggest that we consider options for dynamically expanding upon the set of template functions, such that more advanced processing can be enabled.
I myself have three potential solutions:
- Make a hardcoded template function called
remote_json, which takes aurlas its first argument and aninterface{}as its second argument, serializes theinterface{}to JSON, andPOSTs it to theurloverHTTP(s), expecting aJSONobject to be returned, which will then be returned to the template context.
Example usage: Label template:
'{{ index ( remote_json "http://localhost:4242" (index .extra "ip") ) "country_code" }}
Log message:
{"message": "Login occured", "user": "Skeen", "ip": "1.1.1.1"}'
With the service at localhost:4242 returning JSON akin to JSON from: https://freegeoip.app/json/1.1.1.1
With the output label being:
label: "AU"
This system has the advantage of being very flexible, as people can develop services externally using whatever language and framework they desire, but may have a disadvantage of being poor for performance, and especially for latency.
- Make a plugin architecture, where go plugins can be dynamically loaded in at start up, and added to the template functions map.
I have implemented a proof-of-concept for this soiution here: https://github.com/magenta-aps/grok_exporter/tree/feature/template_plugins, with a geo_ip filter implemented here: https://github.com/magenta-aps/grok_exporter/tree/feature/template_plugins/dynamic/geo_ip
Example usage: Label template:
'{{ index ( geo_ip (index .extra "ip") ) "country_code" }}
With the output label being:
label: "AU"
This system has the advantage of binding go-functions directly in the binary, and thus reducing latency to the absolut minimum, however it requires services to be written in go and with a specific architecture in mind. It is however still very flexible, but only on non-Windows platforms.
- Embed a scripting language into the system, and allow plugins to be written in whatever dynamic language one chooses. I imagine this would work similar to how solution 2 works, but with a more dynamic structure compared to
.soloading.
The system has some of the same disadvantages as 2, being bound to a single language, but also introduces higher latency and probably worse performance, but being very flexible and working on all platforms.
The usual choice for this kind of work would be Lua.
With any of these solutions, the following tickets could be fixed somewhat easily:
- #50 could be implemented with a custom service, which keeps some log id mapped to a timestamp.
- #54 could be implemented with a custom service, which does uppercase on the input.
- #79 could be implemented with a custom service, which simply wraps the sprig functions.
Let me know your thoughts on this.
Thanks for bringing this up. I'm in favor of option 1. Option 2 would mean that we introduce a feature that will not be available for Windows users, and I'd like to avoid this if possible. Option 3 is very flexible, but it also increases complexity for users, because eventually grok_exporter will need to be shipped with a directory of Lua scripts.
Built-in functions are easy to use, and readily available for all users. We should encourage everyone who's missing a function to implement it and open a PR to add it in the templates package. If we are lucky, and add whatever people need, we will end up with a long list of functions that users can choose from. Some of them might be very special purpose and not relevant for most users, but I don't think that's a problem.
For Option 2, https://github.com/golang/go/issues/19282 is related. That Windows is not supported is to be considered temporary until go adds support for Windows for their plugin module (although the ticket has been open for 3 years).