influxdb/telegraf.go

404 lines
13 KiB
Go
Raw Normal View History

package influxdb
2018-10-09 00:45:42 +00:00
import (
"context"
2018-10-16 00:38:36 +00:00
"encoding/json"
2018-10-17 19:51:35 +00:00
"errors"
2018-10-16 00:38:36 +00:00
"fmt"
2018-10-09 00:45:42 +00:00
"time"
2018-10-16 00:38:36 +00:00
"github.com/influxdata/influxdb/telegraf/plugins"
"github.com/influxdata/influxdb/telegraf/plugins/inputs"
"github.com/influxdata/influxdb/telegraf/plugins/outputs"
2018-10-09 00:45:42 +00:00
)
2019-04-15 19:25:48 +00:00
// ErrTelegrafConfigInvalidOrgID is the error message for a missing or invalid organization ID.
const ErrTelegrafConfigInvalidOrgID = "invalid org ID"
// ErrTelegrafConfigNotFound is the error message for a missing telegraf config.
feat(kv): implemented key/value store with end-to-end integration tests * feat(kv:inmem:bolt): implement user service in a kv * refactor(kv): use consistent func receiver name * feat(kv): add initial basic auth service * refactor(passwords): move auth interface into own file * refactor(passwords): rename basic auth files to passwords * refactor(passwords): rename from BasicAuth to Passwords * refactor(kv): copy bolt user test into kv Co-authored-by: Michael Desa <mjdesa@gmail.com> * feat(kv): add inmem testing to kv store * fix(kv): remove extra user index initialization * feat(kv): attempt at making errors nice * fix(http): return not found error if filter is invalid * fix(http): s/platform/influxdb/ for user service * fix(http): s/platform/influxdb/ for user service * feat(kv): initial port of telegraf configs to kv * feat(kv): first pass at migrating bolt org service to kv * feat(kv): first pass at bucket service * feat(kv): first pass at migrating kvlog to kv package * feat(kv): add resource op logs * feat(kv): first pass at user resource mapping migration * feat(kv): add urm usage to bucket and org services * feat(kv): first pass at kv authz service * feat(kv): add cascading auth delete for users * feat(kv): first pass d authorizer.OrganizationService in kv * feat(cmd/influxd/launcher): user kv services where appropriate * fix(kv): initialize authorizations * fix(influxdb): use same buckets while slowly migrating stuff * fix(kv): make staticcheck pass * feat(kv): add dashboards to kv review: make suggestions from pr review fix: use common bucket names for bolt/kv stores * test(kv): add complete password test coverage * chore(kv): fixes for staticcheck * feat(kv): implement labels generically on kv * feat(kv): implement macro service * feat(kv): add source service * feat(kv): add session service * feat(kv): add kv secret service * refactor(kv): update telegraf and urm with error messages * feat(kv): add lookup service * feat(kv): add kv onboarding service * refactor(kv): update telegraf to avoid repetition * feat(cmd/influxd): use kv lookup service * feat(kv): add telegraf to lookup service * feat(cmd/influxd): use kv telegraf service * feat(kv): initial port of scrapers in bolt to kv * feat(kv): update scraper error messaging * feat(cmd/influxd): add kv scraper * feat(kv): add inmem backend tests * refactor(kv): copy paste errors * refactor(kv): add code to password errors * fix(testing): update error messages for incorrect passwords * feat(kv:inmem:bolt): implement user service in a kv * refactor(kv): use consistent func receiver name * refactor(kv): copy bolt user test into kv Co-authored-by: Michael Desa <mjdesa@gmail.com> * feat(kv): add inmem testing to kv store * fix(kv): remove extra user index initialization * feat(kv): attempt at making errors nice * fix(http): return not found error if filter is invalid * fix(http): s/platform/influxdb/ for user service * feat(kv): first pass at migrating bolt org service to kv * feat(kv): first pass at bucket service * feat(kv): first pass at migrating kvlog to kv package * feat(kv): add resource op logs * feat(kv): first pass at user resource mapping migration * feat(kv): add urm usage to bucket and org services * feat(kv): first pass at kv authz service * feat(kv): add cascading auth delete for users * feat(kv): first pass d authorizer.OrganizationService in kv * feat(cmd/influxd/launcher): user kv services where appropriate * feat(kv): add initial basic auth service * refactor(passwords): move auth interface into own file * refactor(passwords): rename basic auth files to passwords * fix(http): s/platform/influxdb/ for user service * fix(kv): initialize authorizations * fix(influxdb): use same buckets while slowly migrating stuff * fix(kv): make staticcheck pass * feat(kv): add dashboards to kv review: make suggestions from pr review fix: use common bucket names for bolt/kv stores * feat(kv): implement labels generically on kv * refactor(passwords): rename from BasicAuth to Passwords * test(kv): add complete password test coverage * chore(kv): fixes for staticcheck * feat(kv): implement macro service * feat(kv): add source service * feat(kv): add session service * feat(kv): initial port of telegraf configs to kv * feat(kv): initial port of scrapers in bolt to kv * feat(kv): add kv secret service * refactor(kv): update telegraf and urm with error messages * feat(kv): add lookup service * feat(kv): add kv onboarding service * refactor(kv): update telegraf to avoid repetition * feat(cmd/influxd): use kv lookup service * feat(kv): add telegraf to lookup service * feat(cmd/influxd): use kv telegraf service * feat(kv): update scraper error messaging * feat(cmd/influxd): add kv scraper * feat(kv): add inmem backend tests * refactor(kv): copy paste errors * refactor(kv): add code to password errors * fix(testing): update error messages for incorrect passwords * feat(http): initial support for flushing all key/values from kv store * feat(kv): rename macro to variable * feat(cmd/influxd/launcher): user kv services where appropriate * refactor(passwords): rename from BasicAuth to Passwords * feat(kv): implement macro service * test(ui): introduce cypress * test(ui): introduce first typescript test * test(ui/e2e): add ci job * chore: update gitignore to ignore test outputs * feat(inmem): in memory influxdb * test(e2e): adding pinger that checks if influxdb is alive * hackathon * hack * hack * hack * hack * Revert "feat(inmem): in memory influxdb" This reverts commit 30ddf032003e704643b07ce80df61c3299ea7295. * hack * hack * hack * hack * hack * hack * hack * hack * hack * hack * hack * hack * hack * chore: lint ignore node_modules * hack * hack * hack * add user and flush * hack * remove unused vars * hack * hack * ci(circle): prefix e2e artifacts * change test to testid * update cypress * moar testid * fix npm warnings * remove absolte path * chore(ci): remove /home/circleci proto mkdir hack * wip: crud resources e2e * fix(inmem): use inmem kv store services * test(dashboard): add first dashboard crud tests * hack * undo hack * fix: use response from setup for orgID * chore: wip * add convenience getByTitle function * test(e2e): ui can create orgs * test(e2e): add test for org deletion and update * test(e2e): introduce task creation test * test(e2e): create and update of buckets on org view * chore: move types to declaration file * chore: use route fixture in dashboard tests * chore(ci): hack back * test(ui): update snapshots * chore: package-lock * chore: remove macros * fix: launcher rebase issues * fix: compile errors * fix: compile errors * feat(cmd/influxdb): add explicit testing, asset-path, and store flags Co-authored-by: Andrew Watkins <watts@influxdb.com> * fix(cmd/influxd): set default HTTP handler and flags Co-authored-by: Andrew Watkins <watts@influxdb.com> * build(Makefile): add run-e2e and PHONY * feat(kv:inmem:bolt): implement user service in a kv * refactor(kv): use consistent func receiver name * feat(kv): add initial basic auth service * refactor(passwords): move auth interface into own file * refactor(passwords): rename basic auth files to passwords * refactor(passwords): rename from BasicAuth to Passwords * refactor(kv): copy bolt user test into kv Co-authored-by: Michael Desa <mjdesa@gmail.com> * feat(kv): add inmem testing to kv store * fix(kv): remove extra user index initialization * feat(kv): attempt at making errors nice * fix(http): return not found error if filter is invalid * fix(http): s/platform/influxdb/ for user service * fix(http): s/platform/influxdb/ for user service * feat(kv): initial port of telegraf configs to kv * feat(kv): initial port of scrapers in bolt to kv * feat(kv): first pass at migrating bolt org service to kv * feat(kv): first pass at bucket service * feat(kv): first pass at migrating kvlog to kv package * feat(kv): add resource op logs * feat(kv): first pass at user resource mapping migration * feat(kv): add urm usage to bucket and org services * feat(kv): first pass at kv authz service * feat(kv): add cascading auth delete for users * feat(kv): first pass d authorizer.OrganizationService in kv * feat(cmd/influxd/launcher): user kv services where appropriate * fix(kv): initialize authorizations * fix(influxdb): use same buckets while slowly migrating stuff * fix(kv): make staticcheck pass * feat(kv): add dashboards to kv review: make suggestions from pr review fix: use common bucket names for bolt/kv stores * test(kv): add complete password test coverage * chore(kv): fixes for staticcheck * feat(kv): implement labels generically on kv * feat(kv): implement macro service * feat(kv): add source service * feat(kv): add session service * feat(kv): add kv secret service * refactor(kv): update telegraf and urm with error messages * feat(kv): add lookup service * feat(kv): add kv onboarding service * refactor(kv): update telegraf to avoid repetition * feat(cmd/influxd): use kv lookup service * feat(kv): add telegraf to lookup service * feat(cmd/influxd): use kv telegraf service * feat(kv): update scraper error messaging * feat(cmd/influxd): add kv scraper * feat(kv): add inmem backend tests * refactor(kv): copy paste errors * refactor(kv): add code to password errors * fix(testing): update error messages for incorrect passwords * feat(kv): rename macro to variable * refactor(kv): auth/bucket/org/user unique checks return errors now * feat(inmem): add way to get all bucket names from store * feat(inmem): Buckets to return slice of bytes rather than strings * feat(inmem): add locks around Buckets to avoid races * feat(cmd/influx): check for unauthorized error in wrapCheckSetup * chore(e2e): add video and screenshot artifcats to gitignore * docs(ci): add build instructions for e2e tests * feat(kv): add id lookup for authorized resources
2019-02-19 23:47:19 +00:00
const ErrTelegrafConfigNotFound = "telegraf configuration not found"
// ops for buckets error and buckets op logs.
var (
OpFindTelegrafConfigByID = "FindTelegrafConfigByID"
OpFindTelegrafConfigs = "FindTelegrafConfigs"
OpCreateTelegrafConfig = "CreateTelegrafConfig"
OpUpdateTelegrafConfig = "UpdateTelegrafConfig"
OpDeleteTelegrafConfig = "DeleteTelegrafConfig"
)
2018-10-09 00:45:42 +00:00
// TelegrafConfigStore represents a service for managing telegraf config data.
type TelegrafConfigStore interface {
2018-10-15 19:17:01 +00:00
// UserResourceMappingService must be part of all TelegrafConfigStore service,
// for create, search, delete.
UserResourceMappingService
2018-10-09 00:45:42 +00:00
// FindTelegrafConfigByID returns a single telegraf config by ID.
FindTelegrafConfigByID(ctx context.Context, id ID) (*TelegrafConfig, error)
// FindTelegrafConfigs returns a list of telegraf configs that match filter and the total count of matching telegraf configs.
// Additional options provide pagination & sorting.
FindTelegrafConfigs(ctx context.Context, filter TelegrafConfigFilter, opt ...FindOptions) ([]*TelegrafConfig, int, error)
2018-10-09 00:45:42 +00:00
2018-10-16 00:38:36 +00:00
// CreateTelegrafConfig creates a new telegraf config and sets b.ID with the new identifier.
2018-12-21 16:05:55 +00:00
CreateTelegrafConfig(ctx context.Context, tc *TelegrafConfig, userID ID) error
2018-10-09 00:45:42 +00:00
// UpdateTelegrafConfig updates a single telegraf config.
// Returns the new telegraf config after update.
2018-12-21 16:05:55 +00:00
UpdateTelegrafConfig(ctx context.Context, id ID, tc *TelegrafConfig, userID ID) (*TelegrafConfig, error)
2018-10-09 00:45:42 +00:00
// DeleteTelegrafConfig removes a telegraf config by ID.
DeleteTelegrafConfig(ctx context.Context, id ID) error
}
// TelegrafConfigFilter represents a set of filter that restrict the returned telegraf configs.
type TelegrafConfigFilter struct {
2019-04-15 19:25:48 +00:00
OrgID *ID
Organization *string
UserResourceMappingFilter
}
2018-10-09 00:45:42 +00:00
// TelegrafConfig stores telegraf config for one telegraf instance.
type TelegrafConfig struct {
2019-04-15 19:25:48 +00:00
ID ID
OrgID ID
Name string
Description string
2018-10-16 00:38:36 +00:00
Agent TelegrafAgentConfig
Plugins []TelegrafPlugin
}
2018-10-17 19:51:35 +00:00
// TOML returns the telegraf toml config string.
func (tc TelegrafConfig) TOML() string {
plugins := ""
for _, p := range tc.Plugins {
plugins += p.Config.TOML()
}
interval := time.Duration(tc.Agent.Interval * 1000000)
return fmt.Sprintf(`# Configuration for telegraf agent
[agent]
## Default data collection interval for all inputs
interval = "%s"
## Rounds collection interval to 'interval'
## ie, if interval="10s" then always collect on :00, :10, :20, etc.
round_interval = true
## Telegraf will send metrics to outputs in batches of at most
## metric_batch_size metrics.
## This controls the size of writes that Telegraf sends to output plugins.
metric_batch_size = 1000
## For failed writes, telegraf will cache metric_buffer_limit metrics for each
## output, and will flush this buffer on a successful write. Oldest metrics
## are dropped first when this buffer fills.
## This buffer only fills when writes fail to output plugin(s).
metric_buffer_limit = 10000
## Collection jitter is used to jitter the collection by a random amount.
## Each plugin will sleep for a random time within jitter before collecting.
## This can be used to avoid many plugins querying things like sysfs at the
## same time, which can have a measurable effect on the system.
collection_jitter = "0s"
## Default flushing interval for all outputs. Maximum flush_interval will be
## flush_interval + flush_jitter
flush_interval = "10s"
## Jitter the flush interval by a random amount. This is primarily to avoid
## large write spikes for users running a large number of telegraf instances.
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
flush_jitter = "0s"
## By default or when set to "0s", precision will be set to the same
## timestamp order as the collection interval, with the maximum being 1s.
## ie, when interval = "10s", precision will be "1s"
## when interval = "250ms", precision will be "1ms"
## Precision will NOT be used for service inputs. It is up to each individual
## service input to set the timestamp at the appropriate precision.
## Valid time units are "ns", "us" (or "µs"), "ms", "s".
precision = ""
## Logging configuration:
## Run telegraf with debug log messages.
debug = false
## Run telegraf in quiet mode (error log messages only).
quiet = false
## Specify the log file name. The empty string means to log to stderr.
logfile = ""
## Override default hostname, if empty use os.Hostname()
hostname = ""
## If set to true, do no set the "host" tag in the telegraf agent.
omit_hostname = false
%s`, interval.String(), plugins)
}
2018-10-16 00:38:36 +00:00
// telegrafConfigEncode is the helper struct for json encoding.
type telegrafConfigEncode struct {
ID *ID `json:"id"`
OrgID *ID `json:"orgID,omitempty"`
2019-04-15 19:25:48 +00:00
Name string `json:"name"`
Description string `json:"description"`
2018-10-16 00:38:36 +00:00
Agent TelegrafAgentConfig `json:"agent"`
Plugins []telegrafPluginEncode `json:"plugins"`
}
// telegrafPluginEncode is the helper struct for json encoding.
type telegrafPluginEncode struct {
// Name of the telegraf plugin, exp "docker"
Name string `json:"name"`
Type plugins.Type `json:"type"`
Comment string `json:"comment,omitempty"`
Config plugins.Config `json:"config,omitempty"`
2018-10-16 00:38:36 +00:00
}
// telegrafConfigDecode is the helper struct for json decoding.
type telegrafConfigDecode struct {
ID *ID `json:"id,omitempty"`
OrganizationID *ID `json:"organizationID,omitempty"`
OrgID *ID `json:"orgID,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
2018-10-09 00:45:42 +00:00
Agent TelegrafAgentConfig `json:"agent"`
2018-10-16 00:38:36 +00:00
Plugins []telegrafPluginDecode `json:"plugins"`
}
// telegrafPluginDecode is the helper struct for json decoding.
type telegrafPluginDecode struct {
// Name of the telegraf plugin, exp "docker"
Name string `json:"name"`
Type plugins.Type `json:"type"`
Comment string `json:"comment,omitempty"`
Config json.RawMessage `json:"config,omitempty"`
2018-10-09 00:45:42 +00:00
}
// TelegrafPlugin is the general wrapper of the telegraf plugin config
type TelegrafPlugin struct {
Comment string `json:"comment,omitempty"`
Config plugins.Config `json:"config,omitempty"`
2018-10-09 00:45:42 +00:00
}
// TelegrafAgentConfig is based telegraf/internal/config AgentConfig.
type TelegrafAgentConfig struct {
2018-10-16 00:38:36 +00:00
// Interval at which to gather information in miliseconds.
Interval int64 `json:"collectionInterval"`
2018-10-09 00:45:42 +00:00
}
2018-10-16 00:38:36 +00:00
// errors
2018-10-09 00:45:42 +00:00
const (
2018-10-16 00:38:36 +00:00
ErrTelegrafPluginNameUnmatch = "the telegraf plugin is name %s doesn't match the config %s"
ErrNoTelegrafPlugins = "there is no telegraf plugin in the config"
ErrUnsupportTelegrafPluginType = "unsupported telegraf plugin type %s"
ErrUnsupportTelegrafPluginName = "unsupported telegraf plugin %s, type %s"
2018-10-09 00:45:42 +00:00
)
2018-10-16 00:38:36 +00:00
// MarshalJSON implement the json.Marshaler interface.
func (tc *TelegrafConfig) MarshalJSON() ([]byte, error) {
tce := &telegrafConfigEncode{
2019-04-15 19:25:48 +00:00
Name: tc.Name,
Description: tc.Description,
Agent: tc.Agent,
Plugins: make([]telegrafPluginEncode, len(tc.Plugins)),
2018-10-16 00:38:36 +00:00
}
if tc.OrgID != 0 {
tce.OrgID = &tc.OrgID
}
if tc.ID != 0 {
tce.ID = &tc.ID
}
2018-10-16 00:38:36 +00:00
for k, p := range tc.Plugins {
tce.Plugins[k] = telegrafPluginEncode{
Name: p.Config.PluginName(),
Type: p.Config.Type(),
Comment: p.Comment,
Config: p.Config,
}
}
return json.Marshal(tce)
}
2018-10-17 19:51:35 +00:00
// UnmarshalTOML implements toml.Unmarshaler interface.
func (tc *TelegrafConfig) UnmarshalTOML(data interface{}) error {
dataOk, ok := data.(map[string]interface{})
if !ok {
return errors.New("blank string")
}
agent, ok := dataOk["agent"].(map[string]interface{})
if !ok {
return errors.New("agent is missing")
}
intervalStr, ok := agent["interval"].(string)
if !ok {
return errors.New("agent interval is not string")
}
interval, err := time.ParseDuration(intervalStr)
if err != nil {
return err
}
tc.Agent = TelegrafAgentConfig{
Interval: interval.Nanoseconds() / 1000000,
}
for tp, ps := range dataOk {
if tp == "agent" {
continue
}
plugins, ok := ps.(map[string]interface{})
if !ok {
return &Error{
Msg: "bad plugin type",
}
}
for name, configDataArray := range plugins {
if configDataArray == nil {
if err := tc.parseTOMLPluginConfig(tp, name, configDataArray); err != nil {
return err
}
continue
}
for _, configData := range configDataArray.([]map[string]interface{}) {
if err := tc.parseTOMLPluginConfig(tp, name, configData); err != nil {
return err
}
}
}
}
return nil
}
func (tc *TelegrafConfig) parseTOMLPluginConfig(typ, name string, configData interface{}) error {
var ok bool
var tpFn func() plugins.Config
2018-10-17 19:51:35 +00:00
switch typ {
case "inputs":
tpFn, ok = availableInputPlugins[name]
2018-10-17 19:51:35 +00:00
case "outputs":
tpFn, ok = availableOutputPlugins[name]
2018-10-17 19:51:35 +00:00
default:
return &Error{
Msg: fmt.Sprintf(ErrUnsupportTelegrafPluginType, typ),
}
}
2018-10-17 19:51:35 +00:00
if !ok {
return &Error{
Msg: fmt.Sprintf(ErrUnsupportTelegrafPluginName, name, typ),
}
}
p := tpFn()
2018-10-17 19:51:35 +00:00
if err := p.UnmarshalTOML(configData); err != nil {
return err
}
tc.Plugins = append(tc.Plugins, TelegrafPlugin{
Config: p,
})
return nil
}
2018-10-16 00:38:36 +00:00
// UnmarshalJSON implement the json.Unmarshaler interface.
func (tc *TelegrafConfig) UnmarshalJSON(b []byte) error {
tcd := new(telegrafConfigDecode)
if err := json.Unmarshal(b, tcd); err != nil {
return err
}
*tc = TelegrafConfig{
2019-04-15 19:25:48 +00:00
Name: tcd.Name,
Description: tcd.Description,
Agent: tcd.Agent,
Plugins: make([]TelegrafPlugin, len(tcd.Plugins)),
2018-10-16 00:38:36 +00:00
}
if tcd.ID != nil {
tc.ID = *tcd.ID
}
if orgID := tcd.OrgID; orgID != nil && orgID.Valid() {
tc.OrgID = *orgID
} else if tcd.OrganizationID != nil {
tc.OrgID = *tcd.OrganizationID
}
2018-10-16 00:38:36 +00:00
return decodePluginRaw(tcd, tc)
}
func decodePluginRaw(tcd *telegrafConfigDecode, tc *TelegrafConfig) (err error) {
op := "unmarshal telegraf config raw plugin"
for k, pr := range tcd.Plugins {
var tpFn func() plugins.Config
var config plugins.Config
2018-10-16 00:38:36 +00:00
var ok bool
switch pr.Type {
case plugins.Input:
tpFn, ok = availableInputPlugins[pr.Name]
2018-10-16 00:38:36 +00:00
case plugins.Output:
tpFn, ok = availableOutputPlugins[pr.Name]
2018-10-16 00:38:36 +00:00
default:
return &Error{
Code: EInvalid,
Msg: fmt.Sprintf(ErrUnsupportTelegrafPluginType, pr.Type),
Op: op,
}
}
if ok {
config = tpFn()
// if pr.Config if empty, make it a blank obj,
// so it will still go to the unmarshalling process to validate.
if len(string(pr.Config)) == 0 {
pr.Config = []byte("{}")
}
2018-10-16 00:38:36 +00:00
if err = json.Unmarshal(pr.Config, config); err != nil {
return &Error{
Code: EInvalid,
Err: err,
Op: op,
}
}
tc.Plugins[k] = TelegrafPlugin{
Comment: pr.Comment,
Config: config,
}
continue
}
return &Error{
Code: EInvalid,
Op: op,
Msg: fmt.Sprintf(ErrUnsupportTelegrafPluginName, pr.Name, pr.Type),
}
}
return nil
}
var availableInputPlugins = map[string](func() plugins.Config){
"cpu": func() plugins.Config { return &inputs.CPUStats{} },
"disk": func() plugins.Config { return &inputs.DiskStats{} },
"diskio": func() plugins.Config { return &inputs.DiskIO{} },
"docker": func() plugins.Config { return &inputs.Docker{} },
"file": func() plugins.Config { return &inputs.File{} },
"kernel": func() plugins.Config { return &inputs.Kernel{} },
"kubernetes": func() plugins.Config { return &inputs.Kubernetes{} },
"logparser": func() plugins.Config { return &inputs.LogParserPlugin{} },
"mem": func() plugins.Config { return &inputs.MemStats{} },
"net_response": func() plugins.Config { return &inputs.NetResponse{} },
"net": func() plugins.Config { return &inputs.NetIOStats{} },
"nginx": func() plugins.Config { return &inputs.Nginx{} },
"processes": func() plugins.Config { return &inputs.Processes{} },
"procstat": func() plugins.Config { return &inputs.Procstat{} },
"prometheus": func() plugins.Config { return &inputs.Prometheus{} },
"redis": func() plugins.Config { return &inputs.Redis{} },
"swap": func() plugins.Config { return &inputs.SwapStats{} },
"syslog": func() plugins.Config { return &inputs.Syslog{} },
"system": func() plugins.Config { return &inputs.SystemStats{} },
"tail": func() plugins.Config { return &inputs.Tail{} },
2018-10-16 00:38:36 +00:00
}
var availableOutputPlugins = map[string](func() plugins.Config){
"file": func() plugins.Config { return &outputs.File{} },
"influxdb_v2": func() plugins.Config { return &outputs.InfluxDBV2{} },
2018-10-16 00:38:36 +00:00
}