1// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// 1. Redistributions of source code must retain the above copyright notice, this
9//    list of conditions and the following disclaimer.
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11//    this list of conditions and the following disclaimer in the documentation
12//    and/or other materials provided with the distribution.
13//
14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25package seelog
26
27import (
28	"crypto/tls"
29	"encoding/xml"
30	"errors"
31	"fmt"
32	"io"
33	"strconv"
34	"strings"
35	"time"
36)
37
38// Names of elements of seelog config.
39const (
40	seelogConfigID                   = "seelog"
41	outputsID                        = "outputs"
42	formatsID                        = "formats"
43	minLevelID                       = "minlevel"
44	maxLevelID                       = "maxlevel"
45	levelsID                         = "levels"
46	exceptionsID                     = "exceptions"
47	exceptionID                      = "exception"
48	funcPatternID                    = "funcpattern"
49	filePatternID                    = "filepattern"
50	formatID                         = "format"
51	formatAttrID                     = "format"
52	formatKeyAttrID                  = "id"
53	outputFormatID                   = "formatid"
54	pathID                           = "path"
55	fileWriterID                     = "file"
56	smtpWriterID                     = "smtp"
57	senderaddressID                  = "senderaddress"
58	senderNameID                     = "sendername"
59	recipientID                      = "recipient"
60	mailHeaderID                     = "header"
61	mailHeaderNameID                 = "name"
62	mailHeaderValueID                = "value"
63	addressID                        = "address"
64	hostNameID                       = "hostname"
65	hostPortID                       = "hostport"
66	userNameID                       = "username"
67	userPassID                       = "password"
68	cACertDirpathID                  = "cacertdirpath"
69	subjectID                        = "subject"
70	splitterDispatcherID             = "splitter"
71	consoleWriterID                  = "console"
72	customReceiverID                 = "custom"
73	customNameAttrID                 = "name"
74	customNameDataAttrPrefix         = "data-"
75	filterDispatcherID               = "filter"
76	filterLevelsAttrID               = "levels"
77	rollingfileWriterID              = "rollingfile"
78	rollingFileTypeAttr              = "type"
79	rollingFilePathAttr              = "filename"
80	rollingFileMaxSizeAttr           = "maxsize"
81	rollingFileMaxRollsAttr          = "maxrolls"
82	rollingFileNameModeAttr          = "namemode"
83	rollingFileDataPatternAttr       = "datepattern"
84	rollingFileArchiveAttr           = "archivetype"
85	rollingFileArchivePathAttr       = "archivepath"
86	rollingFileArchiveExplodedAttr   = "archiveexploded"
87	rollingFileFullNameAttr          = "fullname"
88	bufferedWriterID                 = "buffered"
89	bufferedSizeAttr                 = "size"
90	bufferedFlushPeriodAttr          = "flushperiod"
91	loggerTypeFromStringAttr         = "type"
92	asyncLoggerIntervalAttr          = "asyncinterval"
93	adaptLoggerMinIntervalAttr       = "mininterval"
94	adaptLoggerMaxIntervalAttr       = "maxinterval"
95	adaptLoggerCriticalMsgCountAttr  = "critmsgcount"
96	predefinedPrefix                 = "std:"
97	connWriterID                     = "conn"
98	connWriterAddrAttr               = "addr"
99	connWriterNetAttr                = "net"
100	connWriterReconnectOnMsgAttr     = "reconnectonmsg"
101	connWriterUseTLSAttr             = "tls"
102	connWriterInsecureSkipVerifyAttr = "insecureskipverify"
103)
104
105// CustomReceiverProducer is the signature of the function CfgParseParams needs to create
106// custom receivers.
107type CustomReceiverProducer func(CustomReceiverInitArgs) (CustomReceiver, error)
108
109// CfgParseParams represent specific parse options or flags used by parser. It is used if seelog parser needs
110// some special directives or additional info to correctly parse a config.
111type CfgParseParams struct {
112	// CustomReceiverProducers expose the same functionality as RegisterReceiver func
113	// but only in the scope (context) of the config parse func instead of a global package scope.
114	//
115	// It means that if you use custom receivers in your code, you may either register them globally once with
116	// RegisterReceiver or you may call funcs like LoggerFromParamConfigAsFile (with 'ParamConfig')
117	// and use CustomReceiverProducers to provide custom producer funcs.
118	//
119	// A producer func is called when config parser processes a '<custom>' element. It takes the 'name' attribute
120	// of the element and tries to find a match in two places:
121	// 1) CfgParseParams.CustomReceiverProducers map
122	// 2) Global type map, filled by RegisterReceiver
123	//
124	// If a match is found in the CustomReceiverProducers map, parser calls the corresponding producer func
125	// passing the init args to it.	The func takes exactly the same args as CustomReceiver.AfterParse.
126	// The producer func must return a correct receiver or an error. If case of error, seelog will behave
127	// in the same way as with any other config error.
128	//
129	// You may use this param to set custom producers in case you need to pass some context when instantiating
130	// a custom receiver or if you frequently change custom receivers with different parameters or in any other
131	// situation where package-level registering (RegisterReceiver) is not an option for you.
132	CustomReceiverProducers map[string]CustomReceiverProducer
133}
134
135func (cfg *CfgParseParams) String() string {
136	return fmt.Sprintf("CfgParams: {custom_recs=%d}", len(cfg.CustomReceiverProducers))
137}
138
139type elementMapEntry struct {
140	constructor func(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error)
141}
142
143var elementMap map[string]elementMapEntry
144var predefinedFormats map[string]*formatter
145
146func init() {
147	elementMap = map[string]elementMapEntry{
148		fileWriterID:         {createfileWriter},
149		splitterDispatcherID: {createSplitter},
150		customReceiverID:     {createCustomReceiver},
151		filterDispatcherID:   {createFilter},
152		consoleWriterID:      {createConsoleWriter},
153		rollingfileWriterID:  {createRollingFileWriter},
154		bufferedWriterID:     {createbufferedWriter},
155		smtpWriterID:         {createSMTPWriter},
156		connWriterID:         {createconnWriter},
157	}
158
159	err := fillPredefinedFormats()
160	if err != nil {
161		panic(fmt.Sprintf("Seelog couldn't start: predefined formats creation failed. Error: %s", err.Error()))
162	}
163}
164
165func fillPredefinedFormats() error {
166	predefinedFormatsWithoutPrefix := map[string]string{
167		"xml-debug":       `<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg><path>%RelFile</path><func>%Func</func><line>%Line</line>`,
168		"xml-debug-short": `<t>%Ns</t><l>%l</l><m>%Msg</m><p>%RelFile</p><f>%Func</f>`,
169		"xml":             `<time>%Ns</time><lev>%Lev</lev><msg>%Msg</msg>`,
170		"xml-short":       `<t>%Ns</t><l>%l</l><m>%Msg</m>`,
171
172		"json-debug":       `{"time":%Ns,"lev":"%Lev","msg":"%Msg","path":"%RelFile","func":"%Func","line":"%Line"}`,
173		"json-debug-short": `{"t":%Ns,"l":"%Lev","m":"%Msg","p":"%RelFile","f":"%Func"}`,
174		"json":             `{"time":%Ns,"lev":"%Lev","msg":"%Msg"}`,
175		"json-short":       `{"t":%Ns,"l":"%Lev","m":"%Msg"}`,
176
177		"debug":       `[%LEVEL] %RelFile:%Func.%Line %Date %Time %Msg%n`,
178		"debug-short": `[%LEVEL] %Date %Time %Msg%n`,
179		"fast":        `%Ns %l %Msg%n`,
180	}
181
182	predefinedFormats = make(map[string]*formatter)
183
184	for formatKey, format := range predefinedFormatsWithoutPrefix {
185		formatter, err := NewFormatter(format)
186		if err != nil {
187			return err
188		}
189
190		predefinedFormats[predefinedPrefix+formatKey] = formatter
191	}
192
193	return nil
194}
195
196// configFromXMLDecoder parses data from a given XML decoder.
197// Returns parsed config which can be used to create logger in case no errors occured.
198// Returns error if format is incorrect or anything happened.
199func configFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (*configForParsing, error) {
200	return configFromXMLDecoderWithConfig(xmlParser, rootNode, nil)
201}
202
203// configFromXMLDecoderWithConfig parses data from a given XML decoder.
204// Returns parsed config which can be used to create logger in case no errors occured.
205// Returns error if format is incorrect or anything happened.
206func configFromXMLDecoderWithConfig(xmlParser *xml.Decoder, rootNode xml.Token, cfg *CfgParseParams) (*configForParsing, error) {
207	_, ok := rootNode.(xml.StartElement)
208	if !ok {
209		return nil, errors.New("rootNode must be XML startElement")
210	}
211
212	config, err := unmarshalNode(xmlParser, rootNode)
213	if err != nil {
214		return nil, err
215	}
216	if config == nil {
217		return nil, errors.New("xml has no content")
218	}
219
220	return configFromXMLNodeWithConfig(config, cfg)
221}
222
223// configFromReader parses data from a given reader.
224// Returns parsed config which can be used to create logger in case no errors occured.
225// Returns error if format is incorrect or anything happened.
226func configFromReader(reader io.Reader) (*configForParsing, error) {
227	return configFromReaderWithConfig(reader, nil)
228}
229
230// configFromReaderWithConfig parses data from a given reader.
231// Returns parsed config which can be used to create logger in case no errors occured.
232// Returns error if format is incorrect or anything happened.
233func configFromReaderWithConfig(reader io.Reader, cfg *CfgParseParams) (*configForParsing, error) {
234	config, err := unmarshalConfig(reader)
235	if err != nil {
236		return nil, err
237	}
238
239	if config.name != seelogConfigID {
240		return nil, errors.New("root xml tag must be '" + seelogConfigID + "'")
241	}
242
243	return configFromXMLNodeWithConfig(config, cfg)
244}
245
246func configFromXMLNodeWithConfig(config *xmlNode, cfg *CfgParseParams) (*configForParsing, error) {
247	err := checkUnexpectedAttribute(
248		config,
249		minLevelID,
250		maxLevelID,
251		levelsID,
252		loggerTypeFromStringAttr,
253		asyncLoggerIntervalAttr,
254		adaptLoggerMinIntervalAttr,
255		adaptLoggerMaxIntervalAttr,
256		adaptLoggerCriticalMsgCountAttr,
257	)
258	if err != nil {
259		return nil, err
260	}
261
262	err = checkExpectedElements(config, optionalElement(outputsID), optionalElement(formatsID), optionalElement(exceptionsID))
263	if err != nil {
264		return nil, err
265	}
266
267	constraints, err := getConstraints(config)
268	if err != nil {
269		return nil, err
270	}
271
272	exceptions, err := getExceptions(config)
273	if err != nil {
274		return nil, err
275	}
276	err = checkDistinctExceptions(exceptions)
277	if err != nil {
278		return nil, err
279	}
280
281	formats, err := getFormats(config)
282	if err != nil {
283		return nil, err
284	}
285
286	dispatcher, err := getOutputsTree(config, formats, cfg)
287	if err != nil {
288		// If we open several files, but then fail to parse the config, we should close
289		// those files before reporting that config is invalid.
290		if dispatcher != nil {
291			dispatcher.Close()
292		}
293
294		return nil, err
295	}
296
297	loggerType, logData, err := getloggerTypeFromStringData(config)
298	if err != nil {
299		return nil, err
300	}
301
302	return newFullLoggerConfig(constraints, exceptions, dispatcher, loggerType, logData, cfg)
303}
304
305func getConstraints(node *xmlNode) (logLevelConstraints, error) {
306	minLevelStr, isMinLevel := node.attributes[minLevelID]
307	maxLevelStr, isMaxLevel := node.attributes[maxLevelID]
308	levelsStr, isLevels := node.attributes[levelsID]
309
310	if isLevels && (isMinLevel && isMaxLevel) {
311		return nil, errors.New("for level declaration use '" + levelsID + "'' OR '" + minLevelID +
312			"', '" + maxLevelID + "'")
313	}
314
315	offString := LogLevel(Off).String()
316
317	if (isLevels && strings.TrimSpace(levelsStr) == offString) ||
318		(isMinLevel && !isMaxLevel && minLevelStr == offString) {
319
320		return NewOffConstraints()
321	}
322
323	if isLevels {
324		levels, err := parseLevels(levelsStr)
325		if err != nil {
326			return nil, err
327		}
328		return NewListConstraints(levels)
329	}
330
331	var minLevel = LogLevel(TraceLvl)
332	if isMinLevel {
333		found := true
334		minLevel, found = LogLevelFromString(minLevelStr)
335		if !found {
336			return nil, errors.New("declared " + minLevelID + " not found: " + minLevelStr)
337		}
338	}
339
340	var maxLevel = LogLevel(CriticalLvl)
341	if isMaxLevel {
342		found := true
343		maxLevel, found = LogLevelFromString(maxLevelStr)
344		if !found {
345			return nil, errors.New("declared " + maxLevelID + " not found: " + maxLevelStr)
346		}
347	}
348
349	return NewMinMaxConstraints(minLevel, maxLevel)
350}
351
352func parseLevels(str string) ([]LogLevel, error) {
353	levelsStrArr := strings.Split(strings.Replace(str, " ", "", -1), ",")
354	var levels []LogLevel
355	for _, levelStr := range levelsStrArr {
356		level, found := LogLevelFromString(levelStr)
357		if !found {
358			return nil, errors.New("declared level not found: " + levelStr)
359		}
360
361		levels = append(levels, level)
362	}
363
364	return levels, nil
365}
366
367func getExceptions(config *xmlNode) ([]*LogLevelException, error) {
368	var exceptions []*LogLevelException
369
370	var exceptionsNode *xmlNode
371	for _, child := range config.children {
372		if child.name == exceptionsID {
373			exceptionsNode = child
374			break
375		}
376	}
377
378	if exceptionsNode == nil {
379		return exceptions, nil
380	}
381
382	err := checkUnexpectedAttribute(exceptionsNode)
383	if err != nil {
384		return nil, err
385	}
386
387	err = checkExpectedElements(exceptionsNode, multipleMandatoryElements("exception"))
388	if err != nil {
389		return nil, err
390	}
391
392	for _, exceptionNode := range exceptionsNode.children {
393		if exceptionNode.name != exceptionID {
394			return nil, errors.New("incorrect nested element in exceptions section: " + exceptionNode.name)
395		}
396
397		err := checkUnexpectedAttribute(exceptionNode, minLevelID, maxLevelID, levelsID, funcPatternID, filePatternID)
398		if err != nil {
399			return nil, err
400		}
401
402		constraints, err := getConstraints(exceptionNode)
403		if err != nil {
404			return nil, errors.New("incorrect " + exceptionsID + " node: " + err.Error())
405		}
406
407		funcPattern, isFuncPattern := exceptionNode.attributes[funcPatternID]
408		filePattern, isFilePattern := exceptionNode.attributes[filePatternID]
409		if !isFuncPattern {
410			funcPattern = "*"
411		}
412		if !isFilePattern {
413			filePattern = "*"
414		}
415
416		exception, err := NewLogLevelException(funcPattern, filePattern, constraints)
417		if err != nil {
418			return nil, errors.New("incorrect exception node: " + err.Error())
419		}
420
421		exceptions = append(exceptions, exception)
422	}
423
424	return exceptions, nil
425}
426
427func checkDistinctExceptions(exceptions []*LogLevelException) error {
428	for i, exception := range exceptions {
429		for j, exception1 := range exceptions {
430			if i == j {
431				continue
432			}
433
434			if exception.FuncPattern() == exception1.FuncPattern() &&
435				exception.FilePattern() == exception1.FilePattern() {
436
437				return fmt.Errorf("there are two or more duplicate exceptions. Func: %v, file %v",
438					exception.FuncPattern(), exception.FilePattern())
439			}
440		}
441	}
442
443	return nil
444}
445
446func getFormats(config *xmlNode) (map[string]*formatter, error) {
447	formats := make(map[string]*formatter, 0)
448
449	var formatsNode *xmlNode
450	for _, child := range config.children {
451		if child.name == formatsID {
452			formatsNode = child
453			break
454		}
455	}
456
457	if formatsNode == nil {
458		return formats, nil
459	}
460
461	err := checkUnexpectedAttribute(formatsNode)
462	if err != nil {
463		return nil, err
464	}
465
466	err = checkExpectedElements(formatsNode, multipleMandatoryElements("format"))
467	if err != nil {
468		return nil, err
469	}
470
471	for _, formatNode := range formatsNode.children {
472		if formatNode.name != formatID {
473			return nil, errors.New("incorrect nested element in " + formatsID + " section: " + formatNode.name)
474		}
475
476		err := checkUnexpectedAttribute(formatNode, formatKeyAttrID, formatID)
477		if err != nil {
478			return nil, err
479		}
480
481		id, isID := formatNode.attributes[formatKeyAttrID]
482		formatStr, isFormat := formatNode.attributes[formatAttrID]
483		if !isID {
484			return nil, errors.New("format has no '" + formatKeyAttrID + "' attribute")
485		}
486		if !isFormat {
487			return nil, errors.New("format[" + id + "] has no '" + formatAttrID + "' attribute")
488		}
489
490		formatter, err := NewFormatter(formatStr)
491		if err != nil {
492			return nil, err
493		}
494
495		formats[id] = formatter
496	}
497
498	return formats, nil
499}
500
501func getloggerTypeFromStringData(config *xmlNode) (logType loggerTypeFromString, logData interface{}, err error) {
502	logTypeStr, loggerTypeExists := config.attributes[loggerTypeFromStringAttr]
503
504	if !loggerTypeExists {
505		return defaultloggerTypeFromString, nil, nil
506	}
507
508	logType, found := getLoggerTypeFromString(logTypeStr)
509
510	if !found {
511		return 0, nil, fmt.Errorf("unknown logger type: %s", logTypeStr)
512	}
513
514	if logType == asyncTimerloggerTypeFromString {
515		intervalStr, intervalExists := config.attributes[asyncLoggerIntervalAttr]
516		if !intervalExists {
517			return 0, nil, newMissingArgumentError(config.name, asyncLoggerIntervalAttr)
518		}
519
520		interval, err := strconv.ParseUint(intervalStr, 10, 32)
521		if err != nil {
522			return 0, nil, err
523		}
524
525		logData = asyncTimerLoggerData{uint32(interval)}
526	} else if logType == adaptiveLoggerTypeFromString {
527
528		// Min interval
529		minIntStr, minIntExists := config.attributes[adaptLoggerMinIntervalAttr]
530		if !minIntExists {
531			return 0, nil, newMissingArgumentError(config.name, adaptLoggerMinIntervalAttr)
532		}
533		minInterval, err := strconv.ParseUint(minIntStr, 10, 32)
534		if err != nil {
535			return 0, nil, err
536		}
537
538		// Max interval
539		maxIntStr, maxIntExists := config.attributes[adaptLoggerMaxIntervalAttr]
540		if !maxIntExists {
541			return 0, nil, newMissingArgumentError(config.name, adaptLoggerMaxIntervalAttr)
542		}
543		maxInterval, err := strconv.ParseUint(maxIntStr, 10, 32)
544		if err != nil {
545			return 0, nil, err
546		}
547
548		// Critical msg count
549		criticalMsgCountStr, criticalMsgCountExists := config.attributes[adaptLoggerCriticalMsgCountAttr]
550		if !criticalMsgCountExists {
551			return 0, nil, newMissingArgumentError(config.name, adaptLoggerCriticalMsgCountAttr)
552		}
553		criticalMsgCount, err := strconv.ParseUint(criticalMsgCountStr, 10, 32)
554		if err != nil {
555			return 0, nil, err
556		}
557
558		logData = adaptiveLoggerData{uint32(minInterval), uint32(maxInterval), uint32(criticalMsgCount)}
559	}
560
561	return logType, logData, nil
562}
563
564func getOutputsTree(config *xmlNode, formats map[string]*formatter, cfg *CfgParseParams) (dispatcherInterface, error) {
565	var outputsNode *xmlNode
566	for _, child := range config.children {
567		if child.name == outputsID {
568			outputsNode = child
569			break
570		}
571	}
572
573	if outputsNode != nil {
574		err := checkUnexpectedAttribute(outputsNode, outputFormatID)
575		if err != nil {
576			return nil, err
577		}
578
579		formatter, err := getCurrentFormat(outputsNode, DefaultFormatter, formats)
580		if err != nil {
581			return nil, err
582		}
583
584		output, err := createSplitter(outputsNode, formatter, formats, cfg)
585		if err != nil {
586			return nil, err
587		}
588
589		dispatcher, ok := output.(dispatcherInterface)
590		if ok {
591			return dispatcher, nil
592		}
593	}
594
595	console, err := NewConsoleWriter()
596	if err != nil {
597		return nil, err
598	}
599	return NewSplitDispatcher(DefaultFormatter, []interface{}{console})
600}
601
602func getCurrentFormat(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter) (*formatter, error) {
603	formatID, isFormatID := node.attributes[outputFormatID]
604	if !isFormatID {
605		return formatFromParent, nil
606	}
607
608	format, ok := formats[formatID]
609	if ok {
610		return format, nil
611	}
612
613	// Test for predefined format match
614	pdFormat, pdOk := predefinedFormats[formatID]
615
616	if !pdOk {
617		return nil, errors.New("formatid = '" + formatID + "' doesn't exist")
618	}
619
620	return pdFormat, nil
621}
622
623func createInnerReceivers(node *xmlNode, format *formatter, formats map[string]*formatter, cfg *CfgParseParams) ([]interface{}, error) {
624	var outputs []interface{}
625	for _, childNode := range node.children {
626		entry, ok := elementMap[childNode.name]
627		if !ok {
628			return nil, errors.New("unnknown tag '" + childNode.name + "' in outputs section")
629		}
630
631		output, err := entry.constructor(childNode, format, formats, cfg)
632		if err != nil {
633			return nil, err
634		}
635
636		outputs = append(outputs, output)
637	}
638
639	return outputs, nil
640}
641
642func createSplitter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
643	err := checkUnexpectedAttribute(node, outputFormatID)
644	if err != nil {
645		return nil, err
646	}
647
648	if !node.hasChildren() {
649		return nil, errNodeMustHaveChildren
650	}
651
652	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
653	if err != nil {
654		return nil, err
655	}
656
657	receivers, err := createInnerReceivers(node, currentFormat, formats, cfg)
658	if err != nil {
659		return nil, err
660	}
661
662	return NewSplitDispatcher(currentFormat, receivers)
663}
664
665func createCustomReceiver(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
666	dataCustomPrefixes := make(map[string]string)
667	// Expecting only 'formatid', 'name' and 'data-' attrs
668	for attr, attrval := range node.attributes {
669		isExpected := false
670		if attr == outputFormatID ||
671			attr == customNameAttrID {
672			isExpected = true
673		}
674		if strings.HasPrefix(attr, customNameDataAttrPrefix) {
675			dataCustomPrefixes[attr[len(customNameDataAttrPrefix):]] = attrval
676			isExpected = true
677		}
678		if !isExpected {
679			return nil, newUnexpectedAttributeError(node.name, attr)
680		}
681	}
682
683	if node.hasChildren() {
684		return nil, errNodeCannotHaveChildren
685	}
686	customName, hasCustomName := node.attributes[customNameAttrID]
687	if !hasCustomName {
688		return nil, newMissingArgumentError(node.name, customNameAttrID)
689	}
690	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
691	if err != nil {
692		return nil, err
693	}
694	args := CustomReceiverInitArgs{
695		XmlCustomAttrs: dataCustomPrefixes,
696	}
697
698	if cfg != nil && cfg.CustomReceiverProducers != nil {
699		if prod, ok := cfg.CustomReceiverProducers[customName]; ok {
700			rec, err := prod(args)
701			if err != nil {
702				return nil, err
703			}
704			creceiver, err := NewCustomReceiverDispatcherByValue(currentFormat, rec, customName, args)
705			if err != nil {
706				return nil, err
707			}
708			err = rec.AfterParse(args)
709			if err != nil {
710				return nil, err
711			}
712			return creceiver, nil
713		}
714	}
715
716	return NewCustomReceiverDispatcher(currentFormat, customName, args)
717}
718
719func createFilter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
720	err := checkUnexpectedAttribute(node, outputFormatID, filterLevelsAttrID)
721	if err != nil {
722		return nil, err
723	}
724
725	if !node.hasChildren() {
726		return nil, errNodeMustHaveChildren
727	}
728
729	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
730	if err != nil {
731		return nil, err
732	}
733
734	levelsStr, isLevels := node.attributes[filterLevelsAttrID]
735	if !isLevels {
736		return nil, newMissingArgumentError(node.name, filterLevelsAttrID)
737	}
738
739	levels, err := parseLevels(levelsStr)
740	if err != nil {
741		return nil, err
742	}
743
744	receivers, err := createInnerReceivers(node, currentFormat, formats, cfg)
745	if err != nil {
746		return nil, err
747	}
748
749	return NewFilterDispatcher(currentFormat, receivers, levels...)
750}
751
752func createfileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
753	err := checkUnexpectedAttribute(node, outputFormatID, pathID)
754	if err != nil {
755		return nil, err
756	}
757
758	if node.hasChildren() {
759		return nil, errNodeCannotHaveChildren
760	}
761
762	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
763	if err != nil {
764		return nil, err
765	}
766
767	path, isPath := node.attributes[pathID]
768	if !isPath {
769		return nil, newMissingArgumentError(node.name, pathID)
770	}
771
772	fileWriter, err := NewFileWriter(path)
773	if err != nil {
774		return nil, err
775	}
776
777	return NewFormattedWriter(fileWriter, currentFormat)
778}
779
780// Creates new SMTP writer if encountered in the config file.
781func createSMTPWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
782	err := checkUnexpectedAttribute(node, outputFormatID, senderaddressID, senderNameID, hostNameID, hostPortID, userNameID, userPassID, subjectID)
783	if err != nil {
784		return nil, err
785	}
786	// Node must have children.
787	if !node.hasChildren() {
788		return nil, errNodeMustHaveChildren
789	}
790	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
791	if err != nil {
792		return nil, err
793	}
794	senderAddress, ok := node.attributes[senderaddressID]
795	if !ok {
796		return nil, newMissingArgumentError(node.name, senderaddressID)
797	}
798	senderName, ok := node.attributes[senderNameID]
799	if !ok {
800		return nil, newMissingArgumentError(node.name, senderNameID)
801	}
802	// Process child nodes scanning for recipient email addresses and/or CA certificate paths.
803	var recipientAddresses []string
804	var caCertDirPaths []string
805	var mailHeaders []string
806	for _, childNode := range node.children {
807		switch childNode.name {
808		// Extract recipient address from child nodes.
809		case recipientID:
810			address, ok := childNode.attributes[addressID]
811			if !ok {
812				return nil, newMissingArgumentError(childNode.name, addressID)
813			}
814			recipientAddresses = append(recipientAddresses, address)
815		// Extract CA certificate file path from child nodes.
816		case cACertDirpathID:
817			path, ok := childNode.attributes[pathID]
818			if !ok {
819				return nil, newMissingArgumentError(childNode.name, pathID)
820			}
821			caCertDirPaths = append(caCertDirPaths, path)
822
823		// Extract email headers from child nodes.
824		case mailHeaderID:
825			headerName, ok := childNode.attributes[mailHeaderNameID]
826			if !ok {
827				return nil, newMissingArgumentError(childNode.name, mailHeaderNameID)
828			}
829
830			headerValue, ok := childNode.attributes[mailHeaderValueID]
831			if !ok {
832				return nil, newMissingArgumentError(childNode.name, mailHeaderValueID)
833			}
834
835			// Build header line
836			mailHeaders = append(mailHeaders, fmt.Sprintf("%s: %s", headerName, headerValue))
837		default:
838			return nil, newUnexpectedChildElementError(childNode.name)
839		}
840	}
841	hostName, ok := node.attributes[hostNameID]
842	if !ok {
843		return nil, newMissingArgumentError(node.name, hostNameID)
844	}
845
846	hostPort, ok := node.attributes[hostPortID]
847	if !ok {
848		return nil, newMissingArgumentError(node.name, hostPortID)
849	}
850
851	// Check if the string can really be converted into int.
852	if _, err := strconv.Atoi(hostPort); err != nil {
853		return nil, errors.New("invalid host port number")
854	}
855
856	userName, ok := node.attributes[userNameID]
857	if !ok {
858		return nil, newMissingArgumentError(node.name, userNameID)
859	}
860
861	userPass, ok := node.attributes[userPassID]
862	if !ok {
863		return nil, newMissingArgumentError(node.name, userPassID)
864	}
865
866	// subject is optionally set by configuration.
867	// default value is defined by DefaultSubjectPhrase constant in the writers_smtpwriter.go
868	var subjectPhrase = DefaultSubjectPhrase
869
870	subject, ok := node.attributes[subjectID]
871	if ok {
872		subjectPhrase = subject
873	}
874
875	smtpWriter := NewSMTPWriter(
876		senderAddress,
877		senderName,
878		recipientAddresses,
879		hostName,
880		hostPort,
881		userName,
882		userPass,
883		caCertDirPaths,
884		subjectPhrase,
885		mailHeaders,
886	)
887
888	return NewFormattedWriter(smtpWriter, currentFormat)
889}
890
891func createConsoleWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
892	err := checkUnexpectedAttribute(node, outputFormatID)
893	if err != nil {
894		return nil, err
895	}
896
897	if node.hasChildren() {
898		return nil, errNodeCannotHaveChildren
899	}
900
901	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
902	if err != nil {
903		return nil, err
904	}
905
906	consoleWriter, err := NewConsoleWriter()
907	if err != nil {
908		return nil, err
909	}
910
911	return NewFormattedWriter(consoleWriter, currentFormat)
912}
913
914func createconnWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
915	if node.hasChildren() {
916		return nil, errNodeCannotHaveChildren
917	}
918
919	err := checkUnexpectedAttribute(node, outputFormatID, connWriterAddrAttr, connWriterNetAttr, connWriterReconnectOnMsgAttr, connWriterUseTLSAttr, connWriterInsecureSkipVerifyAttr)
920	if err != nil {
921		return nil, err
922	}
923
924	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
925	if err != nil {
926		return nil, err
927	}
928
929	addr, isAddr := node.attributes[connWriterAddrAttr]
930	if !isAddr {
931		return nil, newMissingArgumentError(node.name, connWriterAddrAttr)
932	}
933
934	net, isNet := node.attributes[connWriterNetAttr]
935	if !isNet {
936		return nil, newMissingArgumentError(node.name, connWriterNetAttr)
937	}
938
939	reconnectOnMsg := false
940	reconnectOnMsgStr, isReconnectOnMsgStr := node.attributes[connWriterReconnectOnMsgAttr]
941	if isReconnectOnMsgStr {
942		if reconnectOnMsgStr == "true" {
943			reconnectOnMsg = true
944		} else if reconnectOnMsgStr == "false" {
945			reconnectOnMsg = false
946		} else {
947			return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterReconnectOnMsgAttr + "' attribute value")
948		}
949	}
950
951	useTLS := false
952	useTLSStr, isUseTLSStr := node.attributes[connWriterUseTLSAttr]
953	if isUseTLSStr {
954		if useTLSStr == "true" {
955			useTLS = true
956		} else if useTLSStr == "false" {
957			useTLS = false
958		} else {
959			return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterUseTLSAttr + "' attribute value")
960		}
961		if useTLS {
962			insecureSkipVerify := false
963			insecureSkipVerifyStr, isInsecureSkipVerify := node.attributes[connWriterInsecureSkipVerifyAttr]
964			if isInsecureSkipVerify {
965				if insecureSkipVerifyStr == "true" {
966					insecureSkipVerify = true
967				} else if insecureSkipVerifyStr == "false" {
968					insecureSkipVerify = false
969				} else {
970					return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterInsecureSkipVerifyAttr + "' attribute value")
971				}
972			}
973			config := tls.Config{InsecureSkipVerify: insecureSkipVerify}
974			connWriter := newTLSWriter(net, addr, reconnectOnMsg, &config)
975			return NewFormattedWriter(connWriter, currentFormat)
976		}
977	}
978
979	connWriter := NewConnWriter(net, addr, reconnectOnMsg)
980
981	return NewFormattedWriter(connWriter, currentFormat)
982}
983
984func createRollingFileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
985	if node.hasChildren() {
986		return nil, errNodeCannotHaveChildren
987	}
988
989	rollingTypeStr, isRollingType := node.attributes[rollingFileTypeAttr]
990	if !isRollingType {
991		return nil, newMissingArgumentError(node.name, rollingFileTypeAttr)
992	}
993
994	rollingType, ok := rollingTypeFromString(rollingTypeStr)
995	if !ok {
996		return nil, errors.New("unknown rolling file type: " + rollingTypeStr)
997	}
998
999	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
1000	if err != nil {
1001		return nil, err
1002	}
1003
1004	path, isPath := node.attributes[rollingFilePathAttr]
1005	if !isPath {
1006		return nil, newMissingArgumentError(node.name, rollingFilePathAttr)
1007	}
1008
1009	rollingArchiveStr, archiveAttrExists := node.attributes[rollingFileArchiveAttr]
1010
1011	var rArchiveType rollingArchiveType
1012	var rArchivePath string
1013	var rArchiveExploded bool = false
1014	if !archiveAttrExists {
1015		rArchiveType = rollingArchiveNone
1016		rArchivePath = ""
1017	} else {
1018		rArchiveType, ok = rollingArchiveTypeFromString(rollingArchiveStr)
1019		if !ok {
1020			return nil, errors.New("unknown rolling archive type: " + rollingArchiveStr)
1021		}
1022
1023		if rArchiveType == rollingArchiveNone {
1024			rArchivePath = ""
1025		} else {
1026			if rArchiveExplodedAttr, ok := node.attributes[rollingFileArchiveExplodedAttr]; ok {
1027				if rArchiveExploded, err = strconv.ParseBool(rArchiveExplodedAttr); err != nil {
1028					return nil, fmt.Errorf("archive exploded should be true or false, but was %v",
1029						rArchiveExploded)
1030				}
1031			}
1032
1033			rArchivePath, ok = node.attributes[rollingFileArchivePathAttr]
1034			if ok {
1035				if rArchivePath == "" {
1036					return nil, fmt.Errorf("empty archive path is not supported")
1037				}
1038			} else {
1039				if rArchiveExploded {
1040					rArchivePath = rollingArchiveDefaultExplodedName
1041
1042				} else {
1043					rArchivePath, err = rollingArchiveTypeDefaultName(rArchiveType, false)
1044					if err != nil {
1045						return nil, err
1046					}
1047				}
1048			}
1049		}
1050	}
1051
1052	nameMode := rollingNameMode(rollingNameModePostfix)
1053	nameModeStr, ok := node.attributes[rollingFileNameModeAttr]
1054	if ok {
1055		mode, found := rollingNameModeFromString(nameModeStr)
1056		if !found {
1057			return nil, errors.New("unknown rolling filename mode: " + nameModeStr)
1058		} else {
1059			nameMode = mode
1060		}
1061	}
1062
1063	if rollingType == rollingTypeSize {
1064		err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr,
1065			rollingFileMaxSizeAttr, rollingFileMaxRollsAttr, rollingFileArchiveAttr,
1066			rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr)
1067		if err != nil {
1068			return nil, err
1069		}
1070
1071		maxSizeStr, ok := node.attributes[rollingFileMaxSizeAttr]
1072		if !ok {
1073			return nil, newMissingArgumentError(node.name, rollingFileMaxSizeAttr)
1074		}
1075
1076		maxSize, err := strconv.ParseInt(maxSizeStr, 10, 64)
1077		if err != nil {
1078			return nil, err
1079		}
1080
1081		maxRolls := 0
1082		maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr]
1083		if ok {
1084			maxRolls, err = strconv.Atoi(maxRollsStr)
1085			if err != nil {
1086				return nil, err
1087			}
1088		}
1089
1090		rollingWriter, err := NewRollingFileWriterSize(path, rArchiveType, rArchivePath, maxSize, maxRolls, nameMode, rArchiveExploded)
1091		if err != nil {
1092			return nil, err
1093		}
1094
1095		return NewFormattedWriter(rollingWriter, currentFormat)
1096
1097	} else if rollingType == rollingTypeTime {
1098		err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr,
1099			rollingFileDataPatternAttr, rollingFileArchiveAttr, rollingFileMaxRollsAttr,
1100			rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr,
1101			rollingFileFullNameAttr)
1102		if err != nil {
1103			return nil, err
1104		}
1105
1106		maxRolls := 0
1107		maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr]
1108		if ok {
1109			maxRolls, err = strconv.Atoi(maxRollsStr)
1110			if err != nil {
1111				return nil, err
1112			}
1113		}
1114
1115		fullName := false
1116		fn, ok := node.attributes[rollingFileFullNameAttr]
1117		if ok {
1118			if fn == "true" {
1119				fullName = true
1120			} else if fn == "false" {
1121				fullName = false
1122			} else {
1123				return nil, errors.New("node '" + node.name + "' has incorrect '" + rollingFileFullNameAttr + "' attribute value")
1124			}
1125		}
1126
1127		dataPattern, ok := node.attributes[rollingFileDataPatternAttr]
1128		if !ok {
1129			return nil, newMissingArgumentError(node.name, rollingFileDataPatternAttr)
1130		}
1131
1132		rollingWriter, err := NewRollingFileWriterTime(path, rArchiveType, rArchivePath, maxRolls, dataPattern, nameMode, rArchiveExploded, fullName)
1133		if err != nil {
1134			return nil, err
1135		}
1136
1137		return NewFormattedWriter(rollingWriter, currentFormat)
1138	}
1139
1140	return nil, errors.New("incorrect rolling writer type " + rollingTypeStr)
1141}
1142
1143func createbufferedWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) {
1144	err := checkUnexpectedAttribute(node, outputFormatID, bufferedSizeAttr, bufferedFlushPeriodAttr)
1145	if err != nil {
1146		return nil, err
1147	}
1148
1149	if !node.hasChildren() {
1150		return nil, errNodeMustHaveChildren
1151	}
1152
1153	currentFormat, err := getCurrentFormat(node, formatFromParent, formats)
1154	if err != nil {
1155		return nil, err
1156	}
1157
1158	sizeStr, isSize := node.attributes[bufferedSizeAttr]
1159	if !isSize {
1160		return nil, newMissingArgumentError(node.name, bufferedSizeAttr)
1161	}
1162
1163	size, err := strconv.Atoi(sizeStr)
1164	if err != nil {
1165		return nil, err
1166	}
1167
1168	flushPeriod := 0
1169	flushPeriodStr, isFlushPeriod := node.attributes[bufferedFlushPeriodAttr]
1170	if isFlushPeriod {
1171		flushPeriod, err = strconv.Atoi(flushPeriodStr)
1172		if err != nil {
1173			return nil, err
1174		}
1175	}
1176
1177	// Inner writer couldn't have its own format, so we pass 'currentFormat' as its parent format
1178	receivers, err := createInnerReceivers(node, currentFormat, formats, cfg)
1179	if err != nil {
1180		return nil, err
1181	}
1182
1183	formattedWriter, ok := receivers[0].(*formattedWriter)
1184	if !ok {
1185		return nil, errors.New("buffered writer's child is not writer")
1186	}
1187
1188	// ... and then we check that it hasn't changed
1189	if formattedWriter.Format() != currentFormat {
1190		return nil, errors.New("inner writer cannot have his own format")
1191	}
1192
1193	bufferedWriter, err := NewBufferedWriter(formattedWriter.Writer(), size, time.Duration(flushPeriod))
1194	if err != nil {
1195		return nil, err
1196	}
1197
1198	return NewFormattedWriter(bufferedWriter, currentFormat)
1199}
1200
1201// Returns an error if node has any attributes not listed in expectedAttrs.
1202func checkUnexpectedAttribute(node *xmlNode, expectedAttrs ...string) error {
1203	for attr := range node.attributes {
1204		isExpected := false
1205		for _, expected := range expectedAttrs {
1206			if attr == expected {
1207				isExpected = true
1208				break
1209			}
1210		}
1211		if !isExpected {
1212			return newUnexpectedAttributeError(node.name, attr)
1213		}
1214	}
1215
1216	return nil
1217}
1218
1219type expectedElementInfo struct {
1220	name      string
1221	mandatory bool
1222	multiple  bool
1223}
1224
1225func optionalElement(name string) expectedElementInfo {
1226	return expectedElementInfo{name, false, false}
1227}
1228func mandatoryElement(name string) expectedElementInfo {
1229	return expectedElementInfo{name, true, false}
1230}
1231func multipleElements(name string) expectedElementInfo {
1232	return expectedElementInfo{name, false, true}
1233}
1234func multipleMandatoryElements(name string) expectedElementInfo {
1235	return expectedElementInfo{name, true, true}
1236}
1237
1238func checkExpectedElements(node *xmlNode, elements ...expectedElementInfo) error {
1239	for _, element := range elements {
1240		count := 0
1241		for _, child := range node.children {
1242			if child.name == element.name {
1243				count++
1244			}
1245		}
1246
1247		if count == 0 && element.mandatory {
1248			return errors.New(node.name + " does not have mandatory subnode - " + element.name)
1249		}
1250		if count > 1 && !element.multiple {
1251			return errors.New(node.name + " has more then one subnode - " + element.name)
1252		}
1253	}
1254
1255	for _, child := range node.children {
1256		isExpected := false
1257		for _, element := range elements {
1258			if child.name == element.name {
1259				isExpected = true
1260			}
1261		}
1262
1263		if !isExpected {
1264			return errors.New(node.name + " has unexpected child: " + child.name)
1265		}
1266	}
1267
1268	return nil
1269}
1270