diff --git a/.gitignore b/.gitignore
index a30249f..d205738 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
# Vagrant
.vagrant*/
+Vagrantfile
# Binaries for programs and plugins
*.exe
@@ -33,7 +34,3 @@ internal/watcher/testdata/test.log
# goreleaser files
dist/
-# codecov.io repo token and report
-.codecov
-coverage.txt
-coverage.xml
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 137125d..2a27c89 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -15,7 +15,7 @@ builds:
- linux
# - darwin
# - windows
- # - freebsd
+ - freebsd
# - solaris
goarch:
@@ -34,7 +34,6 @@ archive:
replacements:
amd64: 64-bit
- darwin: macOS
format_overrides:
- goos: windows
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21d8e3d..760fc85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# v0.3.1
+
+* add: freebsd to release
+
# v0.3.0
* add: example of apache access log latency histograms
diff --git a/coverage.html b/coverage.html
deleted file mode 100644
index 116e092..0000000
--- a/coverage.html
+++ /dev/null
@@ -1,2396 +0,0 @@
-
-
-
-
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package cmd
-
-import (
- "fmt"
- stdlog "log"
- "os"
- "time"
-
- "github.com/circonus-labs/circonus-logwatch/internal/agent"
- "github.com/circonus-labs/circonus-logwatch/internal/config"
- "github.com/circonus-labs/circonus-logwatch/internal/config/defaults"
- "github.com/circonus-labs/circonus-logwatch/internal/release"
- "github.com/pkg/errors"
- "github.com/rs/zerolog"
- "github.com/rs/zerolog/log"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
-)
-
-var cfgFile string
-
-// RootCmd represents the base command when called without any subcommands
-var RootCmd = &cobra.Command{
- Use: release.NAME,
- Short: "A small utility to send metrics extracted from log files to Circonus.",
- Long: `Send metrics extracted from log files to Circonus.
-
-Not all useful metrics can be sent directly to a centralized system for analysis
-and alerting. Often, there are valuable metrics sequesterd in system and application
-log files. These logs are not always in a common, easily parsable format.
-
-Using named regular expressions and templates, this utility offers the ability
-to extract these metrics from the logs and send them directly to the Circonus
-system using one of several methods.
-
-See https://github.com/circonus-labs/circonus-logwatch/etc for example configurations.
-`,
- PersistentPreRunE: initLogging,
- Run: func(cmd *cobra.Command, args []string) {
- //
- // show version and exit
- //
- if viper.GetBool(config.KeyShowVersion) {
- fmt.Printf("%s v%s - commit: %s, date: %s, tag: %s\n", release.NAME, release.VERSION, release.COMMIT, release.DATE, release.TAG)
- return
- }
-
- //
- // show configuration and exit
- //
- if viper.GetString(config.KeyShowConfig) != "" {
- if err := config.ShowConfig(os.Stdout); err != nil {
- log.Fatal().Err(err).Msg("show-config")
- }
- return
- }
-
- log.Info().
- Int("pid", os.Getpid()).
- Str("name", release.NAME).
- Str("ver", release.VERSION).Msg("Starting")
-
- a, err := agent.New()
- if err != nil {
- log.Fatal().Err(err).Msg("Initializing")
- }
-
- config.StatConfig()
-
- if err := a.Start(); err != nil {
- log.Fatal().Err(err).Msg("Startup")
- }
-
- },
-}
-
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute() {
- if err := RootCmd.Execute(); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-}
-
-func init() {
- zerolog.TimeFieldFormat = time.RFC3339Nano
- zerolog.SetGlobalLevel(zerolog.InfoLevel)
- zlog := zerolog.New(zerolog.SyncWriter(os.Stderr)).With().Timestamp().Logger()
- log.Logger = zlog
-
- stdlog.SetFlags(0)
- stdlog.SetOutput(zlog)
-
- cobra.OnInitialize(initConfig)
-
- desc := func(desc, env string) string {
- return fmt.Sprintf("[ENV: %s] %s", env, desc)
- }
-
- //
- // Basic
- //
- {
- var (
- longOpt = "config"
- shortOpt = "c"
- description = "config file (default is " + defaults.EtcPath + "/" + release.NAME + ".(json|toml|yaml)"
- )
- RootCmd.PersistentFlags().StringVarP(&cfgFile, longOpt, shortOpt, "", description)
- }
-
- {
- const (
- key = config.KeyLogConfDir
- longOpt = "log-conf-dir"
- shortOpt = "l"
- envVar = release.ENVPREFIX + "_PLUGIN_DIR"
- description = "Log configuration directory"
- )
-
- RootCmd.Flags().StringP(longOpt, shortOpt, defaults.LogConfPath, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.LogConfPath)
- }
-
- //
- // Destination for metrics
- //
- {
- const (
- key = config.KeyDestType
- longOpt = "dest"
- envVar = release.ENVPREFIX + "_DESTINATION"
- description = "Destination[agent|check|log|statsd] type for metrics"
- )
-
- RootCmd.Flags().String(longOpt, defaults.DestinationType, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.DestinationType)
- }
- {
- const (
- key = config.KeyDestCfgID
- longOpt = "dest-id"
- envVar = release.ENVPREFIX + "_DEST_ID"
- description = "Destination[statsd|agent] metric group ID"
- )
-
- RootCmd.Flags().String(longOpt, release.NAME, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgCID
- longOpt = "dest-cid"
- envVar = release.ENVPREFIX + "_DEST_CID"
- description = "Destination[check] Check Bundle ID"
- )
-
- RootCmd.Flags().String(longOpt, "", desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgURL
- longOpt = "dest-url"
- envVar = release.ENVPREFIX + "_DEST_URL"
- description = "Destination[check] Check Submission URL"
- )
-
- RootCmd.Flags().String(longOpt, "", desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgPort
- longOpt = "dest-port"
- envVar = release.ENVPREFIX + "_DEST_PORT"
- description = "Destination[agent|statsd] port (agent=2609, statsd=8125)"
- )
-
- RootCmd.Flags().String(longOpt, "", desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgInstanceID
- longOpt = "dest-instance-id"
- envVar = release.ENVPREFIX + "_DEST_INSTANCE_ID"
- description = "Destination[check] Check Instance ID"
- )
-
- RootCmd.Flags().String(longOpt, "", desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgTarget
- longOpt = "dest-target"
- envVar = release.ENVPREFIX + "_DEST_TARGET"
- description = "Destination[check] Check target (default hostname)"
- )
-
- RootCmd.Flags().String(longOpt, "", desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgSearchTag
- longOpt = "dest-tag"
- envVar = release.ENVPREFIX + "_DEST_TAG"
- description = "Destination[check] Check search tag"
- )
-
- RootCmd.Flags().String(longOpt, "", desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
- {
- const (
- key = config.KeyDestCfgStatsdPrefix
- longOpt = "dest-statsd-prefix"
- envVar = release.ENVPREFIX + "_DEST_STATSD_PREFIX"
- description = "Destination[statsd] Prefix prepended to every metric sent to StatsD"
- )
-
- RootCmd.Flags().String(longOpt, defaults.StatsdPrefix, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.StatsdPrefix)
- }
-
- //
- // API
- //
- {
- const (
- key = config.KeyAPITokenKey
- longOpt = "api-key"
- defaultValue = ""
- envVar = release.ENVPREFIX + "_API_KEY"
- description = "Circonus API Token key or 'cosi' to use COSI config"
- )
- RootCmd.Flags().String(longOpt, defaultValue, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
-
- {
- const (
- key = config.KeyAPITokenApp
- longOpt = "api-app"
- envVar = release.ENVPREFIX + "_API_APP"
- description = "Circonus API Token app"
- )
-
- RootCmd.Flags().String(longOpt, defaults.APIApp, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.APIApp)
- }
-
- {
- const (
- key = config.KeyAPIURL
- longOpt = "api-url"
- envVar = release.ENVPREFIX + "_API_URL"
- description = "Circonus API URL"
- )
-
- RootCmd.Flags().String(longOpt, defaults.APIURL, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.APIURL)
- }
-
- {
- const (
- key = config.KeyAPICAFile
- longOpt = "api-ca-file"
- defaultValue = ""
- envVar = release.ENVPREFIX + "_API_CA_FILE"
- description = "Circonus API CA certificate file"
- )
-
- RootCmd.Flags().String(longOpt, defaultValue, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- }
-
- // Miscellenous
-
- {
- const (
- key = config.KeyDebug
- longOpt = "debug"
- shortOpt = "d"
- envVar = release.ENVPREFIX + "_DEBUG"
- description = "Enable debug messages"
- )
-
- RootCmd.Flags().BoolP(longOpt, shortOpt, defaults.Debug, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.Debug)
- }
-
- {
- const (
- key = config.KeyDebugCGM
- longOpt = "debug-cgm"
- defaultValue = false
- envVar = release.ENVPREFIX + "_DEBUG_CGM"
- description = "Enable CGM & API debug messages"
- )
-
- RootCmd.Flags().Bool(longOpt, defaultValue, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaultValue)
- }
-
- {
- const (
- key = config.KeyDebugTail
- longOpt = "debug-tail"
- defaultValue = false
- envVar = release.ENVPREFIX + "_DEBUG_TAIL"
- description = "Enable log tailing messages"
- )
-
- RootCmd.Flags().Bool(longOpt, defaultValue, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaultValue)
- }
-
- {
- const (
- key = config.KeyDebugMetric
- longOpt = "debug-metric"
- defaultValue = false
- envVar = release.ENVPREFIX + "_DEBUG_METRIC"
- description = "Enable metric rule evaluation tracing debug messages"
- )
-
- RootCmd.Flags().Bool(longOpt, defaultValue, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaultValue)
- }
-
- {
- const (
- key = config.KeyAppStatPort
- longOpt = "stat-port"
- envVar = release.ENVPREFIX + "_STAT_PORT"
- description = "Exposes app stats while running"
- )
-
- RootCmd.Flags().String(longOpt, defaults.AppStatPort, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.AppStatPort)
- }
-
- {
- const (
- key = config.KeyLogLevel
- longOpt = "log-level"
- envVar = release.ENVPREFIX + "_LOG_LEVEL"
- description = "Log level [(panic|fatal|error|warn|info|debug|disabled)]"
- )
-
- RootCmd.Flags().String(longOpt, defaults.LogLevel, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.LogLevel)
- }
-
- {
- const (
- key = config.KeyLogPretty
- longOpt = "log-pretty"
- envVar = release.ENVPREFIX + "_LOG_PRETTY"
- description = "Output formatted/colored log lines"
- )
-
- RootCmd.Flags().Bool(longOpt, defaults.LogPretty, desc(description, envVar))
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- viper.BindEnv(key, envVar)
- viper.SetDefault(key, defaults.LogPretty)
- }
-
- {
- const (
- key = config.KeyShowVersion
- longOpt = "version"
- shortOpt = "V"
- defaultValue = false
- description = "Show version and exit"
- )
- RootCmd.Flags().BoolP(longOpt, shortOpt, defaultValue, description)
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- }
-
- {
- const (
- key = config.KeyShowConfig
- longOpt = "show-config"
- description = "Show config (json|toml|yaml) and exit"
- )
-
- RootCmd.Flags().String(longOpt, "", description)
- viper.BindPFlag(key, RootCmd.Flags().Lookup(longOpt))
- }
-
-}
-
-// initConfig reads in config file and ENV variables if set.
-func initConfig() {
- if cfgFile != "" {
- viper.SetConfigFile(cfgFile)
- } else {
- viper.AddConfigPath(defaults.EtcPath)
- viper.AddConfigPath(".")
- viper.SetConfigName(release.NAME)
- }
-
- viper.AutomaticEnv()
-
- if err := viper.ReadInConfig(); err != nil {
- f := viper.ConfigFileUsed()
- if f != "" {
- log.Fatal().Err(err).Str("config_file", f).Msg("Unable to load config file")
- }
- }
-}
-
-// initLogging initializes zerolog
-func initLogging(cmd *cobra.Command, args []string) error {
- //
- // Enable formatted output
- //
- if viper.GetBool(config.KeyLogPretty) {
- log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
- }
-
- //
- // Enable debug logging, if requested
- // otherwise, default to info level and set custom level, if specified
- //
- if viper.GetBool(config.KeyDebug) {
- viper.Set(config.KeyLogLevel, "debug")
- zerolog.SetGlobalLevel(zerolog.DebugLevel)
- log.Debug().Msg("--debug flag, forcing debug log level")
- } else {
- if viper.IsSet(config.KeyLogLevel) {
- level := viper.GetString(config.KeyLogLevel)
-
- switch level {
- case "panic":
- zerolog.SetGlobalLevel(zerolog.PanicLevel)
- case "fatal":
- zerolog.SetGlobalLevel(zerolog.FatalLevel)
- case "error":
- zerolog.SetGlobalLevel(zerolog.ErrorLevel)
- case "warn":
- zerolog.SetGlobalLevel(zerolog.WarnLevel)
- case "info":
- zerolog.SetGlobalLevel(zerolog.InfoLevel)
- case "debug":
- zerolog.SetGlobalLevel(zerolog.DebugLevel)
- case "disabled":
- zerolog.SetGlobalLevel(zerolog.Disabled)
- default:
- return errors.Errorf("Unknown log level (%s)", level)
- }
-
- log.Debug().Str("log-level", level).Msg("Logging level")
- }
- }
-
- return nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package agent
-
-import (
- "expvar"
- "net"
- "net/http"
- "os"
- "os/signal"
-
- "github.com/circonus-labs/circonus-logwatch/internal/config"
- "github.com/circonus-labs/circonus-logwatch/internal/configs"
- "github.com/circonus-labs/circonus-logwatch/internal/metrics/circonus"
- "github.com/circonus-labs/circonus-logwatch/internal/metrics/logonly"
- "github.com/circonus-labs/circonus-logwatch/internal/metrics/statsd"
- "github.com/circonus-labs/circonus-logwatch/internal/release"
- "github.com/circonus-labs/circonus-logwatch/internal/watcher"
- "github.com/pkg/errors"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
-)
-
-func init() {
- http.Handle("/stats", expvar.Handler())
-}
-
-// New returns a new agent instance
-func New() (*Agent, error) {
- a := Agent{
- signalCh: make(chan os.Signal),
- }
-
- //
- // validate the configuration
- //
- if err := config.Validate(); err != nil {
- return nil, err
- }
-
- dest := viper.GetString(config.KeyDestType)
- switch dest {
- case "agent":
- fallthrough
- case "check":
- d, err := circonus.New()
- if err != nil {
- return nil, err
- }
- a.destClient = d
-
- case "statsd":
- d, err := statsd.New()
- if err != nil {
- return nil, err
- }
- a.destClient = d
-
- case "log":
- d, err := logonly.New()
- if err != nil {
- return nil, err
- }
- a.destClient = d
-
- default:
- return nil, errors.Errorf("unknown metric destination (%s)", dest)
- }
-
- cfgs, err := configs.Load()
- if err != nil {
- return nil, err
- }
- if len(cfgs) == 0 {
- return nil, err
- }
-
- a.watchers = make([]*watcher.Watcher, len(cfgs))
- for idx, cfg := range cfgs {
- w, err := watcher.New(a.destClient, cfg)
- if err != nil {
- log.Error().Err(err).Str("id", cfg.ID).Msg("adding watcher, log will NOT be processed")
- }
- a.watchers[idx] = w
- }
-
- a.svrHTTP = &http.Server{Addr: net.JoinHostPort("localhost", viper.GetString(config.KeyAppStatPort))}
- a.svrHTTP.SetKeepAlivesEnabled(false)
-
- a.setupSignalHandler()
-
- return &a, nil
-}
-
-// Start the agent
-func (a *Agent) Start() error {
- a.t.Go(a.handleSignals)
-
- for _, w := range a.watchers {
- a.t.Go(w.Start)
- }
-
- a.t.Go(a.serveMetrics)
-
- log.Debug().
- Int("pid", os.Getpid()).
- Str("name", release.NAME).
- Str("ver", release.VERSION).Msg("Starting wait")
-
- return a.t.Wait()
-}
-
-// Stop cleans up and shuts down the Agent
-func (a *Agent) Stop() {
- a.stopSignalHandler()
-
- for _, w := range a.watchers {
- w.Stop()
- }
-
- a.svrHTTP.Close()
-
- log.Debug().
- Int("pid", os.Getpid()).
- Str("name", release.NAME).
- Str("ver", release.VERSION).Msg("Stopped")
-
- if a.t.Alive() {
- a.t.Kill(nil)
- }
-}
-
-func (a *Agent) serveMetrics() error {
- log.Debug().Str("url", "http://"+a.svrHTTP.Addr+"/stats").Msg("app stats listener")
- if err := a.svrHTTP.ListenAndServe(); err != nil {
- if err != http.ErrServerClosed {
- return errors.Wrap(err, "HTTP server")
- }
- }
- return nil
-}
-
-// stopSignalHandler disables the signal handler
-func (a *Agent) stopSignalHandler() {
- signal.Stop(a.signalCh)
- signal.Reset() // so a second ctrl-c will force a kill
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-// +build freebsd openbsd solaris darwin
-
-// Signal handling for FreeBSD, OpenBSD, Darwin, and Solaris
-// systems that have SIGINFO
-
-package agent
-
-import (
- "fmt"
- "os"
- "os/signal"
- "runtime"
-
- "github.com/alecthomas/units"
- "github.com/rs/zerolog/log"
- "golang.org/x/sys/unix"
-)
-
-func (a *Agent) setupSignalHandler() {
- signal.Notify(a.signalCh, os.Interrupt, unix.SIGTERM, unix.SIGHUP, unix.SIGPIPE, unix.SIGINFO)
-}
-
-// handleSignals runs the signal handler thread
-func (a *Agent) handleSignals() error {
- const stacktraceBufSize = 1 * units.MiB
-
- // pre-allocate a buffer
- buf := make([]byte, stacktraceBufSize)
-
- for {
- select {
- case <-a.t.Dying():
- return nil
- case sig := <-a.signalCh:
- log.Info().Str("signal", sig.String()).Msg("Received signal")
- switch sig {
- case os.Interrupt, unix.SIGTERM:
- a.Stop()
- case unix.SIGPIPE, unix.SIGHUP:
- // Noop
- case unix.SIGINFO:
- stacklen := runtime.Stack(buf, true)
- fmt.Printf("=== received SIGINFO ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
- default:
- log.Warn().Str("signal", sig.String()).Msg("unsupported")
- }
- }
- }
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package appstats
-
-import (
- "expvar"
- "sync"
-
- "github.com/pkg/errors"
-)
-
-var (
- once sync.Once
- stats *expvar.Map
- notInitializedErr = errors.Errorf("stats not initialized")
-)
-
-func init() {
- once.Do(func() {
- stats = expvar.NewMap("stats")
- })
-}
-
-// NewString creates a new string stat
-func NewString(name string) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) != nil {
- return nil
- }
-
- stats.Set(name, new(expvar.String))
-
- return nil
-}
-
-// SetString sets string stat to value
-func SetString(name string, value string) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) == nil {
- if err := NewString(name); err != nil {
- return err
- }
- }
-
- stats.Get(name).(*expvar.String).Set(value)
-
- return nil
-}
-
-// NewInt creates a new int stat
-func NewInt(name string) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) != nil {
- return nil
- }
-
- stats.Set(name, new(expvar.Int))
-
- return nil
-}
-
-// SetInt sets int stat to value
-func SetInt(name string, value int64) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) == nil {
- if err := NewInt(name); err != nil {
- return err
- }
- }
-
- stats.Get(name).(*expvar.Int).Set(value)
-
- return nil
-}
-
-// IncrementInt increment int stat
-func IncrementInt(name string) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) == nil {
- if err := NewInt(name); err != nil {
- return err
- }
- }
-
- stats.Add(name, 1)
-
- return nil
-}
-
-// DecrementInt decrement int stat
-func DecrementInt(name string) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) == nil {
- if err := NewInt(name); err != nil {
- return err
- }
- }
-
- stats.Add(name, -1)
-
- return nil
-}
-
-// NewFloat creates a new float stat
-func NewFloat(name string) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) != nil {
- return nil
- }
-
- stats.Set(name, new(expvar.Float))
-
- return nil
-}
-
-// SetFloat sets a float stat to value
-func SetFloat(name string, value float64) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) == nil {
- if err := NewFloat(name); err != nil {
- return err
- }
- }
-
- stats.Get(name).(*expvar.Float).Set(value)
-
- return nil
-}
-
-// AddFloat adds value to existing float
-func AddFloat(name string, value float64) error {
- if stats == nil {
- return notInitializedErr
- }
-
- if stats.Get(name) == nil {
- if err := NewFloat(name); err != nil {
- return err
- }
- }
-
- stats.Get(name).(*expvar.Float).Add(value)
-
- return nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package defaults
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/circonus-labs/circonus-logwatch/internal/release"
-)
-
-const (
- // APIURL for circonus
- APIURL = "https://api.circonus.com/v2/"
-
- // APIApp defines the api app name associated with the api token key
- APIApp = release.NAME
-
- // Debug is false by default
- Debug = false
-
- // AppStatPort for accessing runtime metrics (expvar)
- AppStatPort = "33284"
-
- // LogLevel set to info by default
- LogLevel = "info"
-
- // LogPretty colored/formatted output to stderr
- LogPretty = false
-
- // DestinationType where metrics should be sent (agent|check|log|statsd)
- DestinationType = "log"
-
- // AgentPort for circonus-agent
- AgentPort = "2609"
-
- // StatsdPort for circonus-agent
- StatsdPort = "8125"
-
- // StatsdPrefix to prepend to every metric
- StatsdPrefix = "host."
-)
-
-var (
- // BasePath is the "base" directory
- //
- // expected installation structure:
- // base (e.g. /opt/circonus/logwatch)
- // /etc (e.g. /opt/circonus/logwatch/etc)
- // /etc/log.d (e.g. /opt/circonus/logwatch/etc/log.d)
- // /sbin (e.g. /opt/circonus/logwatch/sbin)
- BasePath = ""
-
- // EtcPath returns the default etc directory within base directory
- EtcPath = "" // (e.g. /opt/circonus/logwatch/etc)
-
- // LogConfPath returns the default directory for log configurations within base directory
- LogConfPath = "" // (e.g. /opt/circonus/logwatch/etc/log.d)
-
- // Target used when destination type is "check"
- Target = ""
-)
-
-func init() {
- var exePath string
- var resolvedExePath string
- var err error
-
- exePath, err = os.Executable()
- if err == nil {
- resolvedExePath, err = filepath.EvalSymlinks(exePath)
- if err == nil {
- BasePath = filepath.Clean(filepath.Join(filepath.Dir(resolvedExePath), "..")) // e.g. /opt/circonus/agent
- }
- }
-
- if err != nil {
- fmt.Printf("Unable to determine path to binary %v\n", err)
- os.Exit(1)
- }
-
- EtcPath = filepath.Join(BasePath, "etc")
- LogConfPath = filepath.Join(EtcPath, "log.d")
-
- Target, err = os.Hostname()
- if err != nil {
- fmt.Printf("Unable to determine hostname for target %v\n", err)
- os.Exit(1)
- }
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package config
-
-import (
- "encoding/json"
- "expvar"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/url"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/circonus-labs/circonus-logwatch/internal/config/defaults"
- "github.com/circonus-labs/circonus-logwatch/internal/release"
- toml "github.com/pelletier/go-toml"
- "github.com/pkg/errors"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
- yaml "gopkg.in/yaml.v2"
-)
-
-// Validate the configuration options supplied
-func Validate() error {
-
- if err := logConfDir(); err != nil {
- return err
- }
-
- if err := destConf(); err != nil {
- return err
- }
-
- if viper.GetString(KeyDestType) == "check" {
- if err := apiConf(); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func destConf() error {
- dest := viper.GetString(KeyDestType)
- switch dest {
- case "log":
- return nil // nothing to validate
-
- case "check":
- return nil // cgm will vet the config
-
- case "statsd":
- id := viper.GetString(KeyDestCfgID)
- if id == "" {
- viper.Set(KeyDestCfgID, release.NAME)
- }
- port := viper.GetString(KeyDestCfgPort)
- if port == "" {
- port = defaults.StatsdPort
- viper.Set(KeyDestCfgPort, port)
- }
-
- addr := net.JoinHostPort("localhost", port)
- a, err := net.ResolveUDPAddr("udp", addr)
- if err != nil {
- return errors.Wrapf(err, "destination %s, port %s", dest, addr)
- }
-
- if err := testPort("udp", a.String()); err != nil {
- return errors.Wrapf(err, "destination %s, port %s", dest, addr)
- }
-
- case "agent":
- id := viper.GetString(KeyDestCfgID)
- if id == "" {
- viper.Set(KeyDestCfgID, release.NAME)
- }
- port := viper.GetString(KeyDestCfgPort)
- if port == "" {
- port = defaults.AgentPort
- viper.Set(KeyDestCfgPort, port)
- }
-
- addr := net.JoinHostPort("localhost", port)
- a, err := net.ResolveTCPAddr("tcp", addr)
- if err != nil {
- return errors.Wrapf(err, "destination %s, port %s", dest, addr)
- }
-
- if err := testPort("tcp", a.String()); err != nil {
- return errors.Wrapf(err, "destination %s, port %s", dest, addr)
- }
-
- viper.Set(KeyDestAgentURL, fmt.Sprintf("http://%s/write/%s", a.String(), id))
-
- default:
- return errors.Errorf("invalid/unknown metric destination (%s)", dest)
- }
-
- return nil
-}
-
-func apiConf() error {
- apiKey := viper.GetString(KeyAPITokenKey)
- apiApp := viper.GetString(KeyAPITokenApp)
- apiURL := viper.GetString(KeyAPIURL)
-
- // if key is 'cosi' - load the cosi api config
- if strings.ToLower(apiKey) == cosiName {
- cKey, cApp, cURL, err := loadCOSIConfig()
- if err != nil {
- return err
- }
-
- apiKey = cKey
- apiApp = cApp
- apiURL = cURL
- }
-
- // API is required for reverse and/or statsd
-
- if apiKey == "" {
- return errors.New("API key is required")
- }
-
- if apiApp == "" {
- return errors.New("API app is required")
- }
-
- if apiURL == "" {
- return errors.New("API URL is required")
- }
-
- if apiURL != defaults.APIURL {
- parsedURL, err := url.Parse(apiURL)
- if err != nil {
- return errors.Wrap(err, "Invalid API URL")
- }
- if parsedURL.Scheme == "" || parsedURL.Host == "" || parsedURL.Path == "" {
- return errors.Errorf("Invalid API URL (%s)", apiURL)
- }
- }
-
- viper.Set(KeyAPITokenKey, apiKey)
- viper.Set(KeyAPITokenApp, apiApp)
- viper.Set(KeyAPIURL, apiURL)
-
- return nil
-}
-
-type cosiConfig struct {
- APIKey string `json:"api_key"`
- APIApp string `json:"api_app"`
- APIURL string `json:"api_url"`
-}
-
-func loadCOSIConfig() (string, string, string, error) {
- data, err := ioutil.ReadFile(cosiCfgFile)
- if err != nil {
- return "", "", "", errors.Wrap(err, "Unable to access cosi config")
- }
-
- var cfg cosiConfig
- if err := json.Unmarshal(data, &cfg); err != nil {
- return "", "", "", errors.Wrapf(err, "Unable to parse cosi config (%s)", cosiCfgFile)
- }
-
- if cfg.APIKey == "" {
- return "", "", "", errors.Errorf("Missing API key, invalid cosi config (%s)", cosiCfgFile)
- }
- if cfg.APIApp == "" {
- return "", "", "", errors.Errorf("Missing API app, invalid cosi config (%s)", cosiCfgFile)
- }
- if cfg.APIURL == "" {
- return "", "", "", errors.Errorf("Missing API URL, invalid cosi config (%s)", cosiCfgFile)
- }
-
- return cfg.APIKey, cfg.APIApp, cfg.APIURL, nil
-
-}
-
-func logConfDir() error {
- errMsg := "Invalid log configuration directory"
- dir := viper.GetString(KeyLogConfDir)
-
- if dir == "" {
- return errors.Errorf(errMsg+" (%s)", dir)
- }
-
- absDir, err := filepath.Abs(dir)
- if err != nil {
- return errors.Wrap(err, errMsg)
- }
-
- dir = absDir
-
- fi, err := os.Stat(dir)
- if err != nil {
- return errors.Wrap(err, errMsg)
- }
-
- if !fi.Mode().IsDir() {
- return errors.Errorf(errMsg+" (%s) not a directory", dir)
- }
-
- // also try opening, to verify permissions
- // if last dir on path is not accessible to user, stat doesn't return EPERM
- f, err := os.Open(dir)
- if err != nil {
- return errors.Wrap(err, errMsg)
- }
- f.Close()
-
- viper.Set(KeyLogConfDir, dir)
-
- return nil
-}
-
-// testPort is used to verify agent|statsd port
-func testPort(network, address string) error {
- c, err := net.Dial(network, address)
- if err != nil {
- return err
- }
-
- return c.Close()
-}
-
-// StatConfig adds the running config to the app stats
-func StatConfig() error {
- cfg, err := getConfig()
- if err != nil {
- return err
- }
-
- cfg.API.Key = "..."
- cfg.API.App = "..."
-
- expvar.Publish("config", expvar.Func(func() interface{} {
- return &cfg
- }))
-
- return nil
-}
-
-// getConfig dumps the current configuration and returns it
-func getConfig() (*Config, error) {
- var cfg *Config
-
- if err := viper.Unmarshal(&cfg); err != nil {
- return nil, errors.Wrap(err, "parsing config")
- }
-
- return cfg, nil
-}
-
-// ShowConfig prints the running configuration
-func ShowConfig(w io.Writer) error {
- var cfg *Config
- var err error
- var data []byte
-
- cfg, err = getConfig()
- if err != nil {
- return err
- }
-
- format := viper.GetString(KeyShowConfig)
-
- log.Debug().Str("format", format).Msg("show-config")
-
- switch format {
- case "json":
- data, err = json.MarshalIndent(cfg, " ", " ")
- if err != nil {
- return errors.Wrap(err, "formatting config (json)")
- }
- case "yaml":
- data, err = yaml.Marshal(cfg)
- if err != nil {
- return errors.Wrap(err, "formatting config (yaml)")
- }
- case "toml":
- data, err = toml.Marshal(*cfg)
- if err != nil {
- return errors.Wrap(err, "formatting config (toml)")
- }
- default:
- return errors.Errorf("unknown config format '%s'", format)
- }
-
- fmt.Fprintf(w, "%s v%s running config:\n%s\n", release.NAME, release.VERSION, data)
- return nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package configs
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "strings"
- "text/template"
-
- "github.com/circonus-labs/circonus-logwatch/internal/config"
- "github.com/pelletier/go-toml"
- "github.com/pkg/errors"
- "github.com/rs/zerolog"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
- "gopkg.in/yaml.v2"
-)
-
-// Load reads the log configurations from log config directory
-func Load() ([]*Config, error) {
- logger := log.With().Str("pkg", "configs").Logger()
- supportedConfExts := regexp.MustCompile(`^\.(yaml|json|toml)$`)
- logConfDir := viper.GetString(config.KeyLogConfDir)
-
- if logConfDir == "" {
- return nil, errors.Errorf("invalid log config directory (empty)")
- }
-
- logger.Debug().
- Str("dir", logConfDir).
- Msg("loading log configs")
-
- entries, err := ioutil.ReadDir(logConfDir)
- if err != nil {
- return nil, err
- }
-
- if len(entries) == 0 {
- return nil, errors.Errorf("no log configurations found in (%s)", logConfDir)
- }
-
- var cfgs []*Config
-
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
-
- cfgFile := path.Join(logConfDir, entry.Name())
- cfgType := filepath.Ext(cfgFile)
- if !supportedConfExts.MatchString(cfgType) {
- logger.Warn().
- Str("type", cfgType).
- Str("file", cfgFile).
- Msg("unsupported config type, ignoring")
- continue
- }
-
- logger.Debug().
- Str("type", cfgType).
- Str("file", cfgFile).
- Msg("loading")
- logcfg, err := parse(cfgType, cfgFile)
- if err != nil {
- logger.Warn().
- Err(err).
- Str("file", cfgFile).
- Msg("parsing")
- continue
- }
-
- if err := checkLogFileAccess(logcfg.LogFile); err != nil {
- logger.Warn().
- Err(err).
- Str("log", logcfg.LogFile).
- Msg("access")
- continue
- }
-
- if logcfg.ID == "" { // ID not explicitly set, use the base of the config file name
- logcfg.ID = strings.Replace(filepath.Base(logcfg.LogFile), filepath.Ext(logcfg.LogFile), "", -1)
- }
-
- if validMetricRules(logcfg.ID, logger, logcfg.Metrics) {
- cfgs = append(cfgs, &logcfg)
- }
- }
-
- return cfgs, nil
-}
-
-func validMetricRules(logID string, logger zerolog.Logger, rules []*Metric) bool {
- for ruleID, rule := range rules {
- if rule.Match == "" {
- logger.Warn().
- Str("log_id", logID).
- Int("rule_id", ruleID).
- Msg("invalid metric rule, empty 'match', skipping config")
- return false
- }
-
- if rule.Name == "" {
- logger.Warn().
- Str("log_id", logID).
- Int("rule_id", ruleID).
- Msg("invalid metric rule, empty 'name', skipping config")
- return false
- }
-
- matcher, err := regexp.Compile(rule.Match)
- if err != nil {
- logger.Warn().
- Err(err).
- Str("log_id", logID).
- Int("rule_id", ruleID).
- Str("match", rule.Match).
- Msg("rule match compile failed, skipping config")
- return false
- }
- if matcher == nil {
- logger.Warn().
- Str("log_id", logID).
- Int("rule_id", ruleID).
- Str("match", rule.Match).
- Msg("rule match compile resulted in nil value, skipping config")
- return false
- }
- rule.Matcher = matcher
- rule.MatchParts = matcher.SubexpNames()
-
- if len(rule.MatchParts) < 2 {
- logger.Warn().
- Str("log_id", logID).
- Int("rule_id", ruleID).
- Msg("forcing type to counter, no named subexpressions found")
- rule.Type = "c"
- } else {
- // find the 'Value' subexpression and save its index for extraction on matched lines
- for _, subName := range rule.MatchParts {
- if strings.ToLower(subName) == "value" {
- rule.ValueKey = subName
- break // there can be only one
- }
- }
-
- if rule.ValueKey == "" {
- logger.Warn().
- Str("log_id", logID).
- Int("id_id", ruleID).
- Msg("forcing type to counter, no subexpression named 'Value' found")
- rule.Type = "c"
- }
- }
-
- // template does not contain template interpolation code, treat it as a literal metric name
- if !strings.Contains(rule.Name, "{{.") {
- continue
- }
-
- if len(rule.MatchParts) < 2 {
- logger.Warn().
- Str("log_id", logID).
- Int("id_id", ruleID).
- Str("match", rule.Match).
- Str("name", rule.Name).
- Msg("'name' expects matches, match has no named subexpressions, skipping config")
- return false
- }
-
- templateID := fmt.Sprintf("%s:M%d", logID, ruleID)
- namer, err := template.New(templateID).Parse(rule.Name)
- if err != nil {
- logger.Warn().
- Err(err).
- Str("log_id", logID).
- Int("id_id", ruleID).
- Str("name", rule.Name).
- Msg("name template parse failed, skipping config")
- return false
- }
- if namer == nil {
- logger.Warn().
- Str("log_id", logID).
- Int("id_id", ruleID).
- Str("name", rule.Name).
- Msg("name template parse resulted in nil value, skipping config")
- return false
- }
- rule.Namer = namer
- }
-
- return true
-}
-
-// parse reads and parses a log configuration
-func parse(cfgType, cfgFile string) (Config, error) {
- var cfg Config
-
- data, err := ioutil.ReadFile(cfgFile)
- if err != nil {
- return cfg, err
- }
-
- switch cfgType {
- case ".json":
- err := json.Unmarshal(data, &cfg)
- if serr, ok := err.(*json.SyntaxError); ok {
- line, col := findLine(data, serr.Offset)
- return cfg, errors.Wrapf(err, "line %d, col %d", line, col)
- }
- return cfg, err
- case ".yaml":
- err := yaml.Unmarshal(data, &cfg)
- if err != nil {
- return cfg, err
- }
- case ".toml":
- err := toml.Unmarshal(data, &cfg)
- if err != nil {
- return cfg, err
- }
- default:
- return cfg, errors.Errorf("unknown config type (%s)", cfgType)
- }
-
- return cfg, nil
-}
-
-// checkLogFileAccess verifies a log file can be opened for reading
-func checkLogFileAccess(file string) error {
- f, err := os.Open(file)
- if err != nil {
- return err
- }
- f.Close()
- return nil
-}
-
-func findLine(data []byte, offset int64) (int, int) {
- if offset == 0 {
- return 1, 1
- }
- const (
- CR = 13
- LF = 10
- )
- line := 1
- col := 0
- for i := int64(0); i < offset; i++ {
- col++
- if data[i] == CR || data[i] == LF {
- col = 0
- line++
- }
- }
- return line, col
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package circonus
-
-import (
- "fmt"
- stdlog "log"
-
- cgm "github.com/circonus-labs/circonus-gometrics"
- "github.com/circonus-labs/circonus-logwatch/internal/config"
- "github.com/pkg/errors"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
-)
-
-// New returns a new instance of the circonus metrics destination
-func New() (*Circonus, error) {
- var client *cgm.CirconusMetrics
-
- logger := log.With().Str("pkg", "circonus").Logger()
- dest := viper.GetString(config.KeyDestType)
-
- switch dest {
- case "agent":
- sURL := viper.GetString(config.KeyDestAgentURL)
- if sURL == "" {
- return nil, errors.Errorf("invalid agent url defined (empty)")
- }
- cmc := &cgm.Config{
- Debug: viper.GetBool(config.KeyDebugCGM),
- Log: stdlog.New(log.With().Str("pkg", "dest-agent").Logger(), "", 0),
- }
- cmc.Interval = "60s"
- cmc.CheckManager.Check.SubmissionURL = sURL
- c, err := cgm.New(cmc)
- if err != nil {
- return nil, errors.Wrap(err, "creating client for destination 'agent'")
- }
- client = c
-
- case "check":
- cmc := &cgm.Config{
- Debug: viper.GetBool(config.KeyDebugCGM),
- Log: stdlog.New(log.With().Str("pkg", "dest-check").Logger(), "", 0),
- }
- cmc.CheckManager.Check.ID = viper.GetString(config.KeyDestCfgCID)
- cmc.CheckManager.Check.SubmissionURL = viper.GetString(config.KeyDestCfgURL)
- cmc.CheckManager.Check.SearchTag = viper.GetString(config.KeyDestCfgSearchTag)
- cmc.CheckManager.Check.TargetHost = viper.GetString(config.KeyDestCfgTarget)
- c, err := cgm.New(cmc)
- if err != nil {
- return nil, errors.Wrap(err, "creating client for destination 'check'")
- }
- client = c
-
- default:
- return nil, errors.Errorf("unknown destination type for circonus client %s", dest)
- }
-
- return &Circonus{client: client, logger: logger}, nil
-}
-
-// Start NOOP cgm starts as it is initialized
-func (c *Circonus) Start() error {
- // noop
- return nil
-}
-
-// Stop flushes any outstanding metrics
-func (c *Circonus) Stop() error {
- c.client.Flush()
- return nil
-}
-
-// SetGaugeValue sends a gauge metric
-func (c *Circonus) SetGaugeValue(metric string, value interface{}) error { // gauge (ints or floats)
- c.client.Gauge(metric, value)
- return nil
-}
-
-// SetTimingValue sends a timing metric
-func (c *Circonus) SetTimingValue(metric string, value float64) error { // histogram
- return c.SetHistogramValue(metric, value)
-}
-
-// SetHistogramValue sends a histogram metric
-func (c *Circonus) SetHistogramValue(metric string, value float64) error { // histogram
- c.client.RecordValue(metric, value)
- return nil
-}
-
-// IncrementCounter sends a counter increment
-func (c *Circonus) IncrementCounter(metric string) error { // counter (monotonically increasing value)
- return c.IncrementCounterByValue(metric, 1)
-}
-
-// IncrementCounterByValue sends value to add to counter
-func (c *Circonus) IncrementCounterByValue(metric string, value uint64) error { // counter (monotonically increasing value)
- c.client.IncrementByValue(metric, value)
- return nil
-}
-
-// AddSetValue sends a unique value to the set metric
-func (c *Circonus) AddSetValue(metric string, value string) error { // set metric (ala statsd, counts unique values)
- c.IncrementCounter(fmt.Sprintf("%s`%s", metric, value))
- return nil
-}
-
-// SetTextValue sends a text metric
-func (c *Circonus) SetTextValue(metric string, value string) error { // text metric
- c.client.SetTextValue(metric, value)
- return nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package logonly
-
-import (
- "github.com/rs/zerolog/log"
-)
-
-// New creates a new log only destination
-func New() (*LogOnly, error) {
-
- once.Do(func() {
- client = &LogOnly{
- logger: log.With().Str("pkg", "dest-log").Logger(),
- }
- })
-
- return client, nil
-}
-
-// Start is a NOP for log only destination
-func (c *LogOnly) Start() error {
- // NOP
- return nil
-}
-
-// Stop is a NOP for log only destination
-func (c *LogOnly) Stop() error {
- // NOP
- return nil
-}
-
-// IncrementCounter increments a counter - type 'c'
-func (c *LogOnly) IncrementCounter(metric string) error { // counter (monotonically increasing value)
- c.logger.Info().Str("name", metric).Interface("value", 1).Msg("metric")
- return nil
-}
-
-// IncrementCounterByValue sends value to add to counter - type 'c'
-func (c *LogOnly) IncrementCounterByValue(metric string, value uint64) error { // counter (monotonically increasing value)
- c.logger.Info().Str("name", metric).Interface("value", value).Msg("metric")
- return nil
-}
-
-// SetGaugeValue sets a gauge metric to the specified value - type 'g'
-func (c *LogOnly) SetGaugeValue(metric string, value interface{}) error { // gauge (ints or floats)
- c.logger.Info().Str("name", metric).Interface("value", value).Msg("metric")
- return nil
-}
-
-// SetHistogramValue sets a histogram metric to the specified value - type 'h'
-func (c *LogOnly) SetHistogramValue(metric string, value float64) error { // histogram
- c.logger.Info().Str("name", metric).Interface("value", value).Msg("metric")
- return nil
-}
-
-// SetTimingValue sets a timing metric to the specified value - type 'ms'
-func (c *LogOnly) SetTimingValue(metric string, value float64) error { // histogram
- c.logger.Info().Str("name", metric).Interface("value", value).Msg("metric")
- return nil
-}
-
-// AddSetValue adds (or increments the counter) for the specified unique value - type 's'
-func (c *LogOnly) AddSetValue(metric, value string) error { // set metric (ala statsd, counts unique values)
- c.logger.Info().Str("name", metric).Interface("value", value).Msg("metric")
- return nil
-}
-
-// SetTextValue sets a text metric to the specified value - type 't'
-func (c *LogOnly) SetTextValue(metric, value string) error { // text metric
- c.logger.Info().Str("name", metric).Interface("value", value).Msg("metric")
- return nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package statsd
-
-import (
- crand "crypto/rand"
- "fmt"
- "math"
- "math/big"
- "math/rand"
- "net"
- "time"
-
- "github.com/circonus-labs/circonus-logwatch/internal/config"
- "github.com/pkg/errors"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
-)
-
-func init() {
- n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
- if err != nil {
- rand.Seed(time.Now().UTC().UnixNano())
- return
- }
- rand.Seed(n.Int64())
-}
-
-// New initializes a udp connection to the localhost
-func New() (*Statsd, error) {
- id := viper.GetString(config.KeyDestCfgID)
- if id == "" {
- return nil, errors.Errorf("invalid id, empty")
- }
-
- port := viper.GetString(config.KeyDestCfgPort)
- if port == "" {
- return nil, errors.Errorf("invalid port, empty")
- }
-
- once.Do(func() {
- client = &Statsd{
- id: id,
- port: port,
- prefix: viper.GetString(config.KeyDestCfgStatsdPrefix) + id,
- logger: log.With().Str("pkg", "dest-statsd").Logger(),
- }
- })
-
- return client, nil
-}
-
-// Start the statsd Statsd
-func (c *Statsd) Start() error {
- return c.open()
-}
-
-// Stop the statsd Statsd
-func (c *Statsd) Stop() error {
- if c.conn == nil {
- return nil
- }
- return c.conn.Close()
-}
-
-// SetGaugeValue sends a gauge metric
-func (c *Statsd) SetGaugeValue(metric string, value interface{}) error { // gauge (ints or floats)
- v, err := getGaugeValue(value)
- if err != nil {
- return err
- }
- return c.send(fmt.Sprintf("%s:%s|g", metric, v))
-}
-
-// SetTimingValue sends a timing metric
-func (c *Statsd) SetTimingValue(metric string, value float64) error { // histogram
- return c.SetHistogramValue(metric, value)
-}
-
-// SetHistogramValue sends a histogram metric
-func (c *Statsd) SetHistogramValue(metric string, value float64) error { // histogram
- return c.send(fmt.Sprintf("%s:%e|ms", metric, value))
-}
-
-// IncrementCounter sends a counter increment
-func (c *Statsd) IncrementCounter(metric string) error { // counter (monotonically increasing value)
- return c.IncrementCounterByValue(metric, 1)
-}
-
-// IncrementCounterByValue sends value to add to counter
-func (c *Statsd) IncrementCounterByValue(metric string, value uint64) error { // counter (monotonically increasing value)
- return c.send(fmt.Sprintf("%s:%d|c", metric, value))
-}
-
-// AddSetValue sends a unique value to the set metric
-func (c *Statsd) AddSetValue(metric string, value string) error { // set metric (ala statsd, counts unique values)
- return c.send(fmt.Sprintf("%s:%s|s", metric, value))
-}
-
-// SetTextValue sends a text metric
-func (c *Statsd) SetTextValue(metric string, value string) error { // text metric
- return c.send(fmt.Sprintf("%s:%s|t", metric, value))
-}
-
-// send stats data to udp statsd daemon
-//
-// Outgoing metric format:
-//
-// name:value|type[@rate]
-//
-// e.g.
-// foo:1|c
-// foo:1|c@0.5
-// bar:2.5|ms
-// bar:2.5|ms@.25
-// baz:25|g
-// qux:abcd123|s
-// dib:38.282|h
-// dab:yadda yadda yadda|t
-func (c *Statsd) send(metric string) error {
- if c.conn == nil {
- if err := c.open(); err != nil {
- return err
- }
- }
-
- m := c.prefix + metric
-
- c.logger.Debug().Str("metric", m).Msg("sending")
-
- _, err := fmt.Fprintf(c.conn, m)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// open udp connection
-func (c *Statsd) open() error {
- if c.conn != nil {
- c.conn.Close()
- }
-
- conn, err := net.Dial("udp", net.JoinHostPort("", c.port))
- if err != nil {
- return err
- }
-
- c.conn = conn
-
- return nil
-}
-
-// getGaugeValue as string from interface
-func getGaugeValue(value interface{}) (string, error) {
- vs := ""
- switch v := value.(type) {
- case int:
- vs = fmt.Sprintf("%d", v)
- case int8:
- vs = fmt.Sprintf("%d", v)
- case int16:
- vs = fmt.Sprintf("%d", v)
- case int32:
- vs = fmt.Sprintf("%d", v)
- case int64:
- vs = fmt.Sprintf("%d", v)
- case uint:
- vs = fmt.Sprintf("%d", v)
- case uint8:
- vs = fmt.Sprintf("%d", v)
- case uint16:
- vs = fmt.Sprintf("%d", v)
- case uint32:
- vs = fmt.Sprintf("%d", v)
- case uint64:
- vs = fmt.Sprintf("%d", v)
- case float32:
- vs = fmt.Sprintf("%f", v)
- case float64:
- vs = fmt.Sprintf("%f", v)
- default:
- return "", errors.Errorf("unknown type for value %v", v)
- }
- return vs, nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package release
-
-import (
- "expvar"
-)
-
-func init() {
- expvar.Publish("app", expvar.Func(info))
-}
-
-func info() interface{} {
- return &Info{
- Name: NAME,
- Version: VERSION,
- Commit: COMMIT,
- BuildDate: DATE,
- Tag: TAG,
- }
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package watcher
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- stdlog "log"
- "strconv"
- "strings"
- "time"
-
- "github.com/circonus-labs/circonus-logwatch/internal/appstats"
- "github.com/circonus-labs/circonus-logwatch/internal/config"
- "github.com/circonus-labs/circonus-logwatch/internal/configs"
- "github.com/circonus-labs/circonus-logwatch/internal/metrics"
- "github.com/hpcloud/tail"
- "github.com/pkg/errors"
- "github.com/rs/zerolog/log"
- "github.com/spf13/viper"
-)
-
-// New creates a new watcher instance
-func New(metricDest metrics.Destination, logConfig *configs.Config) (*Watcher, error) {
- if metricDest == nil {
- return nil, errors.New("invalid metric destination (nil)")
- }
- if logConfig == nil {
- return nil, errors.New("invalid log config (nil)")
- }
-
- w := Watcher{
- logger: log.With().Str("pkg", "watcher").Str("log_id", logConfig.ID).Logger(),
- cfg: logConfig,
- dest: metricDest,
- metricLines: make(chan metricLine, metricLineQueueSize),
- metrics: make(chan metric, metricQueueSize),
- trace: viper.GetBool(config.KeyDebugMetric),
- statMatchedLines: logConfig.ID + "_lines_matched",
- statTotalLines: logConfig.ID + "_lines_total",
- }
-
- appstats.NewInt(w.statMatchedLines)
- appstats.NewInt(w.statTotalLines)
-
- return &w, nil
-}
-
-// Start the watcher
-func (w *Watcher) Start() error {
- w.t.Go(w.save)
- w.t.Go(w.parse)
- w.t.Go(w.process)
- return w.t.Wait()
-}
-
-// Stop the watcher
-func (w *Watcher) Stop() error {
- w.logger.Info().Msg("stopping")
- if w.t.Alive() {
- w.t.Kill(nil)
- }
-
- return nil
-}
-
-// process opens log and checks log lines for matches
-func (w *Watcher) process() error {
- cfg := tail.Config{
- Follow: true,
- ReOpen: true,
- MustExist: true,
- Location: &tail.SeekInfo{
- Offset: 0,
- Whence: io.SeekEnd,
- },
- Logger: stdlog.New(ioutil.Discard, "", 0),
- }
-
- if viper.GetBool(config.KeyDebugTail) {
- cfg.Logger = stdlog.New(w.logger.With().Str("pkg", "tail").Logger(), "", 0)
- }
-
- tailer, err := tail.TailFile(w.cfg.LogFile, cfg)
- if err != nil {
- return err
- }
-
- for {
- select {
- case <-w.t.Dying():
- tailer.Cleanup()
- return nil
- case <-tailer.Dying():
- tailer.Cleanup()
- return nil
- case line := <-tailer.Lines:
- if line == nil {
- _, err := tailer.Tell()
- if err != nil {
- if !strings.Contains(err.Error(), "file already closed") {
- tailer.Cleanup()
- return err
- }
- }
- w.logger.Warn().Msg("nil line, stopping tail")
- tailer.Cleanup()
- return nil
- }
- if line.Err != nil {
- w.logger.Error().
- Err(line.Err).
- Str("log_line", line.Text).
- Msg("tail")
- tailer.Cleanup()
- return err
- }
- appstats.IncrementInt(w.statTotalLines)
- for id, def := range w.cfg.Metrics {
- if w.trace {
- w.logger.Log().
- Int("metric_id", id).
- Str("metric_match", def.Matcher.String()).
- Str("log_line", line.Text).
- Msg("checking rule")
- }
- matches := def.Matcher.FindAllStringSubmatch(line.Text, -1)
- if matches != nil {
- ml := metricLine{
- line: line.Text,
- metricID: id,
- }
- if len(def.MatchParts) > 0 {
- m := map[string]string{}
- for i, val := range matches[0] {
- if def.MatchParts[i] != "" {
- m[def.MatchParts[i]] = val
- }
- }
- ml.matches = &m
- }
- w.metricLines <- ml
- // NOTE: do not 'break' on match, a single
- // line may generate multiple metrics.
- }
- }
- }
- }
-
- return nil
-}
-
-// parse log line to extract metric
-func (w *Watcher) parse() error {
- for {
- select {
- case <-w.t.Dying():
- return nil
- case l := <-w.metricLines:
- appstats.IncrementInt(w.statMatchedLines)
- if w.trace {
- w.logger.Log().
- Int("metric_id", l.metricID).
- Str("line", l.line).
- Interface("matches", l.matches).
- Msg("matched, parsing metric line")
- }
-
- r := w.cfg.Metrics[l.metricID]
- m := metric{
- Name: fmt.Sprintf("%s`%s", w.cfg.ID, r.Name),
- Type: r.Type,
- }
-
- if m.Type == "c" {
- m.Value = "1" // default to simple incrment by 1
- }
-
- if l.matches != nil {
- if r.ValueKey != "" {
- v, ok := (*l.matches)[r.ValueKey]
- if !ok {
- w.logger.Warn().
- Str("value_key", r.ValueKey).
- Str("line", l.line).
- Interface("matches", *l.matches).
- Msg("'Value' key defined but not found in matches")
- continue
- }
- m.Value = v
- }
- if r.Namer != nil {
- var b bytes.Buffer
- r.Namer.Execute(&b, *l.matches)
- m.Name = fmt.Sprintf("%s`%s", w.cfg.ID, b.String())
- }
- }
-
- w.metrics <- m
- }
- }
- return nil
-}
-
-// save metrics to configured destination
-func (w *Watcher) save() error {
- for {
- select {
- case <-w.t.Dying():
- return nil
- case m := <-w.metrics:
- w.logger.Info().
- Str("metric", fmt.Sprintf("%#v", m)).
- Msg("sending")
-
- switch m.Type {
- case "c":
- v, err := strconv.ParseUint(m.Value, 10, 64)
- if err != nil {
- w.logger.Warn().Err(err).Msg(m.Name)
- } else {
- w.dest.IncrementCounterByValue(m.Name, v)
- }
- case "g":
- w.dest.SetGaugeValue(m.Name, m.Value)
- case "h":
- v, err := strconv.ParseFloat(m.Value, 64)
- if err != nil {
- w.logger.Warn().Err(err).Msg(m.Name)
- } else {
- w.dest.SetHistogramValue(m.Name, v)
- }
- case "ms":
- // parse as float
- v, errFloat := strconv.ParseFloat(m.Value, 64)
- if errFloat == nil {
- w.dest.SetTimingValue(m.Name, v)
- continue
- }
- // try parsing as a duration (e.g. 60ms, 1m, 3s)
- dur, errDuration := time.ParseDuration(m.Value)
- if errDuration != nil {
- w.logger.Warn().Err(errFloat).Err(errDuration).Str("metric", m.Name).Msg("failed to parse timing as float or duration")
- continue
- }
- w.dest.SetTimingValue(m.Name, float64(dur/time.Millisecond))
- case "s":
- w.dest.AddSetValue(m.Name, m.Value)
- case "t":
- w.dest.SetTextValue(m.Name, m.Value)
- default:
- w.logger.Info().
- Str("type", m.Type).
- Str("name", m.Name).
- Interface("val", m.Value).
- Msg("metric, unknown type")
- }
- }
- }
- return nil
-}
-
-
-
// Copyright © 2017 Circonus, Inc. <support@circonus.com>
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-//
-
-package main
-
-import (
- "github.com/circonus-labs/circonus-logwatch/cmd"
- "github.com/circonus-labs/circonus-logwatch/internal/release"
-)
-
-func main() {
- cmd.Execute()
-}
-
-// defined during build (e.g. goreleaser, see .goreleaser.yml)
-var (
- version = "dev"
- commit = "none"
- date = "unknown"
- tag = ""
-)
-
-func init() {
- release.VERSION = version
- release.COMMIT = commit
- release.DATE = date
- release.TAG = tag
-}
-
-
-
-
-
-