You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2017/01/24 20:56:20 UTC

[09/13] incubator-trafficcontrol git commit: Vendored github.com/cihub/seelog.

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser.go b/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser.go
new file mode 100644
index 0000000..921bc16
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser.go
@@ -0,0 +1,1269 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import (
+	"crypto/tls"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Names of elements of seelog config.
+const (
+	seelogConfigID                   = "seelog"
+	outputsID                        = "outputs"
+	formatsID                        = "formats"
+	minLevelID                       = "minlevel"
+	maxLevelID                       = "maxlevel"
+	levelsID                         = "levels"
+	exceptionsID                     = "exceptions"
+	exceptionID                      = "exception"
+	funcPatternID                    = "funcpattern"
+	filePatternID                    = "filepattern"
+	formatID                         = "format"
+	formatAttrID                     = "format"
+	formatKeyAttrID                  = "id"
+	outputFormatID                   = "formatid"
+	pathID                           = "path"
+	fileWriterID                     = "file"
+	smtpWriterID                     = "smtp"
+	senderaddressID                  = "senderaddress"
+	senderNameID                     = "sendername"
+	recipientID                      = "recipient"
+	mailHeaderID                     = "header"
+	mailHeaderNameID                 = "name"
+	mailHeaderValueID                = "value"
+	addressID                        = "address"
+	hostNameID                       = "hostname"
+	hostPortID                       = "hostport"
+	userNameID                       = "username"
+	userPassID                       = "password"
+	cACertDirpathID                  = "cacertdirpath"
+	subjectID                        = "subject"
+	splitterDispatcherID             = "splitter"
+	consoleWriterID                  = "console"
+	customReceiverID                 = "custom"
+	customNameAttrID                 = "name"
+	customNameDataAttrPrefix         = "data-"
+	filterDispatcherID               = "filter"
+	filterLevelsAttrID               = "levels"
+	rollingfileWriterID              = "rollingfile"
+	rollingFileTypeAttr              = "type"
+	rollingFilePathAttr              = "filename"
+	rollingFileMaxSizeAttr           = "maxsize"
+	rollingFileMaxRollsAttr          = "maxrolls"
+	rollingFileNameModeAttr          = "namemode"
+	rollingFileDataPatternAttr       = "datepattern"
+	rollingFileArchiveAttr           = "archivetype"
+	rollingFileArchivePathAttr       = "archivepath"
+	rollingFileArchiveExplodedAttr   = "archiveexploded"
+	rollingFileFullNameAttr          = "fullname"
+	bufferedWriterID                 = "buffered"
+	bufferedSizeAttr                 = "size"
+	bufferedFlushPeriodAttr          = "flushperiod"
+	loggerTypeFromStringAttr         = "type"
+	asyncLoggerIntervalAttr          = "asyncinterval"
+	adaptLoggerMinIntervalAttr       = "mininterval"
+	adaptLoggerMaxIntervalAttr       = "maxinterval"
+	adaptLoggerCriticalMsgCountAttr  = "critmsgcount"
+	predefinedPrefix                 = "std:"
+	connWriterID                     = "conn"
+	connWriterAddrAttr               = "addr"
+	connWriterNetAttr                = "net"
+	connWriterReconnectOnMsgAttr     = "reconnectonmsg"
+	connWriterUseTLSAttr             = "tls"
+	connWriterInsecureSkipVerifyAttr = "insecureskipverify"
+)
+
+// CustomReceiverProducer is the signature of the function CfgParseParams needs to create
+// custom receivers.
+type CustomReceiverProducer func(CustomReceiverInitArgs) (CustomReceiver, error)
+
+// CfgParseParams represent specific parse options or flags used by parser. It is used if seelog parser needs
+// some special directives or additional info to correctly parse a config.
+type CfgParseParams struct {
+	// CustomReceiverProducers expose the same functionality as RegisterReceiver func
+	// but only in the scope (context) of the config parse func instead of a global package scope.
+	//
+	// It means that if you use custom receivers in your code, you may either register them globally once with
+	// RegisterReceiver or you may call funcs like LoggerFromParamConfigAsFile (with 'ParamConfig')
+	// and use CustomReceiverProducers to provide custom producer funcs.
+	//
+	// A producer func is called when config parser processes a '<custom>' element. It takes the 'name' attribute
+	// of the element and tries to find a match in two places:
+	// 1) CfgParseParams.CustomReceiverProducers map
+	// 2) Global type map, filled by RegisterReceiver
+	//
+	// If a match is found in the CustomReceiverProducers map, parser calls the corresponding producer func
+	// passing the init args to it.	The func takes exactly the same args as CustomReceiver.AfterParse.
+	// The producer func must return a correct receiver or an error. If case of error, seelog will behave
+	// in the same way as with any other config error.
+	//
+	// You may use this param to set custom producers in case you need to pass some context when instantiating
+	// a custom receiver or if you frequently change custom receivers with different parameters or in any other
+	// situation where package-level registering (RegisterReceiver) is not an option for you.
+	CustomReceiverProducers map[string]CustomReceiverProducer
+}
+
+func (cfg *CfgParseParams) String() string {
+	return fmt.Sprintf("CfgParams: {custom_recs=%d}", len(cfg.CustomReceiverProducers))
+}
+
+type elementMapEntry struct {
+	constructor func(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error)
+}
+
+var elementMap map[string]elementMapEntry
+var predefinedFormats map[string]*formatter
+
+func init() {
+	elementMap = map[string]elementMapEntry{
+		fileWriterID:         {createfileWriter},
+		splitterDispatcherID: {createSplitter},
+		customReceiverID:     {createCustomReceiver},
+		filterDispatcherID:   {createFilter},
+		consoleWriterID:      {createConsoleWriter},
+		rollingfileWriterID:  {createRollingFileWriter},
+		bufferedWriterID:     {createbufferedWriter},
+		smtpWriterID:         {createSMTPWriter},
+		connWriterID:         {createconnWriter},
+	}
+
+	err := fillPredefinedFormats()
+	if err != nil {
+		panic(fmt.Sprintf("Seelog couldn't start: predefined formats creation failed. Error: %s", err.Error()))
+	}
+}
+
+func fillPredefinedFormats() error {
+	predefinedFormatsWithoutPrefix := map[string]string{
+		"xml-debug":       `<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg><path>%RelFile</path><func>%Func</func><line>%Line</line>`,
+		"xml-debug-short": `<t>%Ns</t><l>%l</l><m>%Msg</m><p>%RelFile</p><f>%Func</f>`,
+		"xml":             `<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg>`,
+		"xml-short":       `<t>%Ns</t><l>%l</l><m>%Msg</m>`,
+
+		"json-debug":       `{"time":%Ns,"lev":"%Lev","msg":"%Msg","path":"%RelFile","func":"%Func","line":"%Line"}`,
+		"json-debug-short": `{"t":%Ns,"l":"%Lev","m":"%Msg","p":"%RelFile","f":"%Func"}`,
+		"json":             `{"time":%Ns,"lev":"%Lev","msg":"%Msg"}`,
+		"json-short":       `{"t":%Ns,"l":"%Lev","m":"%Msg"}`,
+
+		"debug":       `[%LEVEL] %RelFile:%Func.%Line %Date %Time %Msg%n`,
+		"debug-short": `[%LEVEL] %Date %Time %Msg%n`,
+		"fast":        `%Ns %l %Msg%n`,
+	}
+
+	predefinedFormats = make(map[string]*formatter)
+
+	for formatKey, format := range predefinedFormatsWithoutPrefix {
+		formatter, err := NewFormatter(format)
+		if err != nil {
+			return err
+		}
+
+		predefinedFormats[predefinedPrefix+formatKey] = formatter
+	}
+
+	return nil
+}
+
+// configFromXMLDecoder parses data from a given XML decoder.
+// Returns parsed config which can be used to create logger in case no errors occured.
+// Returns error if format is incorrect or anything happened.
+func configFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (*configForParsing, error) {
+	return configFromXMLDecoderWithConfig(xmlParser, rootNode, nil)
+}
+
+// configFromXMLDecoderWithConfig parses data from a given XML decoder.
+// Returns parsed config which can be used to create logger in case no errors occured.
+// Returns error if format is incorrect or anything happened.
+func configFromXMLDecoderWithConfig(xmlParser *xml.Decoder, rootNode xml.Token, cfg *CfgParseParams) (*configForParsing, error) {
+	_, ok := rootNode.(xml.StartElement)
+	if !ok {
+		return nil, errors.New("rootNode must be XML startElement")
+	}
+
+	config, err := unmarshalNode(xmlParser, rootNode)
+	if err != nil {
+		return nil, err
+	}
+	if config == nil {
+		return nil, errors.New("xml has no content")
+	}
+
+	return configFromXMLNodeWithConfig(config, cfg)
+}
+
+// configFromReader parses data from a given reader.
+// Returns parsed config which can be used to create logger in case no errors occured.
+// Returns error if format is incorrect or anything happened.
+func configFromReader(reader io.Reader) (*configForParsing, error) {
+	return configFromReaderWithConfig(reader, nil)
+}
+
+// configFromReaderWithConfig parses data from a given reader.
+// Returns parsed config which can be used to create logger in case no errors occured.
+// Returns error if format is incorrect or anything happened.
+func configFromReaderWithConfig(reader io.Reader, cfg *CfgParseParams) (*configForParsing, error) {
+	config, err := unmarshalConfig(reader)
+	if err != nil {
+		return nil, err
+	}
+
+	if config.name != seelogConfigID {
+		return nil, errors.New("root xml tag must be '" + seelogConfigID + "'")
+	}
+
+	return configFromXMLNodeWithConfig(config, cfg)
+}
+
+func configFromXMLNodeWithConfig(config *xmlNode, cfg *CfgParseParams) (*configForParsing, error) {
+	err := checkUnexpectedAttribute(
+		config,
+		minLevelID,
+		maxLevelID,
+		levelsID,
+		loggerTypeFromStringAttr,
+		asyncLoggerIntervalAttr,
+		adaptLoggerMinIntervalAttr,
+		adaptLoggerMaxIntervalAttr,
+		adaptLoggerCriticalMsgCountAttr,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	err = checkExpectedElements(config, optionalElement(outputsID), optionalElement(formatsID), optionalElement(exceptionsID))
+	if err != nil {
+		return nil, err
+	}
+
+	constraints, err := getConstraints(config)
+	if err != nil {
+		return nil, err
+	}
+
+	exceptions, err := getExceptions(config)
+	if err != nil {
+		return nil, err
+	}
+	err = checkDistinctExceptions(exceptions)
+	if err != nil {
+		return nil, err
+	}
+
+	formats, err := getFormats(config)
+	if err != nil {
+		return nil, err
+	}
+
+	dispatcher, err := getOutputsTree(config, formats, cfg)
+	if err != nil {
+		// If we open several files, but then fail to parse the config, we should close
+		// those files before reporting that config is invalid.
+		if dispatcher != nil {
+			dispatcher.Close()
+		}
+
+		return nil, err
+	}
+
+	loggerType, logData, err := getloggerTypeFromStringData(config)
+	if err != nil {
+		return nil, err
+	}
+
+	return newFullLoggerConfig(constraints, exceptions, dispatcher, loggerType, logData, cfg)
+}
+
+func getConstraints(node *xmlNode) (logLevelConstraints, error) {
+	minLevelStr, isMinLevel := node.attributes[minLevelID]
+	maxLevelStr, isMaxLevel := node.attributes[maxLevelID]
+	levelsStr, isLevels := node.attributes[levelsID]
+
+	if isLevels && (isMinLevel && isMaxLevel) {
+		return nil, errors.New("for level declaration use '" + levelsID + "'' OR '" + minLevelID +
+			"', '" + maxLevelID + "'")
+	}
+
+	offString := LogLevel(Off).String()
+
+	if (isLevels && strings.TrimSpace(levelsStr) == offString) ||
+		(isMinLevel && !isMaxLevel && minLevelStr == offString) {
+
+		return NewOffConstraints()
+	}
+
+	if isLevels {
+		levels, err := parseLevels(levelsStr)
+		if err != nil {
+			return nil, err
+		}
+		return NewListConstraints(levels)
+	}
+
+	var minLevel = LogLevel(TraceLvl)
+	if isMinLevel {
+		found := true
+		minLevel, found = LogLevelFromString(minLevelStr)
+		if !found {
+			return nil, errors.New("declared " + minLevelID + " not found: " + minLevelStr)
+		}
+	}
+
+	var maxLevel = LogLevel(CriticalLvl)
+	if isMaxLevel {
+		found := true
+		maxLevel, found = LogLevelFromString(maxLevelStr)
+		if !found {
+			return nil, errors.New("declared " + maxLevelID + " not found: " + maxLevelStr)
+		}
+	}
+
+	return NewMinMaxConstraints(minLevel, maxLevel)
+}
+
+func parseLevels(str string) ([]LogLevel, error) {
+	levelsStrArr := strings.Split(strings.Replace(str, " ", "", -1), ",")
+	var levels []LogLevel
+	for _, levelStr := range levelsStrArr {
+		level, found := LogLevelFromString(levelStr)
+		if !found {
+			return nil, errors.New("declared level not found: " + levelStr)
+		}
+
+		levels = append(levels, level)
+	}
+
+	return levels, nil
+}
+
+func getExceptions(config *xmlNode) ([]*LogLevelException, error) {
+	var exceptions []*LogLevelException
+
+	var exceptionsNode *xmlNode
+	for _, child := range config.children {
+		if child.name == exceptionsID {
+			exceptionsNode = child
+			break
+		}
+	}
+
+	if exceptionsNode == nil {
+		return exceptions, nil
+	}
+
+	err := checkUnexpectedAttribute(exceptionsNode)
+	if err != nil {
+		return nil, err
+	}
+
+	err = checkExpectedElements(exceptionsNode, multipleMandatoryElements("exception"))
+	if err != nil {
+		return nil, err
+	}
+
+	for _, exceptionNode := range exceptionsNode.children {
+		if exceptionNode.name != exceptionID {
+			return nil, errors.New("incorrect nested element in exceptions section: " + exceptionNode.name)
+		}
+
+		err := checkUnexpectedAttribute(exceptionNode, minLevelID, maxLevelID, levelsID, funcPatternID, filePatternID)
+		if err != nil {
+			return nil, err
+		}
+
+		constraints, err := getConstraints(exceptionNode)
+		if err != nil {
+			return nil, errors.New("incorrect " + exceptionsID + " node: " + err.Error())
+		}
+
+		funcPattern, isFuncPattern := exceptionNode.attributes[funcPatternID]
+		filePattern, isFilePattern := exceptionNode.attributes[filePatternID]
+		if !isFuncPattern {
+			funcPattern = "*"
+		}
+		if !isFilePattern {
+			filePattern = "*"
+		}
+
+		exception, err := NewLogLevelException(funcPattern, filePattern, constraints)
+		if err != nil {
+			return nil, errors.New("incorrect exception node: " + err.Error())
+		}
+
+		exceptions = append(exceptions, exception)
+	}
+
+	return exceptions, nil
+}
+
+func checkDistinctExceptions(exceptions []*LogLevelException) error {
+	for i, exception := range exceptions {
+		for j, exception1 := range exceptions {
+			if i == j {
+				continue
+			}
+
+			if exception.FuncPattern() == exception1.FuncPattern() &&
+				exception.FilePattern() == exception1.FilePattern() {
+
+				return fmt.Errorf("there are two or more duplicate exceptions. Func: %v, file %v",
+					exception.FuncPattern(), exception.FilePattern())
+			}
+		}
+	}
+
+	return nil
+}
+
+func getFormats(config *xmlNode) (map[string]*formatter, error) {
+	formats := make(map[string]*formatter, 0)
+
+	var formatsNode *xmlNode
+	for _, child := range config.children {
+		if child.name == formatsID {
+			formatsNode = child
+			break
+		}
+	}
+
+	if formatsNode == nil {
+		return formats, nil
+	}
+
+	err := checkUnexpectedAttribute(formatsNode)
+	if err != nil {
+		return nil, err
+	}
+
+	err = checkExpectedElements(formatsNode, multipleMandatoryElements("format"))
+	if err != nil {
+		return nil, err
+	}
+
+	for _, formatNode := range formatsNode.children {
+		if formatNode.name != formatID {
+			return nil, errors.New("incorrect nested element in " + formatsID + " section: " + formatNode.name)
+		}
+
+		err := checkUnexpectedAttribute(formatNode, formatKeyAttrID, formatID)
+		if err != nil {
+			return nil, err
+		}
+
+		id, isID := formatNode.attributes[formatKeyAttrID]
+		formatStr, isFormat := formatNode.attributes[formatAttrID]
+		if !isID {
+			return nil, errors.New("format has no '" + formatKeyAttrID + "' attribute")
+		}
+		if !isFormat {
+			return nil, errors.New("format[" + id + "] has no '" + formatAttrID + "' attribute")
+		}
+
+		formatter, err := NewFormatter(formatStr)
+		if err != nil {
+			return nil, err
+		}
+
+		formats[id] = formatter
+	}
+
+	return formats, nil
+}
+
+func getloggerTypeFromStringData(config *xmlNode) (logType loggerTypeFromString, logData interface{}, err error) {
+	logTypeStr, loggerTypeExists := config.attributes[loggerTypeFromStringAttr]
+
+	if !loggerTypeExists {
+		return defaultloggerTypeFromString, nil, nil
+	}
+
+	logType, found := getLoggerTypeFromString(logTypeStr)
+
+	if !found {
+		return 0, nil, fmt.Errorf("unknown logger type: %s", logTypeStr)
+	}
+
+	if logType == asyncTimerloggerTypeFromString {
+		intervalStr, intervalExists := config.attributes[asyncLoggerIntervalAttr]
+		if !intervalExists {
+			return 0, nil, newMissingArgumentError(config.name, asyncLoggerIntervalAttr)
+		}
+
+		interval, err := strconv.ParseUint(intervalStr, 10, 32)
+		if err != nil {
+			return 0, nil, err
+		}
+
+		logData = asyncTimerLoggerData{uint32(interval)}
+	} else if logType == adaptiveLoggerTypeFromString {
+
+		// Min interval
+		minIntStr, minIntExists := config.attributes[adaptLoggerMinIntervalAttr]
+		if !minIntExists {
+			return 0, nil, newMissingArgumentError(config.name, adaptLoggerMinIntervalAttr)
+		}
+		minInterval, err := strconv.ParseUint(minIntStr, 10, 32)
+		if err != nil {
+			return 0, nil, err
+		}
+
+		// Max interval
+		maxIntStr, maxIntExists := config.attributes[adaptLoggerMaxIntervalAttr]
+		if !maxIntExists {
+			return 0, nil, newMissingArgumentError(config.name, adaptLoggerMaxIntervalAttr)
+		}
+		maxInterval, err := strconv.ParseUint(maxIntStr, 10, 32)
+		if err != nil {
+			return 0, nil, err
+		}
+
+		// Critical msg count
+		criticalMsgCountStr, criticalMsgCountExists := config.attributes[adaptLoggerCriticalMsgCountAttr]
+		if !criticalMsgCountExists {
+			return 0, nil, newMissingArgumentError(config.name, adaptLoggerCriticalMsgCountAttr)
+		}
+		criticalMsgCount, err := strconv.ParseUint(criticalMsgCountStr, 10, 32)
+		if err != nil {
+			return 0, nil, err
+		}
+
+		logData = adaptiveLoggerData{uint32(minInterval), uint32(maxInterval), uint32(criticalMsgCount)}
+	}
+
+	return logType, logData, nil
+}
+
+func getOutputsTree(config *xmlNode, formats map[string]*formatter, cfg *CfgParseParams) (dispatcherInterface, error) {
+	var outputsNode *xmlNode
+	for _, child := range config.children {
+		if child.name == outputsID {
+			outputsNode = child
+			break
+		}
+	}
+
+	if outputsNode != nil {
+		err := checkUnexpectedAttribute(outputsNode, outputFormatID)
+		if err != nil {
+			return nil, err
+		}
+
+		formatter, err := getCurrentFormat(outputsNode, DefaultFormatter, formats)
+		if err != nil {
+			return nil, err
+		}
+
+		output, err := createSplitter(outputsNode, formatter, formats, cfg)
+		if err != nil {
+			return nil, err
+		}
+
+		dispatcher, ok := output.(dispatcherInterface)
+		if ok {
+			return dispatcher, nil
+		}
+	}
+
+	console, err := NewConsoleWriter()
+	if err != nil {
+		return nil, err
+	}
+	return NewSplitDispatcher(DefaultFormatter, []interface{}{console})
+}
+
+func getCurrentFormat(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter) (*formatter, error) {
+	formatID, isFormatID := node.attributes[outputFormatID]
+	if !isFormatID {
+		return formatFromParent, nil
+	}
+
+	format, ok := formats[formatID]
+	if ok {
+		return format, nil
+	}
+
+	// Test for predefined format match
+	pdFormat, pdOk := predefinedFormats[formatID]
+
+	if !pdOk {
+		return nil, errors.New("formatid = '" + formatID + "' doesn't exist")
+	}
+
+	return pdFormat, nil
+}
+
+func createInnerReceivers(node *xmlNode, format *formatter, formats map[string]*formatter, cfg *CfgParseParams) ([]interface{}, error) {
+	var outputs []interface{}
+	for _, childNode := range node.children {
+		entry, ok := elementMap[childNode.name]
+		if !ok {
+			return nil, errors.New("unnknown tag '" + childNode.name + "' in outputs section")
+		}
+
+		output, err := entry.constructor(childNode, format, formats, cfg)
+		if err != nil {
+			return nil, err
+		}
+
+		outputs = append(outputs, output)
+	}
+
+	return outputs, nil
+}
+
+func createSplitter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	err := checkUnexpectedAttribute(node, outputFormatID)
+	if err != nil {
+		return nil, err
+	}
+
+	if !node.hasChildren() {
+		return nil, errNodeMustHaveChildren
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	receivers, err := createInnerReceivers(node, currentFormat, formats, cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewSplitDispatcher(currentFormat, receivers)
+}
+
+func createCustomReceiver(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	dataCustomPrefixes := make(map[string]string)
+	// Expecting only 'formatid', 'name' and 'data-' attrs
+	for attr, attrval := range node.attributes {
+		isExpected := false
+		if attr == outputFormatID ||
+			attr == customNameAttrID {
+			isExpected = true
+		}
+		if strings.HasPrefix(attr, customNameDataAttrPrefix) {
+			dataCustomPrefixes[attr[len(customNameDataAttrPrefix):]] = attrval
+			isExpected = true
+		}
+		if !isExpected {
+			return nil, newUnexpectedAttributeError(node.name, attr)
+		}
+	}
+
+	if node.hasChildren() {
+		return nil, errNodeCannotHaveChildren
+	}
+	customName, hasCustomName := node.attributes[customNameAttrID]
+	if !hasCustomName {
+		return nil, newMissingArgumentError(node.name, customNameAttrID)
+	}
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+	args := CustomReceiverInitArgs{
+		XmlCustomAttrs: dataCustomPrefixes,
+	}
+
+	if cfg != nil && cfg.CustomReceiverProducers != nil {
+		if prod, ok := cfg.CustomReceiverProducers[customName]; ok {
+			rec, err := prod(args)
+			if err != nil {
+				return nil, err
+			}
+			creceiver, err := NewCustomReceiverDispatcherByValue(currentFormat, rec, customName, args)
+			if err != nil {
+				return nil, err
+			}
+			err = rec.AfterParse(args)
+			if err != nil {
+				return nil, err
+			}
+			return creceiver, nil
+		}
+	}
+
+	return NewCustomReceiverDispatcher(currentFormat, customName, args)
+}
+
+func createFilter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	err := checkUnexpectedAttribute(node, outputFormatID, filterLevelsAttrID)
+	if err != nil {
+		return nil, err
+	}
+
+	if !node.hasChildren() {
+		return nil, errNodeMustHaveChildren
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	levelsStr, isLevels := node.attributes[filterLevelsAttrID]
+	if !isLevels {
+		return nil, newMissingArgumentError(node.name, filterLevelsAttrID)
+	}
+
+	levels, err := parseLevels(levelsStr)
+	if err != nil {
+		return nil, err
+	}
+
+	receivers, err := createInnerReceivers(node, currentFormat, formats, cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewFilterDispatcher(currentFormat, receivers, levels...)
+}
+
+func createfileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	err := checkUnexpectedAttribute(node, outputFormatID, pathID)
+	if err != nil {
+		return nil, err
+	}
+
+	if node.hasChildren() {
+		return nil, errNodeCannotHaveChildren
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	path, isPath := node.attributes[pathID]
+	if !isPath {
+		return nil, newMissingArgumentError(node.name, pathID)
+	}
+
+	fileWriter, err := NewFileWriter(path)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewFormattedWriter(fileWriter, currentFormat)
+}
+
+// Creates new SMTP writer if encountered in the config file.
+func createSMTPWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	err := checkUnexpectedAttribute(node, outputFormatID, senderaddressID, senderNameID, hostNameID, hostPortID, userNameID, userPassID, subjectID)
+	if err != nil {
+		return nil, err
+	}
+	// Node must have children.
+	if !node.hasChildren() {
+		return nil, errNodeMustHaveChildren
+	}
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+	senderAddress, ok := node.attributes[senderaddressID]
+	if !ok {
+		return nil, newMissingArgumentError(node.name, senderaddressID)
+	}
+	senderName, ok := node.attributes[senderNameID]
+	if !ok {
+		return nil, newMissingArgumentError(node.name, senderNameID)
+	}
+	// Process child nodes scanning for recipient email addresses and/or CA certificate paths.
+	var recipientAddresses []string
+	var caCertDirPaths []string
+	var mailHeaders []string
+	for _, childNode := range node.children {
+		switch childNode.name {
+		// Extract recipient address from child nodes.
+		case recipientID:
+			address, ok := childNode.attributes[addressID]
+			if !ok {
+				return nil, newMissingArgumentError(childNode.name, addressID)
+			}
+			recipientAddresses = append(recipientAddresses, address)
+		// Extract CA certificate file path from child nodes.
+		case cACertDirpathID:
+			path, ok := childNode.attributes[pathID]
+			if !ok {
+				return nil, newMissingArgumentError(childNode.name, pathID)
+			}
+			caCertDirPaths = append(caCertDirPaths, path)
+
+		// Extract email headers from child nodes.
+		case mailHeaderID:
+			headerName, ok := childNode.attributes[mailHeaderNameID]
+			if !ok {
+				return nil, newMissingArgumentError(childNode.name, mailHeaderNameID)
+			}
+
+			headerValue, ok := childNode.attributes[mailHeaderValueID]
+			if !ok {
+				return nil, newMissingArgumentError(childNode.name, mailHeaderValueID)
+			}
+
+			// Build header line
+			mailHeaders = append(mailHeaders, fmt.Sprintf("%s: %s", headerName, headerValue))
+		default:
+			return nil, newUnexpectedChildElementError(childNode.name)
+		}
+	}
+	hostName, ok := node.attributes[hostNameID]
+	if !ok {
+		return nil, newMissingArgumentError(node.name, hostNameID)
+	}
+
+	hostPort, ok := node.attributes[hostPortID]
+	if !ok {
+		return nil, newMissingArgumentError(node.name, hostPortID)
+	}
+
+	// Check if the string can really be converted into int.
+	if _, err := strconv.Atoi(hostPort); err != nil {
+		return nil, errors.New("invalid host port number")
+	}
+
+	userName, ok := node.attributes[userNameID]
+	if !ok {
+		return nil, newMissingArgumentError(node.name, userNameID)
+	}
+
+	userPass, ok := node.attributes[userPassID]
+	if !ok {
+		return nil, newMissingArgumentError(node.name, userPassID)
+	}
+
+	// subject is optionally set by configuration.
+	// default value is defined by DefaultSubjectPhrase constant in the writers_smtpwriter.go
+	var subjectPhrase = DefaultSubjectPhrase
+
+	subject, ok := node.attributes[subjectID]
+	if ok {
+		subjectPhrase = subject
+	}
+
+	smtpWriter := NewSMTPWriter(
+		senderAddress,
+		senderName,
+		recipientAddresses,
+		hostName,
+		hostPort,
+		userName,
+		userPass,
+		caCertDirPaths,
+		subjectPhrase,
+		mailHeaders,
+	)
+
+	return NewFormattedWriter(smtpWriter, currentFormat)
+}
+
+func createConsoleWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	err := checkUnexpectedAttribute(node, outputFormatID)
+	if err != nil {
+		return nil, err
+	}
+
+	if node.hasChildren() {
+		return nil, errNodeCannotHaveChildren
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	consoleWriter, err := NewConsoleWriter()
+	if err != nil {
+		return nil, err
+	}
+
+	return NewFormattedWriter(consoleWriter, currentFormat)
+}
+
+func createconnWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	if node.hasChildren() {
+		return nil, errNodeCannotHaveChildren
+	}
+
+	err := checkUnexpectedAttribute(node, outputFormatID, connWriterAddrAttr, connWriterNetAttr, connWriterReconnectOnMsgAttr, connWriterUseTLSAttr, connWriterInsecureSkipVerifyAttr)
+	if err != nil {
+		return nil, err
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	addr, isAddr := node.attributes[connWriterAddrAttr]
+	if !isAddr {
+		return nil, newMissingArgumentError(node.name, connWriterAddrAttr)
+	}
+
+	net, isNet := node.attributes[connWriterNetAttr]
+	if !isNet {
+		return nil, newMissingArgumentError(node.name, connWriterNetAttr)
+	}
+
+	reconnectOnMsg := false
+	reconnectOnMsgStr, isReconnectOnMsgStr := node.attributes[connWriterReconnectOnMsgAttr]
+	if isReconnectOnMsgStr {
+		if reconnectOnMsgStr == "true" {
+			reconnectOnMsg = true
+		} else if reconnectOnMsgStr == "false" {
+			reconnectOnMsg = false
+		} else {
+			return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterReconnectOnMsgAttr + "' attribute value")
+		}
+	}
+
+	useTLS := false
+	useTLSStr, isUseTLSStr := node.attributes[connWriterUseTLSAttr]
+	if isUseTLSStr {
+		if useTLSStr == "true" {
+			useTLS = true
+		} else if useTLSStr == "false" {
+			useTLS = false
+		} else {
+			return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterUseTLSAttr + "' attribute value")
+		}
+		if useTLS {
+			insecureSkipVerify := false
+			insecureSkipVerifyStr, isInsecureSkipVerify := node.attributes[connWriterInsecureSkipVerifyAttr]
+			if isInsecureSkipVerify {
+				if insecureSkipVerifyStr == "true" {
+					insecureSkipVerify = true
+				} else if insecureSkipVerifyStr == "false" {
+					insecureSkipVerify = false
+				} else {
+					return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterInsecureSkipVerifyAttr + "' attribute value")
+				}
+			}
+			config := tls.Config{InsecureSkipVerify: insecureSkipVerify}
+			connWriter := newTLSWriter(net, addr, reconnectOnMsg, &config)
+			return NewFormattedWriter(connWriter, currentFormat)
+		}
+	}
+
+	connWriter := NewConnWriter(net, addr, reconnectOnMsg)
+
+	return NewFormattedWriter(connWriter, currentFormat)
+}
+
+func createRollingFileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	if node.hasChildren() {
+		return nil, errNodeCannotHaveChildren
+	}
+
+	rollingTypeStr, isRollingType := node.attributes[rollingFileTypeAttr]
+	if !isRollingType {
+		return nil, newMissingArgumentError(node.name, rollingFileTypeAttr)
+	}
+
+	rollingType, ok := rollingTypeFromString(rollingTypeStr)
+	if !ok {
+		return nil, errors.New("unknown rolling file type: " + rollingTypeStr)
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	path, isPath := node.attributes[rollingFilePathAttr]
+	if !isPath {
+		return nil, newMissingArgumentError(node.name, rollingFilePathAttr)
+	}
+
+	rollingArchiveStr, archiveAttrExists := node.attributes[rollingFileArchiveAttr]
+
+	var rArchiveType rollingArchiveType
+	var rArchivePath string
+	var rArchiveExploded bool = false
+	if !archiveAttrExists {
+		rArchiveType = rollingArchiveNone
+		rArchivePath = ""
+	} else {
+		rArchiveType, ok = rollingArchiveTypeFromString(rollingArchiveStr)
+		if !ok {
+			return nil, errors.New("unknown rolling archive type: " + rollingArchiveStr)
+		}
+
+		if rArchiveType == rollingArchiveNone {
+			rArchivePath = ""
+		} else {
+			if rArchiveExplodedAttr, ok := node.attributes[rollingFileArchiveExplodedAttr]; ok {
+				if rArchiveExploded, err = strconv.ParseBool(rArchiveExplodedAttr); err != nil {
+					return nil, fmt.Errorf("archive exploded should be true or false, but was %v",
+						rArchiveExploded)
+				}
+			}
+
+			rArchivePath, ok = node.attributes[rollingFileArchivePathAttr]
+			if ok {
+				if rArchivePath == "" {
+					return nil, fmt.Errorf("empty archive path is not supported")
+				}
+			} else {
+				if rArchiveExploded {
+					rArchivePath = rollingArchiveDefaultExplodedName
+
+				} else {
+					rArchivePath, err = rollingArchiveTypeDefaultName(rArchiveType, false)
+					if err != nil {
+						return nil, err
+					}
+				}
+			}
+		}
+	}
+
+	nameMode := rollingNameMode(rollingNameModePostfix)
+	nameModeStr, ok := node.attributes[rollingFileNameModeAttr]
+	if ok {
+		mode, found := rollingNameModeFromString(nameModeStr)
+		if !found {
+			return nil, errors.New("unknown rolling filename mode: " + nameModeStr)
+		} else {
+			nameMode = mode
+		}
+	}
+
+	if rollingType == rollingTypeSize {
+		err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr,
+			rollingFileMaxSizeAttr, rollingFileMaxRollsAttr, rollingFileArchiveAttr,
+			rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr)
+		if err != nil {
+			return nil, err
+		}
+
+		maxSizeStr, ok := node.attributes[rollingFileMaxSizeAttr]
+		if !ok {
+			return nil, newMissingArgumentError(node.name, rollingFileMaxSizeAttr)
+		}
+
+		maxSize, err := strconv.ParseInt(maxSizeStr, 10, 64)
+		if err != nil {
+			return nil, err
+		}
+
+		maxRolls := 0
+		maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr]
+		if ok {
+			maxRolls, err = strconv.Atoi(maxRollsStr)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		rollingWriter, err := NewRollingFileWriterSize(path, rArchiveType, rArchivePath, maxSize, maxRolls, nameMode, rArchiveExploded)
+		if err != nil {
+			return nil, err
+		}
+
+		return NewFormattedWriter(rollingWriter, currentFormat)
+
+	} else if rollingType == rollingTypeTime {
+		err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr,
+			rollingFileDataPatternAttr, rollingFileArchiveAttr, rollingFileMaxRollsAttr,
+			rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr,
+			rollingFileFullNameAttr)
+		if err != nil {
+			return nil, err
+		}
+
+		maxRolls := 0
+		maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr]
+		if ok {
+			maxRolls, err = strconv.Atoi(maxRollsStr)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		fullName := false
+		fn, ok := node.attributes[rollingFileFullNameAttr]
+		if ok {
+			if fn == "true" {
+				fullName = true
+			} else if fn == "false" {
+				fullName = false
+			} else {
+				return nil, errors.New("node '" + node.name + "' has incorrect '" + rollingFileFullNameAttr + "' attribute value")
+			}
+		}
+
+		dataPattern, ok := node.attributes[rollingFileDataPatternAttr]
+		if !ok {
+			return nil, newMissingArgumentError(node.name, rollingFileDataPatternAttr)
+		}
+
+		rollingWriter, err := NewRollingFileWriterTime(path, rArchiveType, rArchivePath, maxRolls, dataPattern, nameMode, rArchiveExploded, fullName)
+		if err != nil {
+			return nil, err
+		}
+
+		return NewFormattedWriter(rollingWriter, currentFormat)
+	}
+
+	return nil, errors.New("incorrect rolling writer type " + rollingTypeStr)
+}
+
+func createbufferedWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
+	err := checkUnexpectedAttribute(node, outputFormatID, bufferedSizeAttr, bufferedFlushPeriodAttr)
+	if err != nil {
+		return nil, err
+	}
+
+	if !node.hasChildren() {
+		return nil, errNodeMustHaveChildren
+	}
+
+	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
+	if err != nil {
+		return nil, err
+	}
+
+	sizeStr, isSize := node.attributes[bufferedSizeAttr]
+	if !isSize {
+		return nil, newMissingArgumentError(node.name, bufferedSizeAttr)
+	}
+
+	size, err := strconv.Atoi(sizeStr)
+	if err != nil {
+		return nil, err
+	}
+
+	flushPeriod := 0
+	flushPeriodStr, isFlushPeriod := node.attributes[bufferedFlushPeriodAttr]
+	if isFlushPeriod {
+		flushPeriod, err = strconv.Atoi(flushPeriodStr)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Inner writer couldn't have its own format, so we pass 'currentFormat' as its parent format
+	receivers, err := createInnerReceivers(node, currentFormat, formats, cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	formattedWriter, ok := receivers[0].(*formattedWriter)
+	if !ok {
+		return nil, errors.New("buffered writer's child is not writer")
+	}
+
+	// ... and then we check that it hasn't changed
+	if formattedWriter.Format() != currentFormat {
+		return nil, errors.New("inner writer cannot have his own format")
+	}
+
+	bufferedWriter, err := NewBufferedWriter(formattedWriter.Writer(), size, time.Duration(flushPeriod))
+	if err != nil {
+		return nil, err
+	}
+
+	return NewFormattedWriter(bufferedWriter, currentFormat)
+}
+
+// Returns an error if node has any attributes not listed in expectedAttrs.
+func checkUnexpectedAttribute(node *xmlNode, expectedAttrs ...string) error {
+	for attr := range node.attributes {
+		isExpected := false
+		for _, expected := range expectedAttrs {
+			if attr == expected {
+				isExpected = true
+				break
+			}
+		}
+		if !isExpected {
+			return newUnexpectedAttributeError(node.name, attr)
+		}
+	}
+
+	return nil
+}
+
+type expectedElementInfo struct {
+	name      string
+	mandatory bool
+	multiple  bool
+}
+
+func optionalElement(name string) expectedElementInfo {
+	return expectedElementInfo{name, false, false}
+}
+func mandatoryElement(name string) expectedElementInfo {
+	return expectedElementInfo{name, true, false}
+}
+func multipleElements(name string) expectedElementInfo {
+	return expectedElementInfo{name, false, true}
+}
+func multipleMandatoryElements(name string) expectedElementInfo {
+	return expectedElementInfo{name, true, true}
+}
+
+func checkExpectedElements(node *xmlNode, elements ...expectedElementInfo) error {
+	for _, element := range elements {
+		count := 0
+		for _, child := range node.children {
+			if child.name == element.name {
+				count++
+			}
+		}
+
+		if count == 0 && element.mandatory {
+			return errors.New(node.name + " does not have mandatory subnode - " + element.name)
+		}
+		if count > 1 && !element.multiple {
+			return errors.New(node.name + " has more then one subnode - " + element.name)
+		}
+	}
+
+	for _, child := range node.children {
+		isExpected := false
+		for _, element := range elements {
+			if child.name == element.name {
+				isExpected = true
+			}
+		}
+
+		if !isExpected {
+			return errors.New(node.name + " has unexpected child: " + child.name)
+		}
+	}
+
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser_test.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser_test.go b/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser_test.go
new file mode 100644
index 0000000..7ca0b65
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/cfg_parser_test.go
@@ -0,0 +1,1150 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import (
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"testing"
+)
+
+type customTestReceiverOutput struct {
+	initCalled    bool
+	dataPassed    string
+	messageOutput string
+	levelOutput   LogLevel
+	closed        bool
+	flushed       bool
+}
+type customTestReceiver struct{ co *customTestReceiverOutput }
+
+func (cr *customTestReceiver) ReceiveMessage(message string, level LogLevel, context LogContextInterface) error {
+	cr.co.messageOutput = message
+	cr.co.levelOutput = level
+	return nil
+}
+
+func (cr *customTestReceiver) String() string {
+	return fmt.Sprintf("custom data='%s'", cr.co.dataPassed)
+}
+
+func (cr *customTestReceiver) AfterParse(initArgs CustomReceiverInitArgs) error {
+	cr.co = new(customTestReceiverOutput)
+	cr.co.initCalled = true
+	cr.co.dataPassed = initArgs.XmlCustomAttrs["test"]
+	return nil
+}
+
+func (cr *customTestReceiver) Flush() {
+	cr.co.flushed = true
+}
+
+func (cr *customTestReceiver) Close() error {
+	cr.co.closed = true
+	return nil
+}
+
+var re = regexp.MustCompile(`[^a-zA-Z0-9]+`)
+
+func getTestFileName(testName, postfix string) string {
+	if len(postfix) != 0 {
+		return strings.ToLower(re.ReplaceAllString(testName, "_")) + "_" + postfix + "_test.log"
+	}
+	return strings.ToLower(re.ReplaceAllString(testName, "_")) + "_test.log"
+}
+
+var parserTests []parserTest
+
+type parserTest struct {
+	testName      string
+	config        string
+	expected      *configForParsing //interface{}
+	errorExpected bool
+	parserConfig  *CfgParseParams
+}
+
+func getParserTests() []parserTest {
+	if parserTests == nil {
+		parserTests = make([]parserTest, 0)
+
+		testName := "Simple file output"
+		testLogFileName := getTestFileName(testName, "")
+		testConfig := `
+		<seelog>
+			<outputs>
+				<file path="` + testLogFileName + `"/>
+			</outputs>
+		</seelog>
+		`
+		testExpected := new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testfileWriter, _ := NewFileWriter(testLogFileName)
+		testHeadSplitter, _ := NewSplitDispatcher(DefaultFormatter, []interface{}{testfileWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Filter dispatcher"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<filter levels="debug, info, critical">
+					<file path="` + testLogFileName + `"/>
+				</filter>
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testfileWriter, _ = NewFileWriter(testLogFileName)
+		testFilter, _ := NewFilterDispatcher(DefaultFormatter, []interface{}{testfileWriter}, DebugLvl, InfoLvl, CriticalLvl)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testFilter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Console writer"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<console />
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ := NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "SMTP writer"
+		testConfig = `
+<seelog>
+	<outputs>
+		<smtp senderaddress="sa" sendername="sn"  hostname="hn" hostport="123" username="un" password="up">
+			<recipient address="ra1"/>
+			<recipient address="ra2"/>
+			<recipient address="ra3"/>
+			<cacertdirpath path="cacdp1"/>
+			<cacertdirpath path="cacdp2"/>
+		</smtp>
+	</outputs>
+</seelog>
+		`
+
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testSMTPWriter := NewSMTPWriter(
+			"sa",
+			"sn",
+			[]string{"ra1", "ra2", "ra3"},
+			"hn",
+			"123",
+			"un",
+			"up",
+			[]string{"cacdp1", "cacdp2"},
+			DefaultSubjectPhrase,
+			nil,
+		)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testSMTPWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "SMTP writer custom header and subject configuration"
+		testConfig = `
+<seelog>
+	<outputs>
+		<smtp senderaddress="sa" sendername="sn"  hostname="hn" hostport="123" username="un" password="up" subject="ohlala">
+			<recipient address="ra1"/>
+			<cacertdirpath path="cacdp1"/>
+			<header name="Priority" value="Urgent" />
+			<header name="Importance" value="high" />
+			<header name="Sensitivity" value="Company-Confidential" />
+			<header name="Auto-Submitted" value="auto-generated" />
+		</smtp>
+	</outputs>
+</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testSMTPWriter = NewSMTPWriter(
+			"sa",
+			"sn",
+			[]string{"ra1"},
+			"hn",
+			"123",
+			"un",
+			"up",
+			[]string{"cacdp1"},
+			"ohlala",
+			[]string{"Priority: Urgent", "Importance: high", "Sensitivity: Company-Confidential", "Auto-Submitted: auto-generated"},
+		)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testSMTPWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Default output"
+		testConfig = `
+		<seelog type="sync"/>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Asyncloop behavior"
+		testConfig = `
+		<seelog type="asyncloop"/>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Asynctimer behavior"
+		testConfig = `
+		<seelog type="asynctimer" asyncinterval="101"/>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncTimerloggerTypeFromString
+		testExpected.LoggerData = asyncTimerLoggerData{101}
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer size"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" />
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ := NewRollingFileWriterSize(testLogFileName, rollingArchiveNone, "", 100, 5, rollingNameModePostfix, false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer archive gzip"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="gzip"/>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ = NewRollingFileWriterSize(testLogFileName, rollingArchiveGzip, "log.tar.gz", 100, 5, rollingNameModePostfix, false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer archive zip"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="zip"/>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ = NewRollingFileWriterSize(testLogFileName, rollingArchiveZip, "log.zip", 100, 5, rollingNameModePostfix, false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer archive zip with specified path"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile namemode="prefix" type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="zip" archivepath="test.zip"/>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ = NewRollingFileWriterSize(testLogFileName, rollingArchiveZip, "test.zip", 100, 5, rollingNameModePrefix, false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer archive zip exploded"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="zip" archiveexploded="true"/>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ = NewRollingFileWriterSize(testLogFileName, rollingArchiveZip, "old", 100, 5, rollingNameModePostfix, true)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer archive zip exploded with specified path"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile namemode="prefix" type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="zip" archiveexploded="true" archivepath="test_old_logs"/>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ = NewRollingFileWriterSize(testLogFileName, rollingArchiveZip, "test_old_logs", 100, 5, rollingNameModePrefix, true)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer archive none"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile namemode="postfix" type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="none"/>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriter, _ = NewRollingFileWriterSize(testLogFileName, rollingArchiveNone, "", 100, 5, rollingNameModePostfix, false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Rolling file writer date"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile type="date" filename="` + testLogFileName + `" datepattern="2006-01-02T15:04:05Z07:00" />
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriterTime, _ := NewRollingFileWriterTime(testLogFileName, rollingArchiveNone, "", 0, "2006-01-02T15:04:05Z07:00", rollingNameModePostfix, false, false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testrollingFileWriterTime})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Buffered writer"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<buffered size="100500" flushperiod="100">
+					<rollingfile type="date" filename="` + testLogFileName + `" datepattern="2006-01-02T15:04:05Z07:00" />
+				</buffered>
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriterTime, _ = NewRollingFileWriterTime(testLogFileName, rollingArchiveNone, "", 0, "2006-01-02T15:04:05Z07:00", rollingNameModePostfix, false, false)
+		testbufferedWriter, _ := NewBufferedWriter(testrollingFileWriterTime, 100500, 100)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testbufferedWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Inner splitter output"
+		testLogFileName1 := getTestFileName(testName, "1")
+		testLogFileName2 := getTestFileName(testName, "2")
+		testLogFileName3 := getTestFileName(testName, "3")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<file path="` + testLogFileName1 + `"/>
+				<splitter>
+					<file path="` + testLogFileName2 + `"/>
+					<file path="` + testLogFileName3 + `"/>
+				</splitter>
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testfileWriter1, _ := NewFileWriter(testLogFileName2)
+		testfileWriter2, _ := NewFileWriter(testLogFileName3)
+		testInnerSplitter, _ := NewSplitDispatcher(DefaultFormatter, []interface{}{testfileWriter1, testfileWriter2})
+		testfileWriter, _ = NewFileWriter(testLogFileName1)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testfileWriter, testInnerSplitter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		RegisterReceiver("custom-name-1", &customTestReceiver{})
+
+		testName = "Custom receiver 1"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<custom name="custom-name-1" data-test="set"/>
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testCustomReceiver, _ := NewCustomReceiverDispatcher(DefaultFormatter, "custom-name-1", CustomReceiverInitArgs{
+			XmlCustomAttrs: map[string]string{
+				"test": "set",
+			},
+		})
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testCustomReceiver})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Custom receiver 2"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<custom name="custom-name-2" data-test="set2"/>
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		crec := &customTestReceiver{}
+		cargs := CustomReceiverInitArgs{
+			XmlCustomAttrs: map[string]string{
+				"test": "set2",
+			},
+		}
+		crec.AfterParse(cargs)
+		testCustomReceiver2, _ := NewCustomReceiverDispatcherByValue(DefaultFormatter, crec, "custom-name-2", cargs)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testCustomReceiver2})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		fnc := func(initArgs CustomReceiverInitArgs) (CustomReceiver, error) {
+			return &customTestReceiver{}, nil
+		}
+		cfg := CfgParseParams{
+			CustomReceiverProducers: map[string]CustomReceiverProducer{
+				"custom-name-2": CustomReceiverProducer(fnc),
+			},
+		}
+		testExpected.Params = &cfg
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, &cfg})
+
+		RegisterReceiver("-", &customTestReceiver{})
+		testName = "Custom receiver 3"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<custom name="-" data-test="set3"/>
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		creccustom := &customTestReceiver{}
+		cargs3 := CustomReceiverInitArgs{
+			XmlCustomAttrs: map[string]string{
+				"test": "set3",
+			},
+		}
+		creccustom.AfterParse(cargs3)
+		testCustomReceiver, _ = NewCustomReceiverDispatcherByValue(DefaultFormatter, creccustom, "-", cargs3)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testCustomReceiver})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Custom receivers with formats"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<custom name="custom-name-1" data-test="set1"/>
+				<custom name="custom-name-1" data-test="set2"/>
+				<custom name="custom-name-1" data-test="set3"/>
+			</outputs>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testCustomReceivers := make([]*customReceiverDispatcher, 3)
+		for i := 0; i < 3; i++ {
+			testCustomReceivers[i], _ = NewCustomReceiverDispatcher(DefaultFormatter, "custom-name-1", CustomReceiverInitArgs{
+				XmlCustomAttrs: map[string]string{
+					"test": fmt.Sprintf("set%d", i+1),
+				},
+			})
+		}
+
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testCustomReceivers[0], testCustomReceivers[1], testCustomReceivers[2]})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Format"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs formatid="dateFormat">
+				<file path="` + testLogFileName + `"/>
+			</outputs>
+			<formats>
+				<format id="dateFormat" format="%Level %Msg %File" />
+			</formats>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testfileWriter, _ = NewFileWriter(testLogFileName)
+		testFormat, _ := NewFormatter("%Level %Msg %File")
+		testHeadSplitter, _ = NewSplitDispatcher(testFormat, []interface{}{testfileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Format2"
+		testLogFileName = getTestFileName(testName, "")
+		testLogFileName1 = getTestFileName(testName, "1")
+		testConfig = `
+		<seelog type="sync">
+			<outputs formatid="format1">
+				<file path="` + testLogFileName + `"/>
+				<file formatid="format2" path="` + testLogFileName1 + `"/>
+			</outputs>
+			<formats>
+				<format id="format1" format="%Level %Msg %File" />
+				<format id="format2" format="%l %Msg" />
+			</formats>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testfileWriter, _ = NewFileWriter(testLogFileName)
+		testfileWriter1, _ = NewFileWriter(testLogFileName1)
+		testFormat1, _ := NewFormatter("%Level %Msg %File")
+		testFormat2, _ := NewFormatter("%l %Msg")
+		formattedWriter, _ := NewFormattedWriter(testfileWriter1, testFormat2)
+		testHeadSplitter, _ = NewSplitDispatcher(testFormat1, []interface{}{testfileWriter, formattedWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Minlevel = warn"
+		testConfig = `<seelog minlevel="warn"/>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(WarnLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Maxlevel = trace"
+		testConfig = `<seelog maxlevel="trace"/>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, TraceLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Level between info and error"
+		testConfig = `<seelog minlevel="info" maxlevel="error"/>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(InfoLvl, ErrorLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Off with minlevel"
+		testConfig = `<seelog minlevel="off"/>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewOffConstraints()
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Off with levels"
+		testConfig = `<seelog levels="off"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Levels list"
+		testConfig = `<seelog levels="debug, info, critical"/>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewListConstraints([]LogLevel{
+			DebugLvl, InfoLvl, CriticalLvl})
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = asyncLooploggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Errors #1"
+		testConfig = `<seelog minlevel="debug" minlevel="trace"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #2"
+		testConfig = `<seelog minlevel="error" maxlevel="debug"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #3"
+		testConfig = `<seelog maxlevel="debug" maxlevel="trace"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #4"
+		testConfig = `<seelog maxlevel="off"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #5"
+		testConfig = `<seelog minlevel="off" maxlevel="trace"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #6"
+		testConfig = `<seelog minlevel="warn" maxlevel="error" levels="debug"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #7"
+		testConfig = `<not_seelog/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #8"
+		testConfig = `<seelog levels="warn, debug, test"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #9"
+		testConfig = `<seelog levels=""/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #10"
+		testConfig = `<seelog levels="off" something="abc"/>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #11"
+		testConfig = `<seelog><output/></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #12"
+		testConfig = `<seelog><outputs/><outputs/></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #13"
+		testConfig = `<seelog><exceptions/></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #14"
+		testConfig = `<seelog><formats/></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #15"
+		testConfig = `<seelog><outputs><splitter/></outputs></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #16"
+		testConfig = `<seelog><outputs><filter/></outputs></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #17"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `<seelog><outputs><file path="` + testLogFileName + `"><something/></file></outputs></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #18"
+		testConfig = `<seelog><outputs><buffered size="100500" flushperiod="100"/></outputs></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #19"
+		testConfig = `<seelog><outputs></outputs></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Exceptions: restricting"
+		testConfig =
+			`
+		<seelog type="sync">
+			<exceptions>
+				<exception funcpattern="Test*" filepattern="someFile.go" minlevel="off"/>
+			</exceptions>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		listConstraint, _ := NewOffConstraints()
+		exception, _ := NewLogLevelException("Test*", "someFile.go", listConstraint)
+		testExpected.Exceptions = []*LogLevelException{exception}
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Exceptions: allowing #1"
+		testConfig =
+			`
+		<seelog type="sync" levels="error">
+			<exceptions>
+				<exception filepattern="testfile.go" minlevel="trace"/>
+			</exceptions>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewListConstraints([]LogLevel{ErrorLvl})
+		minMaxConstraint, _ := NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		exception, _ = NewLogLevelException("*", "testfile.go", minMaxConstraint)
+		testExpected.Exceptions = []*LogLevelException{exception}
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Exceptions: allowing #2"
+		testConfig = `
+		<seelog type="sync" levels="off">
+			<exceptions>
+				<exception filepattern="testfile.go" minlevel="warn"/>
+			</exceptions>
+		</seelog>
+		`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewOffConstraints()
+		minMaxConstraint, _ = NewMinMaxConstraints(WarnLvl, CriticalLvl)
+		exception, _ = NewLogLevelException("*", "testfile.go", minMaxConstraint)
+		testExpected.Exceptions = []*LogLevelException{exception}
+		testconsoleWriter, _ = NewConsoleWriter()
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testconsoleWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Predefined formats"
+		formatID := predefinedPrefix + "xml-debug-short"
+		testConfig = `
+		<seelog type="sync">
+			<outputs formatid="` + formatID + `">
+				<console />
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testconsoleWriter, _ = NewConsoleWriter()
+		testFormat, _ = predefinedFormats[formatID]
+		testHeadSplitter, _ = NewSplitDispatcher(testFormat, []interface{}{testconsoleWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Predefined formats redefine"
+		testLogFileName = getTestFileName(testName, "")
+		formatID = predefinedPrefix + "xml-debug-short"
+		testConfig = `
+		<seelog type="sync">
+			<outputs formatid="` + formatID + `">
+				<file path="` + testLogFileName + `"/>
+			</outputs>
+			<formats>
+				<format id="` + formatID + `" format="%Level %Msg %File" />
+			</formats>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testfileWriter, _ = NewFileWriter(testLogFileName)
+		testFormat, _ = NewFormatter("%Level %Msg %File")
+		testHeadSplitter, _ = NewSplitDispatcher(testFormat, []interface{}{testfileWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Conn writer 1"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<conn net="tcp" addr=":8888" />
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testConnWriter := NewConnWriter("tcp", ":8888", false)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testConnWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Conn writer 2"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<conn net="tcp" addr=":8888" reconnectonmsg="true" />
+			</outputs>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testConnWriter = NewConnWriter("tcp", ":8888", true)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{testConnWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+		testName = "Errors #11"
+		testConfig = `
+		<seelog type="sync"><exceptions>
+				<exception filepattern="testfile.go" minlevel="trace"/>
+				<exception filepattern="testfile.go" minlevel="warn"/>
+		</exceptions></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #12"
+		testConfig = `
+		<seelog type="sync"><exceptions>
+				<exception filepattern="!@+$)!!%&@(^$" minlevel="trace"/>
+		</exceptions></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #13"
+		testConfig = `
+		<seelog type="sync"><exceptions>
+				<exception filepattern="*" minlevel="unknown"/>
+		</exceptions></seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #14"
+		testConfig = `
+		<seelog type="sync" levels=\u201doff\u201d>
+			<exceptions>
+				<exception filepattern="testfile.go" minlevel="off"/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #15"
+		testConfig = `
+		<seelog type="sync" levels=\u201dtrace\u201d>
+			<exceptions>
+				<exception filepattern="testfile.go" levels="trace"/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #16"
+		testConfig = `
+		<seelog type="sync" minlevel=\u201dtrace\u201d>
+			<exceptions>
+				<exception filepattern="testfile.go" minlevel="trace"/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #17"
+		testConfig = `
+		<seelog type="sync" minlevel=\u201dtrace\u201d>
+			<exceptions>
+				<exception filepattern="testfile.go" minlevel="warn"/>
+			</exceptions>
+			<exceptions>
+				<exception filepattern="testfile.go" minlevel="warn"/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #18"
+		testConfig = `
+		<seelog type="sync" minlevel=\u201dtrace\u201d>
+			<exceptions>
+				<exception filepattern="testfile.go"/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #19"
+		testConfig = `
+		<seelog type="sync" minlevel=\u201dtrace\u201d>
+			<exceptions>
+				<exception minlevel="warn"/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #20"
+		testConfig = `
+		<seelog type="sync" minlevel=\u201dtrace\u201d>
+			<exceptions>
+				<exception/>
+			</exceptions>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #21"
+		testConfig = `
+		<seelog>
+			<outputs>
+				<splitter>
+				</splitter>
+			</outputs>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #22"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<filter levels="debug, info, critical">
+
+				</filter>
+			</outputs>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #23"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<buffered size="100500" flushperiod="100">
+
+				</buffered>
+			</outputs>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #24"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<buffered size="100500" flushperiod="100">
+					<rollingfile type="date" filename="` + testLogFileName + `" datepattern="2006-01-02T15:04:05Z07:00" formatid="testFormat"/>
+				</buffered>
+			</outputs>
+			<formats>
+				<format id="testFormat" format="%Level %Msg %File 123" />
+			</formats>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #25"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<outputs>
+					<file path="` + testLogFileName + `"/>
+				</outputs>
+				<outputs>
+					<file path="` + testLogFileName + `"/>
+				</outputs>
+			</outputs>
+		</seelog>
+		`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #26"
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<conn net="tcp" addr=":8888" reconnectonmsg="true1" />
+			</outputs>
+		</seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Errors #27"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<rollingfile type="size" filename="` + testLogFileName + `" maxsize="100" maxrolls="5" archivetype="zip" archivepath="" />
+			</outputs>
+		</seelog>`
+		parserTests = append(parserTests, parserTest{testName, testConfig, nil, true, nil})
+
+		testName = "Buffered writer same formatid override"
+		testLogFileName = getTestFileName(testName, "")
+		testConfig = `
+		<seelog type="sync">
+			<outputs>
+				<buffered size="100500" flushperiod="100" formatid="testFormat">
+					<rollingfile namemode="prefix" type="date" filename="` + testLogFileName + `" datepattern="2006-01-02T15:04:05Z07:00" formatid="testFormat"/>
+				</buffered>
+			</outputs>
+			<formats>
+				<format id="testFormat" format="%Level %Msg %File 123" />
+			</formats>
+		</seelog>`
+		testExpected = new(configForParsing)
+		testExpected.Constraints, _ = NewMinMaxConstraints(TraceLvl, CriticalLvl)
+		testExpected.Exceptions = nil
+		testrollingFileWriterTime, _ = NewRollingFileWriterTime(testLogFileName, rollingArchiveNone, "", 0, "2006-01-02T15:04:05Z07:00", rollingNameModePrefix, false, false)
+		testbufferedWriter, _ = NewBufferedWriter(testrollingFileWriterTime, 100500, 100)
+		testFormat, _ = NewFormatter("%Level %Msg %File 123")
+		formattedWriter, _ = NewFormattedWriter(testbufferedWriter, testFormat)
+		testHeadSplitter, _ = NewSplitDispatcher(DefaultFormatter, []interface{}{formattedWriter})
+		testExpected.LogType = syncloggerTypeFromString
+		testExpected.RootDispatcher = testHeadSplitter
+		parserTests = append(parserTests, parserTest{testName, testConfig, testExpected, false, nil})
+
+	}
+
+	return parserTests
+}
+
+// Temporary solution: compare by string identity. Not the best solution in
+// terms of performance, but a valid one in terms of comparison, because
+// every seelog dispatcher/receiver must have a valid String() func
+// that fully represents its internal parameters.
+func configsAreEqual(conf1 *configForParsing, conf2 interface{}) bool {
+	if conf1 == nil {
+		return conf2 == nil
+	}
+	if conf2 == nil {
+		return conf1 == nil
+	}
+
+	// configForParsing, ok := conf2 //.(*configForParsing)
+	// if !ok {
+	// 	return false
+	// }
+
+	return fmt.Sprintf("%v", conf1) == fmt.Sprintf("%v", conf2) //configForParsing)
+}
+
+func testLogFileFilter(fn string) bool {
+	return ".log" == filepath.Ext(fn)
+}
+
+func cleanupAfterCfgTest(t *testing.T) {
+	toDel, err := getDirFilePaths(".", testLogFileFilter, true)
+	if nil != err {
+		t.Fatal("Cannot list files in test directory!")
+	}
+
+	for _, p := range toDel {
+		err = tryRemoveFile(p)
+		if nil != err {
+			t.Errorf("cannot remove file %s in test directory: %s", p, err.Error())
+		}
+	}
+}
+
+func parseTest(test parserTest, t *testing.T) {
+	conf, err := configFromReaderWithConfig(strings.NewReader(test.config), test.parserConfig)
+	if /*err != nil &&*/ conf != nil && conf.RootDispatcher != nil {
+		defer func() {
+			if err = conf.RootDispatcher.Close(); err != nil {
+				t.Errorf("\n----ERROR while closing root dispatcher in %s test: %s", test.testName, err)
+			}
+		}()
+	}
+
+	if (err != nil) != test.errorExpected {
+		t.Errorf("\n----ERROR in %s:\nConfig: %s\n* Expected error:%t. Got error: %t\n",
+			test.testName, test.config, test.errorExpected, (err != nil))
+		if err != nil {
+			t.Logf("%s\n", err.Error())
+		}
+		return
+	}
+
+	if err == nil && !configsAreEqual(conf, test.expected) {
+		t.Errorf("\n----ERROR in %s:\nConfig: %s\n* Expected: %v. \n* Got: %v\n",
+			test.testName, test.config, test.expected, conf)
+	}
+}
+
+func TestParser(t *testing.T) {
+	defer cleanupAfterCfgTest(t)
+
+	for _, test := range getParserTests() {
+		parseTest(test, t)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/common_closer.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/common_closer.go b/traffic_stats/vendor/github.com/cihub/seelog/common_closer.go
new file mode 100644
index 0000000..1319c22
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/common_closer.go
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog