github.com/influxdata/telegraf@v1.30.3/cmd/telegraf/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/awnumar/memguard"
    12  	"github.com/urfave/cli/v2"
    13  
    14  	"github.com/influxdata/telegraf/config"
    15  	"github.com/influxdata/telegraf/internal"
    16  	"github.com/influxdata/telegraf/internal/goplugin"
    17  	"github.com/influxdata/telegraf/logger"
    18  	_ "github.com/influxdata/telegraf/plugins/aggregators/all"
    19  	"github.com/influxdata/telegraf/plugins/inputs"
    20  	_ "github.com/influxdata/telegraf/plugins/inputs/all"
    21  	"github.com/influxdata/telegraf/plugins/outputs"
    22  	_ "github.com/influxdata/telegraf/plugins/outputs/all"
    23  	_ "github.com/influxdata/telegraf/plugins/parsers/all"
    24  	_ "github.com/influxdata/telegraf/plugins/processors/all"
    25  	_ "github.com/influxdata/telegraf/plugins/secretstores/all"
    26  	_ "github.com/influxdata/telegraf/plugins/serializers/all"
    27  )
    28  
    29  type TelegrafConfig interface {
    30  	CollectDeprecationInfos([]string, []string, []string, []string) map[string][]config.PluginDeprecationInfo
    31  	PrintDeprecationList([]config.PluginDeprecationInfo)
    32  }
    33  
    34  type Filters struct {
    35  	section     []string
    36  	input       []string
    37  	output      []string
    38  	aggregator  []string
    39  	processor   []string
    40  	secretstore []string
    41  }
    42  
    43  func appendFilter(a, b string) string {
    44  	if a != "" && b != "" {
    45  		return fmt.Sprintf("%s:%s", a, b)
    46  	}
    47  	if a != "" {
    48  		return a
    49  	}
    50  	return b
    51  }
    52  
    53  func processFilterFlags(ctx *cli.Context) Filters {
    54  	var section, input, output, aggregator, processor, secretstore string
    55  
    56  	// Support defining filters before and after the command
    57  	// The old style was:
    58  	// ./telegraf --section-filter inputs --input-filter cpu config >test.conf
    59  	// The new style is:
    60  	// ./telegraf config --section-filter inputs --input-filter cpu >test.conf
    61  	// To support the old style, check if the parent context has the filter flags defined
    62  	if len(ctx.Lineage()) >= 2 {
    63  		parent := ctx.Lineage()[1] // ancestor contexts in order from child to parent
    64  		section = parent.String("section-filter")
    65  		input = parent.String("input-filter")
    66  		output = parent.String("output-filter")
    67  		aggregator = parent.String("aggregator-filter")
    68  		processor = parent.String("processor-filter")
    69  		secretstore = parent.String("secretstore-filter")
    70  	}
    71  
    72  	// If both the parent and command filters are defined, append them together
    73  	section = appendFilter(section, ctx.String("section-filter"))
    74  	input = appendFilter(input, ctx.String("input-filter"))
    75  	output = appendFilter(output, ctx.String("output-filter"))
    76  	aggregator = appendFilter(aggregator, ctx.String("aggregator-filter"))
    77  	processor = appendFilter(processor, ctx.String("processor-filter"))
    78  	secretstore = appendFilter(secretstore, ctx.String("secretstore-filter"))
    79  
    80  	sectionFilters := deleteEmpty(strings.Split(section, ":"))
    81  	inputFilters := deleteEmpty(strings.Split(input, ":"))
    82  	outputFilters := deleteEmpty(strings.Split(output, ":"))
    83  	aggregatorFilters := deleteEmpty(strings.Split(aggregator, ":"))
    84  	processorFilters := deleteEmpty(strings.Split(processor, ":"))
    85  	secretstoreFilters := deleteEmpty(strings.Split(secretstore, ":"))
    86  	return Filters{sectionFilters, inputFilters, outputFilters, aggregatorFilters, processorFilters, secretstoreFilters}
    87  }
    88  
    89  func deleteEmpty(s []string) []string {
    90  	var r []string
    91  	for _, str := range s {
    92  		if str != "" {
    93  			r = append(r, str)
    94  		}
    95  	}
    96  	return r
    97  }
    98  
    99  // runApp defines all the subcommands and flags for Telegraf
   100  // this abstraction is used for testing, so outputBuffer and args can be changed
   101  func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfig, m App) error {
   102  	pluginFilterFlags := []cli.Flag{
   103  		&cli.StringFlag{
   104  			Name: "section-filter",
   105  			Usage: "filter the sections to print, separator is ':'. " +
   106  				"Valid values are 'agent', 'global_tags', 'outputs', 'processors', 'aggregators' and 'inputs'",
   107  		},
   108  		&cli.StringFlag{
   109  			Name:  "input-filter",
   110  			Usage: "filter the inputs to enable, separator is ':'",
   111  		},
   112  		&cli.StringFlag{
   113  			Name:  "output-filter",
   114  			Usage: "filter the outputs to enable, separator is ':'",
   115  		},
   116  		&cli.StringFlag{
   117  			Name:  "aggregator-filter",
   118  			Usage: "filter the aggregators to enable, separator is ':'",
   119  		},
   120  		&cli.StringFlag{
   121  			Name:  "processor-filter",
   122  			Usage: "filter the processors to enable, separator is ':'",
   123  		},
   124  		&cli.StringFlag{
   125  			Name:  "secretstore-filter",
   126  			Usage: "filter the secret-stores to enable, separator is ':'",
   127  		},
   128  	}
   129  
   130  	extraFlags := append(pluginFilterFlags, cliFlags()...)
   131  
   132  	// This function is used when Telegraf is run with only flags
   133  	action := func(cCtx *cli.Context) error {
   134  		// We do not expect any arguments this is likely a misspelling of
   135  		// a command...
   136  		if cCtx.NArg() > 0 {
   137  			return fmt.Errorf("unknown command %q", cCtx.Args().First())
   138  		}
   139  
   140  		err := logger.SetupLogging(logger.LogConfig{})
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		// Deprecated: Use execd instead
   146  		// Load external plugins, if requested.
   147  		if cCtx.String("plugin-directory") != "" {
   148  			log.Printf("I! Loading external plugins from: %s", cCtx.String("plugin-directory"))
   149  			if err := goplugin.LoadExternalPlugins(cCtx.String("plugin-directory")); err != nil {
   150  				return err
   151  			}
   152  		}
   153  
   154  		// switch for flags which just do something and exit immediately
   155  		switch {
   156  		// print available input plugins
   157  		case cCtx.Bool("deprecation-list"):
   158  			filters := processFilterFlags(cCtx)
   159  			infos := c.CollectDeprecationInfos(
   160  				filters.input, filters.output, filters.aggregator, filters.processor,
   161  			)
   162  			outputBuffer.Write([]byte("Deprecated Input Plugins:\n"))
   163  			c.PrintDeprecationList(infos["inputs"])
   164  			outputBuffer.Write([]byte("Deprecated Output Plugins:\n"))
   165  			c.PrintDeprecationList(infos["outputs"])
   166  			outputBuffer.Write([]byte("Deprecated Processor Plugins:\n"))
   167  			c.PrintDeprecationList(infos["processors"])
   168  			outputBuffer.Write([]byte("Deprecated Aggregator Plugins:\n"))
   169  			c.PrintDeprecationList(infos["aggregators"])
   170  			return nil
   171  		// print available output plugins
   172  		case cCtx.Bool("output-list"):
   173  			outputBuffer.Write([]byte("DEPRECATED: use telegraf plugins outputs\n"))
   174  			outputBuffer.Write([]byte("Available Output Plugins:\n"))
   175  			names := make([]string, 0, len(outputs.Outputs))
   176  			for k := range outputs.Outputs {
   177  				names = append(names, k)
   178  			}
   179  			sort.Strings(names)
   180  			for _, k := range names {
   181  				fmt.Fprintf(outputBuffer, "  %s\n", k)
   182  			}
   183  			return nil
   184  		// print available input plugins
   185  		case cCtx.Bool("input-list"):
   186  			outputBuffer.Write([]byte("DEPRECATED: use telegraf plugins inputs\n"))
   187  			outputBuffer.Write([]byte("Available Input Plugins:\n"))
   188  			names := make([]string, 0, len(inputs.Inputs))
   189  			for k := range inputs.Inputs {
   190  				names = append(names, k)
   191  			}
   192  			sort.Strings(names)
   193  			for _, k := range names {
   194  				fmt.Fprintf(outputBuffer, "  %s\n", k)
   195  			}
   196  			return nil
   197  		// print usage for a plugin, ie, 'telegraf --usage mysql'
   198  		case cCtx.String("usage") != "":
   199  			err := PrintInputConfig(cCtx.String("usage"), outputBuffer)
   200  			err2 := PrintOutputConfig(cCtx.String("usage"), outputBuffer)
   201  			if err != nil && err2 != nil {
   202  				return fmt.Errorf("%w and %w", err, err2)
   203  			}
   204  			return nil
   205  		// DEPRECATED
   206  		case cCtx.Bool("version"):
   207  			fmt.Fprintf(outputBuffer, "%s\n", internal.FormatFullVersion())
   208  			return nil
   209  		// DEPRECATED
   210  		case cCtx.Bool("sample-config"):
   211  			filters := processFilterFlags(cCtx)
   212  
   213  			printSampleConfig(outputBuffer, filters)
   214  			return nil
   215  		}
   216  
   217  		if cCtx.String("pprof-addr") != "" {
   218  			pprof.Start(cCtx.String("pprof-addr"))
   219  		}
   220  
   221  		filters := processFilterFlags(cCtx)
   222  
   223  		g := GlobalFlags{
   224  			config:         cCtx.StringSlice("config"),
   225  			configDir:      cCtx.StringSlice("config-directory"),
   226  			testWait:       cCtx.Int("test-wait"),
   227  			watchConfig:    cCtx.String("watch-config"),
   228  			pidFile:        cCtx.String("pidfile"),
   229  			plugindDir:     cCtx.String("plugin-directory"),
   230  			password:       cCtx.String("password"),
   231  			oldEnvBehavior: cCtx.Bool("old-env-behavior"),
   232  			test:           cCtx.Bool("test"),
   233  			debug:          cCtx.Bool("debug"),
   234  			once:           cCtx.Bool("once"),
   235  			quiet:          cCtx.Bool("quiet"),
   236  			unprotected:    cCtx.Bool("unprotected"),
   237  		}
   238  
   239  		w := WindowFlags{
   240  			service:             cCtx.String("service"),
   241  			serviceName:         cCtx.String("service-name"),
   242  			serviceDisplayName:  cCtx.String("service-display-name"),
   243  			serviceRestartDelay: cCtx.String("service-restart-delay"),
   244  			serviceAutoRestart:  cCtx.Bool("service-auto-restart"),
   245  			console:             cCtx.Bool("console"),
   246  		}
   247  
   248  		m.Init(pprof.ErrChan(), filters, g, w)
   249  		return m.Run()
   250  	}
   251  
   252  	commands := append(
   253  		getConfigCommands(pluginFilterFlags, outputBuffer),
   254  		getSecretStoreCommands(m)...,
   255  	)
   256  	commands = append(commands, getPluginCommands(outputBuffer)...)
   257  
   258  	app := &cli.App{
   259  		Name:   "Telegraf",
   260  		Usage:  "The plugin-driven server agent for collecting & reporting metrics.",
   261  		Writer: outputBuffer,
   262  		Flags: append(
   263  			[]cli.Flag{
   264  				// String slice flags
   265  				&cli.StringSliceFlag{
   266  					Name:  "config",
   267  					Usage: "configuration file to load",
   268  				},
   269  				&cli.StringSliceFlag{
   270  					Name:  "config-directory",
   271  					Usage: "directory containing additional *.conf files",
   272  				},
   273  				// Int flags
   274  				&cli.IntFlag{
   275  					Name:  "test-wait",
   276  					Usage: "wait up to this many seconds for service inputs to complete in test mode",
   277  				},
   278  				//
   279  				// String flags
   280  				&cli.StringFlag{
   281  					Name:  "usage",
   282  					Usage: "print usage for a plugin, ie, 'telegraf --usage mysql'",
   283  				},
   284  				&cli.StringFlag{
   285  					Name:  "pprof-addr",
   286  					Usage: "pprof host/IP and port to listen on (e.g. 'localhost:6060')",
   287  				},
   288  				&cli.StringFlag{
   289  					Name:  "watch-config",
   290  					Usage: "monitoring config changes [notify, poll] of --config and --config-directory options",
   291  				},
   292  				&cli.StringFlag{
   293  					Name:  "pidfile",
   294  					Usage: "file to write our pid to",
   295  				},
   296  				&cli.StringFlag{
   297  					Name:  "password",
   298  					Usage: "password to unlock secret-stores",
   299  				},
   300  				//
   301  				// Bool flags
   302  				&cli.BoolFlag{
   303  					Name:  "old-env-behavior",
   304  					Usage: "switch back to pre v1.27 environment replacement behavior",
   305  				},
   306  				&cli.BoolFlag{
   307  					Name:  "once",
   308  					Usage: "run one gather and exit",
   309  				},
   310  				&cli.BoolFlag{
   311  					Name:  "debug",
   312  					Usage: "turn on debug logging",
   313  				},
   314  				&cli.BoolFlag{
   315  					Name:  "quiet",
   316  					Usage: "run in quiet mode",
   317  				},
   318  				&cli.BoolFlag{
   319  					Name:  "unprotected",
   320  					Usage: "do not protect secrets in memory",
   321  				},
   322  				&cli.BoolFlag{
   323  					Name: "test",
   324  					Usage: "enable test mode: gather metrics, print them out, and exit. " +
   325  						"Note: Test mode only runs inputs, not processors, aggregators, or outputs",
   326  				},
   327  				// TODO: Change "deprecation-list, input-list, output-list" flags to become a subcommand "list" that takes
   328  				// "input,output,aggregator,processor, deprecated" as parameters
   329  				&cli.BoolFlag{
   330  					Name:  "deprecation-list",
   331  					Usage: "print all deprecated plugins or plugin options",
   332  				},
   333  				&cli.BoolFlag{
   334  					Name:  "input-list",
   335  					Usage: "print available input plugins",
   336  				},
   337  				&cli.BoolFlag{
   338  					Name:  "output-list",
   339  					Usage: "print available output plugins",
   340  				},
   341  				//
   342  				// !!! The following flags are DEPRECATED !!!
   343  				// Already covered with the subcommand `./telegraf version`
   344  				&cli.BoolFlag{
   345  					Name:  "version",
   346  					Usage: "DEPRECATED: display the version and exit",
   347  				},
   348  				// Already covered with the subcommand `./telegraf config`
   349  				&cli.BoolFlag{
   350  					Name:  "sample-config",
   351  					Usage: "DEPRECATED: print out full sample configuration",
   352  				},
   353  				// Using execd plugin to add external plugins is preferred (less size impact, easier for end user)
   354  				&cli.StringFlag{
   355  					Name:  "plugin-directory",
   356  					Usage: "DEPRECATED: path to directory containing external plugins",
   357  				},
   358  				// !!!
   359  			}, extraFlags...),
   360  		Action: action,
   361  		Commands: append([]*cli.Command{
   362  			{
   363  				Name:  "version",
   364  				Usage: "print current version to stdout",
   365  				Action: func(*cli.Context) error {
   366  					fmt.Fprintf(outputBuffer, "%s\n", internal.FormatFullVersion())
   367  					return nil
   368  				},
   369  			},
   370  		}, commands...),
   371  	}
   372  
   373  	// Make sure we safely erase secrets
   374  	defer memguard.Purge()
   375  
   376  	if err := app.Run(args); err != nil {
   377  		log.Printf("E! %s", err)
   378  		return err
   379  	}
   380  	return nil
   381  }
   382  
   383  func main() {
   384  	// #13481: disables gh:99designs/keyring kwallet.go from connecting to dbus
   385  	os.Setenv("DISABLE_KWALLET", "1")
   386  
   387  	agent := Telegraf{}
   388  	pprof := NewPprofServer()
   389  	c := config.NewConfig()
   390  	if err := runApp(os.Args, os.Stdout, pprof, c, &agent); err != nil {
   391  		os.Exit(1)
   392  	}
   393  }