terraform/vendor/github.com/cihub/seelog/cfg_parser.go

1270 lines
38 KiB
Go

// 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
}