You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2017/04/19 21:32:13 UTC

[2/4] incubator-trafficcontrol git commit: Add TM2 polling config log location changes

Add TM2 polling config log location changes

Also simplifies the FilePoller.


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/7697b5b1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/7697b5b1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/7697b5b1

Branch: refs/heads/master
Commit: 7697b5b1380855fdd562c14b0ad9f02bbb6d42b7
Parents: 240043f
Author: Robert Butts <ro...@gmail.com>
Authored: Wed Mar 29 16:55:20 2017 -0600
Committer: David Neuman <da...@gmail.com>
Committed: Wed Apr 19 15:31:43 2017 -0600

----------------------------------------------------------------------
 .../common/handler/handler.go                   |  28 +---
 traffic_monitor_golang/common/poller/poller.go  |  41 -----
 .../traffic_monitor/config/config.go            |  59 ++++++-
 .../traffic_monitor/manager/manager.go          |  38 ++++-
 .../traffic_monitor/manager/opsconfig.go        | 167 +++++++++----------
 .../traffic_monitor/poller/file.go              |  60 +++++++
 .../traffic_monitor/traffic_monitor.go          |  46 +----
 7 files changed, 242 insertions(+), 197 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/common/handler/handler.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/handler/handler.go b/traffic_monitor_golang/common/handler/handler.go
