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