index 587238a..3b001c6 100644
--- a/traffic_monitor_golang/common/handler/handler.go
+++ b/traffic_monitor_golang/common/handler/handler.go
@@ -20,11 +20,8 @@ package handler
  */
 
 import (
-	"encoding/json"
 	"io"
 	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
 )
 
 const (
@@ -33,16 +30,6 @@ const (
 	NOTIFY_ALWAYS
 )
 
-type Handler interface {
-	Handle(string, io.Reader, time.Duration, time.Time, error, uint64, chan<- uint64)
-}
-
-type OpsConfigFileHandler struct {
-	Content          interface{}
-	ResultChannel    chan interface{}
-	OpsConfigChannel chan OpsConfig
-}
-
 type OpsConfig struct {
 	Username     string `json:"username"`
 	Password     string `json:"password"`
@@ -52,17 +39,6 @@ type OpsConfig struct {
 	HttpListener string `json:"httpListener"`
 }
 
-func (handler OpsConfigFileHandler) Listen() {
-	for {
-		result := <-handler.ResultChannel
-		var toc OpsConfig
-
-		err := json.Unmarshal(result.([]byte), &toc)
-
-		if err != nil {
-			log.Errorf("Could not unmarshal Ops Config JSON: %s\n", err)
-		} else {
-			handler.OpsConfigChannel <- toc
-		}
-	}
+type Handler interface {
+	Handle(string, io.Reader, time.Duration, time.Time, error, uint64, chan<- uint64)
 }

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/common/poller/poller.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/poller/poller.go b/traffic_monitor_golang/common/poller/poller.go
index 9b760d6..6d85713 100644
--- a/traffic_monitor_golang/common/poller/poller.go
+++ b/traffic_monitor_golang/common/poller/poller.go
@@ -20,15 +20,12 @@ package poller
  */
 
 import (
-	"io/ioutil"
 	"math/rand"
 	"net/http"
 	"os"
 	"sync/atomic"
 	"time"
 
-	"gopkg.in/fsnotify.v1"
-
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/fetcher"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
 	instr "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/instrumentation"
@@ -93,12 +90,6 @@ func NewHTTP(
 	}
 }
 
-type FilePoller struct {
-	File                string
-	ResultChannel       chan interface{}
-	NotificationChannel chan int
-}
-
 type MonitorCfg struct {
 	CDN string
 	Cfg to.TrafficMonitorConfigMap
@@ -348,38 +339,6 @@ func insomniacPoller(pollerId int64, polls []HTTPPollInfo, fetcherTemplate fetch
 	}
 }
 
-func (p FilePoller) Poll() {
-	// initial read before watching for changes
-	contents, err := ioutil.ReadFile(p.File)
-
-	if err != nil {
-		log.Errorf("reading %s: %s\n", p.File, err)
-		os.Exit(1) // TODO: this is a little drastic -jse
-	} else {
-		p.ResultChannel <- contents
-	}
-
-	watcher, _ := fsnotify.NewWatcher()
-	watcher.Add(p.File)
-
-	for {
-		select {
-		case event := <-watcher.Events:
-			if event.Op&fsnotify.Write == fsnotify.Write {
-				contents, err := ioutil.ReadFile(p.File)
-
-				if err != nil {
-					log.Errorf("opening %s: %s\n", p.File, err)
-				} else {
-					p.ResultChannel <- contents
-				}
-			}
-		case err := <-watcher.Errors:
-			log.Errorln(time.Now(), "error:", err)
-		}
-	}
-}
-
 // diffConfigs takes the old and new configs, and returns a list of deleted IDs, and a list of new polls to do
 func diffConfigs(old HttpPollerConfig, new HttpPollerConfig) ([]string, []HTTPPollInfo) {
 	deletions := []string{}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/traffic_monitor/config/config.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/config/config.go b/traffic_monitor_golang/traffic_monitor/config/config.go
index 1941ec6..7e1752b 100644
--- a/traffic_monitor_golang/traffic_monitor/config/config.go
+++ b/traffic_monitor_golang/traffic_monitor/config/config.go
@@ -21,8 +21,13 @@ package config
 
 import (
 	"encoding/json"
+	"fmt"
+	"io"
 	"io/ioutil"
+	"os"
 	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
 )
 
 // LogLocation is a location to log to. This may be stdout, stderr, null (/dev/null), or a valid file path.
@@ -179,8 +184,58 @@ func Load(fileName string) (Config, error) {
 		return cfg, nil
 	}
 	configBytes, err := ioutil.ReadFile(fileName)
-	if err == nil {
-		err = json.Unmarshal(configBytes, &cfg)
+	if err != nil {
+		return DefaultConfig, err
 	}
+	return LoadBytes(configBytes)
+}
+
+// LoadBytes loads the given file bytes.
+func LoadBytes(bytes []byte) (Config, error) {
+	cfg := DefaultConfig
+	err := json.Unmarshal(bytes, &cfg)
 	return cfg, err
 }
+
+func getLogWriter(location string) (io.WriteCloser, error) {
+	switch location {
+	case LogLocationStdout:
+		return log.NopCloser(os.Stdout), nil
+	case LogLocationStderr:
+		return log.NopCloser(os.Stderr), nil
+	case LogLocationNull:
+		return log.NopCloser(ioutil.Discard), nil
+	default:
+		return os.OpenFile(location, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
+	}
+}
+
+func GetLogWriters(cfg Config) (io.WriteCloser, io.WriteCloser, io.WriteCloser, io.WriteCloser, io.WriteCloser, error) {
+	eventLoc := cfg.LogLocationEvent
+	errLoc := cfg.LogLocationError
+	warnLoc := cfg.LogLocationWarning
+	infoLoc := cfg.LogLocationInfo
+	debugLoc := cfg.LogLocationDebug
+
+	eventW, err := getLogWriter(eventLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log event writer %v: %v", eventLoc, err)
+	}
+	errW, err := getLogWriter(errLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log error writer %v: %v", errLoc, err)
+	}
+	warnW, err := getLogWriter(warnLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log warning writer %v: %v", warnLoc, err)
+	}
+	infoW, err := getLogWriter(infoLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log info writer %v: %v", infoLoc, err)
+	}
+	debugW, err := getLogWriter(debugLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log debug writer %v: %v", debugLoc, err)
+	}
+	return eventW, errW, warnW, infoW, debugW, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/traffic_monitor/manager/manager.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/manager.go b/traffic_monitor_golang/traffic_monitor/manager/manager.go
index 126ca39..4a5391f 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/manager.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/manager.go
@@ -21,25 +21,29 @@ package manager
 
 import (
 	"crypto/tls"
+	"fmt"
 	"net/http"
 
+	"github.com/davecheney/gmx"
+
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/fetcher"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/poller"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/health"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	simplepoller "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/poller"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
 	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
 	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopswrapper"
-	"github.com/davecheney/gmx"
 )
 
 //
 // Start starts the poller and handler goroutines
 //
-func Start(opsConfigFile string, cfg config.Config, staticAppData config.StaticAppData) {
+func Start(opsConfigFile string, cfg config.Config, staticAppData config.StaticAppData, trafficMonitorConfigFileName string) error {
 	toSession := towrap.ITrafficOpsSession(towrap.NewTrafficOpsSessionThreadsafe(nil))
 	counters := fetcher.Counters{
 		Success: gmx.NewCounter("fetchSuccess"),
@@ -155,7 +159,12 @@ func Start(opsConfigFile string, cfg config.Config, staticAppData config.StaticA
 		cfg,
 	)
 
+	if err := startMonitorConfigFilePoller(trafficMonitorConfigFileName); err != nil {
+		return fmt.Errorf("starting monitor config file poller: %v", err)
+	}
+
 	healthTickListener(cacheHealthPoller.TickChan, healthIteration)
+	return nil
 }
 
 // healthTickListener listens for health ticks, and writes to the health iteration variable. Does not return.
@@ -164,3 +173,28 @@ func healthTickListener(cacheHealthTick <-chan uint64, healthIteration threadsaf
 		healthIteration.Set(i)
 	}
 }
+
+func startMonitorConfigFilePoller(filename string) error {
+	onChange := func(bytes []byte, err error) {
+		if err != nil {
+			log.Errorf("monitor config file poll, polling file '%v': %v", filename, err)
+			return
+		}
+
+		cfg, err := config.LoadBytes(bytes)
+		if err != nil {
+			log.Errorf("monitor config file poll, loading bytes '%v' from '%v': %v", string(bytes), filename, err)
+			return
+		}
+
+		eventW, errW, warnW, infoW, debugW, err := config.GetLogWriters(cfg)
+		if err != nil {
+			log.Errorf("monitor config file poll, getting log writers '%v': %v", filename, err)
+			return
+		}
+		log.Init(eventW, errW, warnW, infoW, debugW)
+	}
+
+	_, err := simplepoller.File(filename, onChange)
+	return err
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go b/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
index e2a275d..a184d42 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
@@ -20,16 +20,17 @@ package manager
  */
 
 import (
+	"encoding/json"
 	"fmt"
 	"time"
 
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/poller"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/datareq"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/health"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	poller "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/poller"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/srvhttp"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
 	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
@@ -65,110 +66,104 @@ func StartOpsConfigManager(
 	unpolledCaches threadsafe.UnpolledCaches,
 	monitorConfig threadsafe.TrafficMonitorConfigMap,
 	cfg config.Config,
-) threadsafe.OpsConfig {
+) (threadsafe.OpsConfig, error) {
 
-	opsConfigFileChannel := make(chan interface{})
-	opsConfigFilePoller := poller.FilePoller{
-		File:          opsConfigFile,
-		ResultChannel: opsConfigFileChannel,
+	handleErr := func(err error) {
+		errorCount.Inc()
+		log.Errorf("OpsConfigManager: %v\n", err)
 	}
 
-	opsConfigChannel := make(chan handler.OpsConfig)
-	opsConfigFileHandler := handler.OpsConfigFileHandler{
-		ResultChannel:    opsConfigFilePoller.ResultChannel,
-		OpsConfigChannel: opsConfigChannel,
-	}
-
-	go opsConfigFileHandler.Listen()
-	go opsConfigFilePoller.Poll()
-
+	httpServer := srvhttp.Server{}
 	opsConfig := threadsafe.NewOpsConfig()
 
 	// TODO remove change subscribers, give Threadsafes directly to the things that need them. If they only set vars, and don't actually do work on change.
-	go func() {
-		handleErr := func(err error) {
-			errorCount.Inc()
-			log.Errorf("OpsConfigManager: %v\n", err)
+	onChange := func(bytes []byte, err error) {
+		if err != nil {
+			handleErr(err)
+			return
 		}
 
-		httpServer := srvhttp.Server{}
+		newOpsConfig := handler.OpsConfig{}
+		if err = json.Unmarshal(bytes, &newOpsConfig); err != nil {
+			handleErr(fmt.Errorf("Could not unmarshal Ops Config JSON: %s\n", err))
+			return
+		}
 
-		for newOpsConfig := range opsConfigChannel {
-			// TODO config? parameter?
-			useCache := false
-			trafficOpsRequestTimeout := time.Second * time.Duration(10)
-			realToSession, err := to.LoginWithAgent(newOpsConfig.Url, newOpsConfig.Username, newOpsConfig.Password, newOpsConfig.Insecure, staticAppData.UserAgent, useCache, trafficOpsRequestTimeout)
-			if err != nil {
-				handleErr(fmt.Errorf("instantiating Session with traffic_ops: %s\n", err))
-				continue
-			}
+		opsConfig.Set(newOpsConfig)
 
-			if cdn, err := getMonitorCDN(realToSession, staticAppData.Hostname); err != nil {
-				handleErr(fmt.Errorf("getting CDN name from Traffic Ops, using config CDN '%s': %s\n", newOpsConfig.CdnName, err))
-			} else {
-				if newOpsConfig.CdnName != "" && newOpsConfig.CdnName != cdn {
-					log.Warnf("%s Traffic Ops CDN '%s' doesn't match config CDN '%s' - using Traffic Ops CDN\n", staticAppData.Hostname, cdn, newOpsConfig.CdnName)
-				}
-				newOpsConfig.CdnName = cdn
-			}
+		listenAddress := ":80" // default
 
-			opsConfig.Set(newOpsConfig)
+		if newOpsConfig.HttpListener != "" {
+			listenAddress = newOpsConfig.HttpListener
+		}
 
-			listenAddress := ":80" // default
+		endpoints := datareq.MakeDispatchMap(
+			opsConfig,
+			toSession,
+			localStates,
+			peerStates,
+			combinedStates,
+			statInfoHistory,
+			statResultHistory,
+			statMaxKbpses,
+			healthHistory,
+			dsStats,
+			events,
+			staticAppData,
+			healthPollInterval,
+			lastHealthDurations,
+			fetchCount,
+			healthIteration,
+			errorCount,
+			toData,
+			localCacheStatus,
+			lastStats,
+			unpolledCaches,
+			monitorConfig,
+		)
+		err = httpServer.Run(endpoints, listenAddress, cfg.ServeReadTimeout, cfg.ServeWriteTimeout, cfg.StaticFileDir)
+		if err != nil {
+			handleErr(fmt.Errorf("MonitorConfigPoller: error creating HTTP server: %s\n", err))
+			return
+		}
 
-			if newOpsConfig.HttpListener != "" {
-				listenAddress = newOpsConfig.HttpListener
-			}
+		// TODO config? parameter?
+		useCache := false
+		trafficOpsRequestTimeout := time.Second * time.Duration(10)
 
-			endpoints := datareq.MakeDispatchMap(
-				opsConfig,
-				toSession,
-				localStates,
-				peerStates,
-				combinedStates,
-				statInfoHistory,
-				statResultHistory,
-				statMaxKbpses,
-				healthHistory,
-				dsStats,
-				events,
-				staticAppData,
-				healthPollInterval,
-				lastHealthDurations,
-				fetchCount,
-				healthIteration,
-				errorCount,
-				toData,
-				localCacheStatus,
-				lastStats,
-				unpolledCaches,
-				monitorConfig,
-			)
-
-			if err := httpServer.Run(endpoints, listenAddress, cfg.ServeReadTimeout, cfg.ServeWriteTimeout, cfg.StaticFileDir); err != nil {
-				handleErr(fmt.Errorf("creating HTTP server: %s\n", err))
-				continue
-			}
-
-			toSession.Set(realToSession)
+		realToSession, err := to.LoginWithAgent(newOpsConfig.Url, newOpsConfig.Username, newOpsConfig.Password, newOpsConfig.Insecure, staticAppData.UserAgent, useCache, trafficOpsRequestTimeout)
+		if err != nil {
+			handleErr(fmt.Errorf("MonitorConfigPoller: error instantiating Session with traffic_ops: %s\n", err))
+			return
+		}
+		toSession.Set(realToSession)
 
-			if err := toData.Fetch(toSession, newOpsConfig.CdnName); err != nil {
-				handleErr(fmt.Errorf("getting Traffic Ops data: %v\n", err))
-				continue
+		if cdn, err := getMonitorCDN(realToSession, staticAppData.Hostname); err != nil {
+			handleErr(fmt.Errorf("getting CDN name from Traffic Ops, using config CDN '%s': %s\n", newOpsConfig.CdnName, err))
+		} else {
+			if newOpsConfig.CdnName != "" && newOpsConfig.CdnName != cdn {
+				log.Warnf("%s Traffic Ops CDN '%s' doesn't match config CDN '%s' - using Traffic Ops CDN\n", staticAppData.Hostname, cdn, newOpsConfig.CdnName)
 			}
+			newOpsConfig.CdnName = cdn
+		}
 
-			// These must be in a goroutine, because the monitorConfigPoller tick sends to a channel this select listens for. Thus, if we block on sends to the monitorConfigPoller, we have a livelock race condition.
-			// More generically, we're using goroutines as an infinite chan buffer, to avoid potential livelocks
-			for _, subscriber := range opsConfigChangeSubscribers {
-				go func(s chan<- handler.OpsConfig) { s <- newOpsConfig }(subscriber)
-			}
-			for _, subscriber := range toChangeSubscribers {
-				go func(s chan<- towrap.ITrafficOpsSession) { s <- toSession }(subscriber)
-			}
+		if err := toData.Fetch(toSession, newOpsConfig.CdnName); err != nil {
+			handleErr(fmt.Errorf("Error getting Traffic Ops data: %v\n", err))
+			return
 		}
-	}()
 
-	return opsConfig
+		// These must be in a goroutine, because the monitorConfigPoller tick sends to a channel this select listens for. Thus, if we block on sends to the monitorConfigPoller, we have a livelock race condition.
+		// More generically, we're using goroutines as an infinite chan buffer, to avoid potential livelocks
+		for _, subscriber := range opsConfigChangeSubscribers {
+			go func(s chan<- handler.OpsConfig) { s <- newOpsConfig }(subscriber)
+		}
+		for _, subscriber := range toChangeSubscribers {
+			go func(s chan<- towrap.ITrafficOpsSession) { s <- toSession }(subscriber)
+		}
+	}
+
+	_, err := poller.File(opsConfigFile, onChange)
+	return opsConfig, err
 }
 
 // getMonitorCDN returns the CDN of a given Traffic Monitor.

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/traffic_monitor/poller/file.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/poller/file.go b/traffic_monitor_golang/traffic_monitor/poller/file.go
new file mode 100644
index 0000000..fdd89d8
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/poller/file.go
@@ -0,0 +1,60 @@
+package poller
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"fmt"
+	"gopkg.in/fsnotify.v1"
+	"io/ioutil"
+)
+
+// FilePoller starts a goroutine polling the given file for changes. When changes occur, including an initial read, the result callback is called asynchronously. Returns a kill chan, which will kill the file poller when written to.
+func File(filename string, result func([]byte, error)) (chan<- struct{}, error) {
+	die := make(chan struct{})
+
+	contents, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, fmt.Errorf("reading file '%v': %v", filename, err)
+	}
+	go result(contents, nil)
+
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		return nil, err
+	}
+	go func() {
+		watcher.Add(filename)
+		defer watcher.Close()
+		for {
+			select {
+			case event := <-watcher.Events:
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					go result(ioutil.ReadFile(filename))
+				}
+			case err := <-watcher.Errors:
+				go result(nil, err)
+			case <-die:
+				return
+			}
+		}
+	}()
+
+	return die, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7697b5b1/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/traffic_monitor.go b/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
index fe7dc38..1395ceb 100644
--- a/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
+++ b/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
@@ -22,8 +22,6 @@ package main
 import (
 	"flag"
 	"fmt"
-	"io"
-	"io/ioutil"
 	"os"
 	"runtime"
 
@@ -40,42 +38,6 @@ var GitRevision = "No Git Revision Specified. Please build with '-X main.GitRevi
 // BuildTimestamp is the time the app was built. The app SHOULD always be built with this set via the `-X` flag.
 var BuildTimestamp = "No Build Timestamp Specified. Please build with '-X main.BuildTimestamp=`date +'%Y-%M-%dT%H:%M:%S'`"
 
-func getLogWriter(location string) (io.WriteCloser, error) {
-	switch location {
-	case config.LogLocationStdout:
-		return log.NopCloser(os.Stdout), nil
-	case config.LogLocationStderr:
-		return log.NopCloser(os.Stderr), nil
-	case config.LogLocationNull:
-		return log.NopCloser(ioutil.Discard), nil
-	default:
-		return os.OpenFile(location, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
-	}
-}
-func getLogWriters(eventLoc, errLoc, warnLoc, infoLoc, debugLoc string) (io.WriteCloser, io.WriteCloser, io.WriteCloser, io.WriteCloser, io.WriteCloser, error) {
-	eventW, err := getLogWriter(eventLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log event writer %v: %v", eventLoc, err)
-	}
-	errW, err := getLogWriter(errLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log error writer %v: %v", errLoc, err)
-	}
-	warnW, err := getLogWriter(warnLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log warning writer %v: %v", warnLoc, err)
-	}
-	infoW, err := getLogWriter(infoLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log info writer %v: %v", infoLoc, err)
-	}
-	debugW, err := getLogWriter(debugLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log debug writer %v: %v", debugLoc, err)
-	}
-	return eventW, errW, warnW, infoW, debugW, nil
-}
-
 func main() {
 	runtime.GOMAXPROCS(runtime.NumCPU())
 
@@ -101,7 +63,7 @@ func main() {
 		os.Exit(1)
 	}
 
-	eventW, errW, warnW, infoW, debugW, err := getLogWriters(cfg.LogLocationEvent, cfg.LogLocationError, cfg.LogLocationWarning, cfg.LogLocationInfo, cfg.LogLocationDebug)
+	eventW, errW, warnW, infoW, debugW, err := config.GetLogWriters(cfg)
 	if err != nil {
 		fmt.Printf("Error starting service: failed to create log writers: %v\n", err)
 		os.Exit(1)
@@ -110,5 +72,9 @@ func main() {
 
 	log.Infof("Starting with config %+v\n", cfg)
 
-	manager.Start(*opsConfigFile, cfg, staticData)
+	err = manager.Start(*opsConfigFile, cfg, staticData, *configFileName)
+	if err != nil {
+		fmt.Printf("Error starting service: failed to start managers: %v\n", err)
+		os.Exit(1)
+	}
 }