You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2017/01/24 20:56:17 UTC
[06/13] incubator-trafficcontrol git commit: Vendored
github.com/cihub/seelog.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_bufferedwriter_test.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_bufferedwriter_test.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_bufferedwriter_test.go
new file mode 100644
index 0000000..03f74f7
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_bufferedwriter_test.go
@@ -0,0 +1,78 @@
+// 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 (
+ "testing"
+)
+
+func TestChunkWriteOnFilling(t *testing.T) {
+ writer, _ := newBytesVerifier(t)
+ bufferedWriter, err := NewBufferedWriter(writer, 1024, 0)
+
+ if err != nil {
+ t.Fatalf("Unexpected buffered writer creation error: %s", err.Error())
+ }
+
+ bytes := make([]byte, 1000)
+
+ bufferedWriter.Write(bytes)
+ writer.ExpectBytes(bytes)
+ bufferedWriter.Write(bytes)
+}
+
+func TestFlushByTimePeriod(t *testing.T) {
+ writer, _ := newBytesVerifier(t)
+ bufferedWriter, err := NewBufferedWriter(writer, 1024, 10)
+
+ if err != nil {
+ t.Fatalf("Unexpected buffered writer creation error: %s", err.Error())
+ }
+
+ bytes := []byte("Hello")
+
+ for i := 0; i < 2; i++ {
+ writer.ExpectBytes(bytes)
+ bufferedWriter.Write(bytes)
+ }
+}
+
+func TestBigMessageMustPassMemoryBuffer(t *testing.T) {
+ writer, _ := newBytesVerifier(t)
+ bufferedWriter, err := NewBufferedWriter(writer, 1024, 0)
+
+ if err != nil {
+ t.Fatalf("Unexpected buffered writer creation error: %s", err.Error())
+ }
+
+ bytes := make([]byte, 5000)
+
+ for i := 0; i < len(bytes); i++ {
+ bytes[i] = uint8(i % 255)
+ }
+
+ writer.ExpectBytes(bytes)
+ bufferedWriter.Write(bytes)
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_connwriter.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_connwriter.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_connwriter.go
new file mode 100644
index 0000000..d199894
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_connwriter.go
@@ -0,0 +1,144 @@
+// 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"
+ "fmt"
+ "io"
+ "net"
+)
+
+// connWriter is used to write to a stream-oriented network connection.
+type connWriter struct {
+ innerWriter io.WriteCloser
+ reconnectOnMsg bool
+ reconnect bool
+ net string
+ addr string
+ useTLS bool
+ configTLS *tls.Config
+}
+
+// Creates writer to the address addr on the network netName.
+// Connection will be opened on each write if reconnectOnMsg = true
+func NewConnWriter(netName string, addr string, reconnectOnMsg bool) *connWriter {
+ newWriter := new(connWriter)
+
+ newWriter.net = netName
+ newWriter.addr = addr
+ newWriter.reconnectOnMsg = reconnectOnMsg
+
+ return newWriter
+}
+
+// Creates a writer that uses SSL/TLS
+func newTLSWriter(netName string, addr string, reconnectOnMsg bool, config *tls.Config) *connWriter {
+ newWriter := new(connWriter)
+
+ newWriter.net = netName
+ newWriter.addr = addr
+ newWriter.reconnectOnMsg = reconnectOnMsg
+ newWriter.useTLS = true
+ newWriter.configTLS = config
+
+ return newWriter
+}
+
+func (connWriter *connWriter) Close() error {
+ if connWriter.innerWriter == nil {
+ return nil
+ }
+
+ return connWriter.innerWriter.Close()
+}
+
+func (connWriter *connWriter) Write(bytes []byte) (n int, err error) {
+ if connWriter.neededConnectOnMsg() {
+ err = connWriter.connect()
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ if connWriter.reconnectOnMsg {
+ defer connWriter.innerWriter.Close()
+ }
+
+ n, err = connWriter.innerWriter.Write(bytes)
+ if err != nil {
+ connWriter.reconnect = true
+ }
+
+ return
+}
+
+func (connWriter *connWriter) String() string {
+ return fmt.Sprintf("Conn writer: [%s, %s, %v]", connWriter.net, connWriter.addr, connWriter.reconnectOnMsg)
+}
+
+func (connWriter *connWriter) connect() error {
+ if connWriter.innerWriter != nil {
+ connWriter.innerWriter.Close()
+ connWriter.innerWriter = nil
+ }
+
+ if connWriter.useTLS {
+ conn, err := tls.Dial(connWriter.net, connWriter.addr, connWriter.configTLS)
+ if err != nil {
+ return err
+ }
+ connWriter.innerWriter = conn
+
+ return nil
+ }
+
+ conn, err := net.Dial(connWriter.net, connWriter.addr)
+ if err != nil {
+ return err
+ }
+
+ tcpConn, ok := conn.(*net.TCPConn)
+ if ok {
+ tcpConn.SetKeepAlive(true)
+ }
+
+ connWriter.innerWriter = conn
+
+ return nil
+}
+
+func (connWriter *connWriter) neededConnectOnMsg() bool {
+ if connWriter.reconnect {
+ connWriter.reconnect = false
+ return true
+ }
+
+ if connWriter.innerWriter == nil {
+ return true
+ }
+
+ return connWriter.reconnectOnMsg
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_consolewriter.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_consolewriter.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_consolewriter.go
new file mode 100644
index 0000000..3eb79af
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_consolewriter.go
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import "fmt"
+
+// consoleWriter is used to write to console
+type consoleWriter struct {
+}
+
+// Creates a new console writer. Returns error, if the console writer couldn't be created.
+func NewConsoleWriter() (writer *consoleWriter, err error) {
+ newWriter := new(consoleWriter)
+
+ return newWriter, nil
+}
+
+// Create folder and file on WriteLog/Write first call
+func (console *consoleWriter) Write(bytes []byte) (int, error) {
+ return fmt.Print(string(bytes))
+}
+
+func (console *consoleWriter) String() string {
+ return "Console writer"
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter.go
new file mode 100644
index 0000000..8d3ae27
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter.go
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+)
+
+// fileWriter is used to write to a file.
+type fileWriter struct {
+ innerWriter io.WriteCloser
+ fileName string
+}
+
+// Creates a new file and a corresponding writer. Returns error, if the file couldn't be created.
+func NewFileWriter(fileName string) (writer *fileWriter, err error) {
+ newWriter := new(fileWriter)
+ newWriter.fileName = fileName
+
+ return newWriter, nil
+}
+
+func (fw *fileWriter) Close() error {
+ if fw.innerWriter != nil {
+ err := fw.innerWriter.Close()
+ if err != nil {
+ return err
+ }
+ fw.innerWriter = nil
+ }
+ return nil
+}
+
+// Create folder and file on WriteLog/Write first call
+func (fw *fileWriter) Write(bytes []byte) (n int, err error) {
+ if fw.innerWriter == nil {
+ if err := fw.createFile(); err != nil {
+ return 0, err
+ }
+ }
+ return fw.innerWriter.Write(bytes)
+}
+
+func (fw *fileWriter) createFile() error {
+ folder, _ := filepath.Split(fw.fileName)
+ var err error
+
+ if 0 != len(folder) {
+ err = os.MkdirAll(folder, defaultDirectoryPermissions)
+ if err != nil {
+ return err
+ }
+ }
+
+ // If exists
+ fw.innerWriter, err = os.OpenFile(fw.fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (fw *fileWriter) String() string {
+ return fmt.Sprintf("File writer: %s", fw.fileName)
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter_test.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter_test.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter_test.go
new file mode 100644
index 0000000..f723912
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_filewriter_test.go
@@ -0,0 +1,257 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+const (
+ messageLen = 10
+)
+
+var bytesFileTest = []byte(strings.Repeat("A", messageLen))
+
+func TestSimpleFileWriter(t *testing.T) {
+ t.Logf("Starting file writer tests")
+ NewFileWriterTester(simplefileWriterTests, simplefileWriterGetter, t).test()
+}
+
+//===============================================================
+
+func simplefileWriterGetter(testCase *fileWriterTestCase) (io.WriteCloser, error) {
+ return NewFileWriter(testCase.fileName)
+}
+
+//===============================================================
+type fileWriterTestCase struct {
+ files []string
+ fileName string
+ rollingType rollingType
+ fileSize int64
+ maxRolls int
+ datePattern string
+ writeCount int
+ resFiles []string
+ nameMode rollingNameMode
+ archiveType rollingArchiveType
+ archiveExploded bool
+ archivePath string
+}
+
+func createSimplefileWriterTestCase(fileName string, writeCount int) *fileWriterTestCase {
+ return &fileWriterTestCase{[]string{}, fileName, rollingTypeSize, 0, 0, "", writeCount, []string{fileName}, 0, rollingArchiveNone, false, ""}
+}
+
+var simplefileWriterTests = []*fileWriterTestCase{
+ createSimplefileWriterTestCase("log.testlog", 1),
+ createSimplefileWriterTestCase("log.testlog", 50),
+ createSimplefileWriterTestCase(filepath.Join("dir", "log.testlog"), 50),
+}
+
+//===============================================================
+
+type fileWriterTester struct {
+ testCases []*fileWriterTestCase
+ writerGetter func(*fileWriterTestCase) (io.WriteCloser, error)
+ t *testing.T
+}
+
+func NewFileWriterTester(
+ testCases []*fileWriterTestCase,
+ writerGetter func(*fileWriterTestCase) (io.WriteCloser, error),
+ t *testing.T) *fileWriterTester {
+
+ return &fileWriterTester{testCases, writerGetter, t}
+}
+
+func isWriterTestFile(fn string) bool {
+ return strings.Contains(fn, ".testlog") || strings.Contains(fn, ".zip") || strings.Contains(fn, ".gz")
+}
+
+func cleanupWriterTest(t *testing.T) {
+ toDel, err := getDirFilePaths(".", isWriterTestFile, true)
+ if nil != err {
+ t.Fatal("Cannot list files in test directory!")
+ }
+
+ for _, p := range toDel {
+ if err = tryRemoveFile(p); nil != err {
+ t.Errorf("cannot remove file %s in test directory: %s", p, err.Error())
+ }
+ }
+
+ if err = os.RemoveAll("dir"); nil != err {
+ t.Errorf("cannot remove temp test directory: %s", err.Error())
+ }
+}
+
+func getWriterTestResultFiles() ([]string, error) {
+ var p []string
+
+ visit := func(path string, f os.FileInfo, err error) error {
+ if !f.IsDir() && isWriterTestFile(path) {
+ abs, err := filepath.Abs(path)
+ if err != nil {
+ return fmt.Errorf("filepath.Abs failed for %s", path)
+ }
+
+ p = append(p, abs)
+ }
+
+ return nil
+ }
+
+ err := filepath.Walk(".", visit)
+ if nil != err {
+ return nil, err
+ }
+
+ return p, nil
+}
+
+func (tester *fileWriterTester) testCase(testCase *fileWriterTestCase, testNum int) {
+ defer cleanupWriterTest(tester.t)
+
+ tester.t.Logf("Start test [%v]\n", testNum)
+
+ for _, filePath := range testCase.files {
+ dir, _ := filepath.Split(filePath)
+
+ var err error
+
+ if 0 != len(dir) {
+ err = os.MkdirAll(dir, defaultDirectoryPermissions)
+ if err != nil {
+ tester.t.Error(err)
+ return
+ }
+ }
+
+ fi, err := os.Create(filePath)
+ if err != nil {
+ tester.t.Error(err)
+ return
+ }
+
+ err = fi.Close()
+ if err != nil {
+ tester.t.Error(err)
+ return
+ }
+ }
+
+ fwc, err := tester.writerGetter(testCase)
+ if err != nil {
+ tester.t.Error(err)
+ return
+ }
+ defer fwc.Close()
+
+ tester.performWrite(fwc, testCase.writeCount)
+
+ files, err := getWriterTestResultFiles()
+ if err != nil {
+ tester.t.Error(err)
+ return
+ }
+
+ tester.checkRequiredFilesExist(testCase, files)
+ tester.checkJustRequiredFilesExist(testCase, files)
+
+}
+
+func (tester *fileWriterTester) test() {
+ for i, tc := range tester.testCases {
+ cleanupWriterTest(tester.t)
+ tester.testCase(tc, i)
+ }
+}
+
+func (tester *fileWriterTester) performWrite(fileWriter io.Writer, count int) {
+ for i := 0; i < count; i++ {
+ _, err := fileWriter.Write(bytesFileTest)
+
+ if err != nil {
+ tester.t.Error(err)
+ return
+ }
+ }
+}
+
+func (tester *fileWriterTester) checkRequiredFilesExist(testCase *fileWriterTestCase, files []string) {
+ var found bool
+ for _, expected := range testCase.resFiles {
+ found = false
+ exAbs, err := filepath.Abs(expected)
+ if err != nil {
+ tester.t.Errorf("filepath.Abs failed for %s", expected)
+ continue
+ }
+
+ for _, f := range files {
+ if af, e := filepath.Abs(f); e == nil {
+ tester.t.Log(af)
+ if exAbs == af {
+ found = true
+ break
+ }
+ } else {
+ tester.t.Errorf("filepath.Abs failed for %s", f)
+ }
+ }
+
+ if !found {
+ tester.t.Errorf("expected file: %s doesn't exist. Got %v\n", exAbs, files)
+ }
+ }
+}
+
+func (tester *fileWriterTester) checkJustRequiredFilesExist(testCase *fileWriterTestCase, files []string) {
+ for _, f := range files {
+ found := false
+ for _, expected := range testCase.resFiles {
+
+ exAbs, err := filepath.Abs(expected)
+ if err != nil {
+ tester.t.Errorf("filepath.Abs failed for %s", expected)
+ } else {
+ if exAbs == f {
+ found = true
+ break
+ }
+ }
+ }
+
+ if !found {
+ tester.t.Errorf("unexpected file: %v", f)
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter.go
new file mode 100644
index 0000000..bf44a41
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter.go
@@ -0,0 +1,62 @@
+// 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 (
+ "errors"
+ "fmt"
+ "io"
+)
+
+type formattedWriter struct {
+ writer io.Writer
+ formatter *formatter
+}
+
+func NewFormattedWriter(writer io.Writer, formatter *formatter) (*formattedWriter, error) {
+ if formatter == nil {
+ return nil, errors.New("formatter can not be nil")
+ }
+
+ return &formattedWriter{writer, formatter}, nil
+}
+
+func (formattedWriter *formattedWriter) Write(message string, level LogLevel, context LogContextInterface) error {
+ str := formattedWriter.formatter.Format(message, level, context)
+ _, err := formattedWriter.writer.Write([]byte(str))
+ return err
+}
+
+func (formattedWriter *formattedWriter) String() string {
+ return fmt.Sprintf("writer: %s, format: %s", formattedWriter.writer, formattedWriter.formatter)
+}
+
+func (formattedWriter *formattedWriter) Writer() io.Writer {
+ return formattedWriter.writer
+}
+
+func (formattedWriter *formattedWriter) Format() *formatter {
+ return formattedWriter.formatter
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter_test.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter_test.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter_test.go
new file mode 100644
index 0000000..351ac4e
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_formattedwriter_test.go
@@ -0,0 +1,65 @@
+// 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 (
+ "testing"
+)
+
+func TestformattedWriter(t *testing.T) {
+ formatStr := "%Level %LEVEL %Msg"
+ message := "message"
+ var logLevel = LogLevel(TraceLvl)
+
+ bytesVerifier, err := newBytesVerifier(t)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ formatter, err := NewFormatter(formatStr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ writer, err := NewFormattedWriter(bytesVerifier, formatter)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ context, err := currentContext(nil)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ logMessage := formatter.Format(message, logLevel, context)
+
+ bytesVerifier.ExpectBytes([]byte(logMessage))
+ writer.Write(message, logLevel, context)
+ bytesVerifier.MustNotExpect()
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go
new file mode 100644
index 0000000..d3903bb
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go
@@ -0,0 +1,770 @@
+// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/cihub/seelog/archive"
+ "github.com/cihub/seelog/archive/gzip"
+ "github.com/cihub/seelog/archive/tar"
+ "github.com/cihub/seelog/archive/zip"
+)
+
+// Common constants
+const (
+ rollingLogHistoryDelimiter = "."
+)
+
+// Types of the rolling writer: roll by date, by time, etc.
+type rollingType uint8
+
+const (
+ rollingTypeSize = iota
+ rollingTypeTime
+)
+
+// Types of the rolled file naming mode: prefix, postfix, etc.
+type rollingNameMode uint8
+
+const (
+ rollingNameModePostfix = iota
+ rollingNameModePrefix
+)
+
+var rollingNameModesStringRepresentation = map[rollingNameMode]string{
+ rollingNameModePostfix: "postfix",
+ rollingNameModePrefix: "prefix",
+}
+
+func rollingNameModeFromString(rollingNameStr string) (rollingNameMode, bool) {
+ for tp, tpStr := range rollingNameModesStringRepresentation {
+ if tpStr == rollingNameStr {
+ return tp, true
+ }
+ }
+
+ return 0, false
+}
+
+var rollingTypesStringRepresentation = map[rollingType]string{
+ rollingTypeSize: "size",
+ rollingTypeTime: "date",
+}
+
+func rollingTypeFromString(rollingTypeStr string) (rollingType, bool) {
+ for tp, tpStr := range rollingTypesStringRepresentation {
+ if tpStr == rollingTypeStr {
+ return tp, true
+ }
+ }
+
+ return 0, false
+}
+
+// Old logs archivation type.
+type rollingArchiveType uint8
+
+const (
+ rollingArchiveNone = iota
+ rollingArchiveZip
+ rollingArchiveGzip
+)
+
+var rollingArchiveTypesStringRepresentation = map[rollingArchiveType]string{
+ rollingArchiveNone: "none",
+ rollingArchiveZip: "zip",
+ rollingArchiveGzip: "gzip",
+}
+
+type archiver func(f *os.File, exploded bool) archive.WriteCloser
+
+type unarchiver func(f *os.File) (archive.ReadCloser, error)
+
+type compressionType struct {
+ extension string
+ handleMultipleEntries bool
+ archiver archiver
+ unarchiver unarchiver
+}
+
+var compressionTypes = map[rollingArchiveType]compressionType{
+ rollingArchiveZip: {
+ extension: ".zip",
+ handleMultipleEntries: true,
+ archiver: func(f *os.File, _ bool) archive.WriteCloser {
+ return zip.NewWriter(f)
+ },
+ unarchiver: func(f *os.File) (archive.ReadCloser, error) {
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+ r, err := zip.NewReader(f, fi.Size())
+ if err != nil {
+ return nil, err
+ }
+ return archive.NopCloser(r), nil
+ },
+ },
+ rollingArchiveGzip: {
+ extension: ".gz",
+ handleMultipleEntries: false,
+ archiver: func(f *os.File, exploded bool) archive.WriteCloser {
+ gw := gzip.NewWriter(f)
+ if exploded {
+ return gw
+ }
+ return tar.NewWriteMultiCloser(gw, gw)
+ },
+ unarchiver: func(f *os.File) (archive.ReadCloser, error) {
+ gr, err := gzip.NewReader(f, f.Name())
+ if err != nil {
+ return nil, err
+ }
+
+ // Determine if the gzip is a tar
+ tr := tar.NewReader(gr)
+ _, err = tr.Next()
+ isTar := err == nil
+
+ // Reset to beginning of file
+ if _, err := f.Seek(0, os.SEEK_SET); err != nil {
+ return nil, err
+ }
+ gr.Reset(f)
+
+ if isTar {
+ return archive.NopCloser(tar.NewReader(gr)), nil
+ }
+ return gr, nil
+ },
+ },
+}
+
+func (compressionType *compressionType) rollingArchiveTypeName(name string, exploded bool) string {
+ if !compressionType.handleMultipleEntries && !exploded {
+ return name + ".tar" + compressionType.extension
+ } else {
+ return name + compressionType.extension
+ }
+
+}
+
+func rollingArchiveTypeFromString(rollingArchiveTypeStr string) (rollingArchiveType, bool) {
+ for tp, tpStr := range rollingArchiveTypesStringRepresentation {
+ if tpStr == rollingArchiveTypeStr {
+ return tp, true
+ }
+ }
+
+ return 0, false
+}
+
+// Default names for different archive types
+var rollingArchiveDefaultExplodedName = "old"
+
+func rollingArchiveTypeDefaultName(archiveType rollingArchiveType, exploded bool) (string, error) {
+ compressionType, ok := compressionTypes[archiveType]
+ if !ok {
+ return "", fmt.Errorf("cannot get default filename for archive type = %v", archiveType)
+ }
+ return compressionType.rollingArchiveTypeName("log", exploded), nil
+}
+
+// rollerVirtual is an interface that represents all virtual funcs that are
+// called in different rolling writer subtypes.
+type rollerVirtual interface {
+ needsToRoll() bool // Returns true if needs to switch to another file.
+ isFileRollNameValid(rname string) bool // Returns true if logger roll file name (postfix/prefix/etc.) is ok.
+ sortFileRollNamesAsc(fs []string) ([]string, error) // Sorts logger roll file names in ascending order of their creation by logger.
+
+ // getNewHistoryRollFileName is called whenever we are about to roll the
+ // current log file. It returns the name the current log file should be
+ // rolled to.
+ getNewHistoryRollFileName(otherHistoryFiles []string) string
+
+ getCurrentFileName() string
+}
+
+// rollingFileWriter writes received messages to a file, until time interval passes
+// or file exceeds a specified limit. After that the current log file is renamed
+// and writer starts to log into a new file. You can set a limit for such renamed
+// files count, if you want, and then the rolling writer would delete older ones when
+// the files count exceed the specified limit.
+type rollingFileWriter struct {
+ fileName string // log file name
+ currentDirPath string
+ currentFile *os.File
+ currentName string
+ currentFileSize int64
+ rollingType rollingType // Rolling mode (Files roll by size/date/...)
+ archiveType rollingArchiveType
+ archivePath string
+ archiveExploded bool
+ fullName bool
+ maxRolls int
+ nameMode rollingNameMode
+ self rollerVirtual // Used for virtual calls
+ rollLock sync.Mutex
+}
+
+func newRollingFileWriter(fpath string, rtype rollingType, atype rollingArchiveType, apath string, maxr int, namemode rollingNameMode,
+ archiveExploded bool, fullName bool) (*rollingFileWriter, error) {
+ rw := new(rollingFileWriter)
+ rw.currentDirPath, rw.fileName = filepath.Split(fpath)
+ if len(rw.currentDirPath) == 0 {
+ rw.currentDirPath = "."
+ }
+
+ rw.rollingType = rtype
+ rw.archiveType = atype
+ rw.archivePath = apath
+ rw.nameMode = namemode
+ rw.maxRolls = maxr
+ rw.archiveExploded = archiveExploded
+ rw.fullName = fullName
+ return rw, nil
+}
+
+func (rw *rollingFileWriter) hasRollName(file string) bool {
+ switch rw.nameMode {
+ case rollingNameModePostfix:
+ rname := rw.fileName + rollingLogHistoryDelimiter
+ return strings.HasPrefix(file, rname)
+ case rollingNameModePrefix:
+ rname := rollingLogHistoryDelimiter + rw.fileName
+ return strings.HasSuffix(file, rname)
+ }
+ return false
+}
+
+func (rw *rollingFileWriter) createFullFileName(originalName, rollname string) string {
+ switch rw.nameMode {
+ case rollingNameModePostfix:
+ return originalName + rollingLogHistoryDelimiter + rollname
+ case rollingNameModePrefix:
+ return rollname + rollingLogHistoryDelimiter + originalName
+ }
+ return ""
+}
+
+func (rw *rollingFileWriter) getSortedLogHistory() ([]string, error) {
+ files, err := getDirFilePaths(rw.currentDirPath, nil, true)
+ if err != nil {
+ return nil, err
+ }
+ var validRollNames []string
+ for _, file := range files {
+ if rw.hasRollName(file) {
+ rname := rw.getFileRollName(file)
+ if rw.self.isFileRollNameValid(rname) {
+ validRollNames = append(validRollNames, rname)
+ }
+ }
+ }
+ sortedTails, err := rw.self.sortFileRollNamesAsc(validRollNames)
+ if err != nil {
+ return nil, err
+ }
+ validSortedFiles := make([]string, len(sortedTails))
+ for i, v := range sortedTails {
+ validSortedFiles[i] = rw.createFullFileName(rw.fileName, v)
+ }
+ return validSortedFiles, nil
+}
+
+func (rw *rollingFileWriter) createFileAndFolderIfNeeded(first bool) error {
+ var err error
+
+ if len(rw.currentDirPath) != 0 {
+ err = os.MkdirAll(rw.currentDirPath, defaultDirectoryPermissions)
+
+ if err != nil {
+ return err
+ }
+ }
+ rw.currentName = rw.self.getCurrentFileName()
+ filePath := filepath.Join(rw.currentDirPath, rw.currentName)
+
+ // If exists
+ stat, err := os.Lstat(filePath)
+ if err == nil {
+ rw.currentFile, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, defaultFilePermissions)
+ if err != nil {
+ return err
+ }
+
+ stat, err = os.Lstat(filePath)
+ if err != nil {
+ return err
+ }
+
+ rw.currentFileSize = stat.Size()
+ } else {
+ rw.currentFile, err = os.Create(filePath)
+ rw.currentFileSize = 0
+ }
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (rw *rollingFileWriter) archiveExplodedLogs(logFilename string, compressionType compressionType) (err error) {
+ closeWithError := func(c io.Closer) {
+ if cerr := c.Close(); cerr != nil && err == nil {
+ err = cerr
+ }
+ }
+
+ rollPath := filepath.Join(rw.currentDirPath, logFilename)
+ src, err := os.Open(rollPath)
+ if err != nil {
+ return err
+ }
+ defer src.Close() // Read-only
+
+ // Buffer to a temporary file on the same partition
+ // Note: archivePath is a path to a directory when handling exploded logs
+ dst, err := rw.tempArchiveFile(rw.archivePath)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ closeWithError(dst)
+ if err != nil {
+ os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file
+ return
+ }
+
+ // Finalize archive by swapping the buffered archive into place
+ err = os.Rename(dst.Name(), filepath.Join(rw.archivePath,
+ compressionType.rollingArchiveTypeName(logFilename, true)))
+ }()
+
+ // archive entry
+ w := compressionType.archiver(dst, true)
+ defer closeWithError(w)
+ fi, err := src.Stat()
+ if err != nil {
+ return err
+ }
+ if err := w.NextFile(logFilename, fi); err != nil {
+ return err
+ }
+ _, err = io.Copy(w, src)
+ return err
+}
+
+func (rw *rollingFileWriter) archiveUnexplodedLogs(compressionType compressionType, rollsToDelete int, history []string) (err error) {
+ closeWithError := func(c io.Closer) {
+ if cerr := c.Close(); cerr != nil && err == nil {
+ err = cerr
+ }
+ }
+
+ // Buffer to a temporary file on the same partition
+ // Note: archivePath is a path to a file when handling unexploded logs
+ dst, err := rw.tempArchiveFile(filepath.Dir(rw.archivePath))
+ if err != nil {
+ return err
+ }
+ defer func() {
+ closeWithError(dst)
+ if err != nil {
+ os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file
+ return
+ }
+
+ // Finalize archive by moving the buffered archive into place
+ err = os.Rename(dst.Name(), rw.archivePath)
+ }()
+
+ w := compressionType.archiver(dst, false)
+ defer closeWithError(w)
+
+ src, err := os.Open(rw.archivePath)
+ switch {
+ // Archive exists
+ case err == nil:
+ defer src.Close() // Read-only
+
+ r, err := compressionType.unarchiver(src)
+ if err != nil {
+ return err
+ }
+ defer r.Close() // Read-only
+
+ if err := archive.Copy(w, r); err != nil {
+ return err
+ }
+
+ // Failed to stat
+ case !os.IsNotExist(err):
+ return err
+ }
+
+ // Add new files to the archive
+ for i := 0; i < rollsToDelete; i++ {
+ rollPath := filepath.Join(rw.currentDirPath, history[i])
+ src, err := os.Open(rollPath)
+ if err != nil {
+ return err
+ }
+ defer src.Close() // Read-only
+ fi, err := src.Stat()
+ if err != nil {
+ return err
+ }
+ if err := w.NextFile(src.Name(), fi); err != nil {
+ return err
+ }
+ if _, err := io.Copy(w, src); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (rw *rollingFileWriter) deleteOldRolls(history []string) error {
+ if rw.maxRolls <= 0 {
+ return nil
+ }
+
+ rollsToDelete := len(history) - rw.maxRolls
+ if rollsToDelete <= 0 {
+ return nil
+ }
+
+ if rw.archiveType != rollingArchiveNone {
+ if rw.archiveExploded {
+ os.MkdirAll(rw.archivePath, defaultDirectoryPermissions)
+
+ // Archive logs
+ for i := 0; i < rollsToDelete; i++ {
+ rw.archiveExplodedLogs(history[i], compressionTypes[rw.archiveType])
+ }
+ } else {
+ os.MkdirAll(filepath.Dir(rw.archivePath), defaultDirectoryPermissions)
+
+ rw.archiveUnexplodedLogs(compressionTypes[rw.archiveType], rollsToDelete, history)
+ }
+ }
+
+ var err error
+ // In all cases (archive files or not) the files should be deleted.
+ for i := 0; i < rollsToDelete; i++ {
+ // Try best to delete files without breaking the loop.
+ if err = tryRemoveFile(filepath.Join(rw.currentDirPath, history[i])); err != nil {
+ reportInternalError(err)
+ }
+ }
+
+ return nil
+}
+
+func (rw *rollingFileWriter) getFileRollName(fileName string) string {
+ switch rw.nameMode {
+ case rollingNameModePostfix:
+ return fileName[len(rw.fileName+rollingLogHistoryDelimiter):]
+ case rollingNameModePrefix:
+ return fileName[:len(fileName)-len(rw.fileName+rollingLogHistoryDelimiter)]
+ }
+ return ""
+}
+
+func (rw *rollingFileWriter) roll() error {
+ // First, close current file.
+ err := rw.currentFile.Close()
+ if err != nil {
+ return err
+ }
+ rw.currentFile = nil
+
+ // Current history of all previous log files.
+ // For file roller it may be like this:
+ // * ...
+ // * file.log.4
+ // * file.log.5
+ // * file.log.6
+ //
+ // For date roller it may look like this:
+ // * ...
+ // * file.log.11.Aug.13
+ // * file.log.15.Aug.13
+ // * file.log.16.Aug.13
+ // Sorted log history does NOT include current file.
+ history, err := rw.getSortedLogHistory()
+ if err != nil {
+ return err
+ }
+ // Renames current file to create a new roll history entry
+ // For file roller it may be like this:
+ // * ...
+ // * file.log.4
+ // * file.log.5
+ // * file.log.6
+ // n file.log.7 <---- RENAMED (from file.log)
+ newHistoryName := rw.createFullFileName(rw.fileName,
+ rw.self.getNewHistoryRollFileName(history))
+
+ err = os.Rename(filepath.Join(rw.currentDirPath, rw.currentName), filepath.Join(rw.currentDirPath, newHistoryName))
+ if err != nil {
+ return err
+ }
+
+ // Finally, add the newly added history file to the history archive
+ // and, if after that the archive exceeds the allowed max limit, older rolls
+ // must the removed/archived.
+ history = append(history, newHistoryName)
+ if len(history) > rw.maxRolls {
+ err = rw.deleteOldRolls(history)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (rw *rollingFileWriter) Write(bytes []byte) (n int, err error) {
+ rw.rollLock.Lock()
+ defer rw.rollLock.Unlock()
+
+ if rw.self.needsToRoll() {
+ if err := rw.roll(); err != nil {
+ return 0, err
+ }
+ }
+
+ if rw.currentFile == nil {
+ err := rw.createFileAndFolderIfNeeded(true)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ n, err = rw.currentFile.Write(bytes)
+ rw.currentFileSize += int64(n)
+ return n, err
+}
+
+func (rw *rollingFileWriter) Close() error {
+ if rw.currentFile != nil {
+ e := rw.currentFile.Close()
+ if e != nil {
+ return e
+ }
+ rw.currentFile = nil
+ }
+ return nil
+}
+
+func (rw *rollingFileWriter) tempArchiveFile(archiveDir string) (*os.File, error) {
+ tmp := filepath.Join(archiveDir, ".seelog_tmp")
+ if err := os.MkdirAll(tmp, defaultDirectoryPermissions); err != nil {
+ return nil, err
+ }
+ return ioutil.TempFile(tmp, "archived_logs")
+}
+
+// =============================================================================================
+// Different types of rolling writers
+// =============================================================================================
+
+// --------------------------------------------------
+// Rolling writer by SIZE
+// --------------------------------------------------
+
+// rollingFileWriterSize performs roll when file exceeds a specified limit.
+type rollingFileWriterSize struct {
+ *rollingFileWriter
+ maxFileSize int64
+}
+
+func NewRollingFileWriterSize(fpath string, atype rollingArchiveType, apath string, maxSize int64, maxRolls int, namemode rollingNameMode, archiveExploded bool) (*rollingFileWriterSize, error) {
+ rw, err := newRollingFileWriter(fpath, rollingTypeSize, atype, apath, maxRolls, namemode, archiveExploded, false)
+ if err != nil {
+ return nil, err
+ }
+ rws := &rollingFileWriterSize{rw, maxSize}
+ rws.self = rws
+ return rws, nil
+}
+
+func (rws *rollingFileWriterSize) needsToRoll() bool {
+ return rws.currentFileSize >= rws.maxFileSize
+}
+
+func (rws *rollingFileWriterSize) isFileRollNameValid(rname string) bool {
+ if len(rname) == 0 {
+ return false
+ }
+ _, err := strconv.Atoi(rname)
+ return err == nil
+}
+
+type rollSizeFileTailsSlice []string
+
+func (p rollSizeFileTailsSlice) Len() int {
+ return len(p)
+}
+func (p rollSizeFileTailsSlice) Less(i, j int) bool {
+ v1, _ := strconv.Atoi(p[i])
+ v2, _ := strconv.Atoi(p[j])
+ return v1 < v2
+}
+func (p rollSizeFileTailsSlice) Swap(i, j int) {
+ p[i], p[j] = p[j], p[i]
+}
+
+func (rws *rollingFileWriterSize) sortFileRollNamesAsc(fs []string) ([]string, error) {
+ ss := rollSizeFileTailsSlice(fs)
+ sort.Sort(ss)
+ return ss, nil
+}
+
+func (rws *rollingFileWriterSize) getNewHistoryRollFileName(otherLogFiles []string) string {
+ v := 0
+ if len(otherLogFiles) != 0 {
+ latest := otherLogFiles[len(otherLogFiles)-1]
+ v, _ = strconv.Atoi(rws.getFileRollName(latest))
+ }
+ return fmt.Sprintf("%d", v+1)
+}
+
+func (rws *rollingFileWriterSize) getCurrentFileName() string {
+ return rws.fileName
+}
+
+func (rws *rollingFileWriterSize) String() string {
+ return fmt.Sprintf("Rolling file writer (By SIZE): filename: %s, archive: %s, archivefile: %s, maxFileSize: %v, maxRolls: %v",
+ rws.fileName,
+ rollingArchiveTypesStringRepresentation[rws.archiveType],
+ rws.archivePath,
+ rws.maxFileSize,
+ rws.maxRolls)
+}
+
+// --------------------------------------------------
+// Rolling writer by TIME
+// --------------------------------------------------
+
+// rollingFileWriterTime performs roll when a specified time interval has passed.
+type rollingFileWriterTime struct {
+ *rollingFileWriter
+ timePattern string
+ currentTimeFileName string
+}
+
+func NewRollingFileWriterTime(fpath string, atype rollingArchiveType, apath string, maxr int,
+ timePattern string, namemode rollingNameMode, archiveExploded bool, fullName bool) (*rollingFileWriterTime, error) {
+
+ rw, err := newRollingFileWriter(fpath, rollingTypeTime, atype, apath, maxr, namemode, archiveExploded, fullName)
+ if err != nil {
+ return nil, err
+ }
+ rws := &rollingFileWriterTime{rw, timePattern, ""}
+ rws.self = rws
+ return rws, nil
+}
+
+func (rwt *rollingFileWriterTime) needsToRoll() bool {
+ newName := time.Now().Format(rwt.timePattern)
+
+ if rwt.currentTimeFileName == "" {
+ // first run; capture the current name
+ rwt.currentTimeFileName = newName
+ return false
+ }
+
+ return newName != rwt.currentTimeFileName
+}
+
+func (rwt *rollingFileWriterTime) isFileRollNameValid(rname string) bool {
+ if len(rname) == 0 {
+ return false
+ }
+ _, err := time.ParseInLocation(rwt.timePattern, rname, time.Local)
+ return err == nil
+}
+
+type rollTimeFileTailsSlice struct {
+ data []string
+ pattern string
+}
+
+func (p rollTimeFileTailsSlice) Len() int {
+ return len(p.data)
+}
+
+func (p rollTimeFileTailsSlice) Less(i, j int) bool {
+ t1, _ := time.ParseInLocation(p.pattern, p.data[i], time.Local)
+ t2, _ := time.ParseInLocation(p.pattern, p.data[j], time.Local)
+ return t1.Before(t2)
+}
+
+func (p rollTimeFileTailsSlice) Swap(i, j int) {
+ p.data[i], p.data[j] = p.data[j], p.data[i]
+}
+
+func (rwt *rollingFileWriterTime) sortFileRollNamesAsc(fs []string) ([]string, error) {
+ ss := rollTimeFileTailsSlice{data: fs, pattern: rwt.timePattern}
+ sort.Sort(ss)
+ return ss.data, nil
+}
+
+func (rwt *rollingFileWriterTime) getNewHistoryRollFileName(_ []string) string {
+ newFileName := rwt.currentTimeFileName
+ rwt.currentTimeFileName = time.Now().Format(rwt.timePattern)
+ return newFileName
+}
+
+func (rwt *rollingFileWriterTime) getCurrentFileName() string {
+ if rwt.fullName {
+ return rwt.createFullFileName(rwt.fileName, time.Now().Format(rwt.timePattern))
+ }
+ return rwt.fileName
+}
+
+func (rwt *rollingFileWriterTime) String() string {
+ return fmt.Sprintf("Rolling file writer (By TIME): filename: %s, archive: %s, archivefile: %s, pattern: %s, maxRolls: %v",
+ rwt.fileName,
+ rollingArchiveTypesStringRepresentation[rwt.archiveType],
+ rwt.archivePath,
+ rwt.timePattern,
+ rwt.maxRolls)
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter_test.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter_test.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter_test.go
new file mode 100644
index 0000000..b23c959
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_rollingfilewriter_test.go
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package seelog
+
+import (
+ "fmt"
+ "io"
+ "testing"
+)
+
+// fileWriterTestCase is declared in writers_filewriter_test.go
+
+func createRollingSizeFileWriterTestCase(
+ files []string,
+ fileName string,
+ fileSize int64,
+ maxRolls int,
+ writeCount int,
+ resFiles []string,
+ nameMode rollingNameMode,
+ archiveType rollingArchiveType,
+ archiveExploded bool,
+ archivePath string) *fileWriterTestCase {
+
+ return &fileWriterTestCase{files, fileName, rollingTypeSize, fileSize, maxRolls, "", writeCount, resFiles, nameMode, archiveType, archiveExploded, archivePath}
+}
+
+func createRollingDatefileWriterTestCase(
+ files []string,
+ fileName string,
+ datePattern string,
+ writeCount int,
+ resFiles []string,
+ nameMode rollingNameMode,
+ archiveType rollingArchiveType,
+ archiveExploded bool,
+ archivePath string) *fileWriterTestCase {
+
+ return &fileWriterTestCase{files, fileName, rollingTypeTime, 0, 0, datePattern, writeCount, resFiles, nameMode, archiveType, archiveExploded, archivePath}
+}
+
+func TestShouldArchiveWithTar(t *testing.T) {
+ compressionType := compressionTypes[rollingArchiveGzip]
+
+ archiveName := compressionType.rollingArchiveTypeName("log", false)
+
+ if archiveName != "log.tar.gz" {
+ t.Fatalf("archive name should be log.tar.gz but got %v", archiveName)
+ }
+}
+
+func TestRollingFileWriter(t *testing.T) {
+ t.Logf("Starting rolling file writer tests")
+ NewFileWriterTester(rollingfileWriterTests, rollingFileWriterGetter, t).test()
+}
+
+//===============================================================
+
+func rollingFileWriterGetter(testCase *fileWriterTestCase) (io.WriteCloser, error) {
+ if testCase.rollingType == rollingTypeSize {
+ return NewRollingFileWriterSize(testCase.fileName, testCase.archiveType, testCase.archivePath, testCase.fileSize, testCase.maxRolls, testCase.nameMode, testCase.archiveExploded)
+ } else if testCase.rollingType == rollingTypeTime {
+ return NewRollingFileWriterTime(testCase.fileName, testCase.archiveType, testCase.archivePath, -1, testCase.datePattern, testCase.nameMode, testCase.archiveExploded, false)
+ }
+
+ return nil, fmt.Errorf("incorrect rollingType")
+}
+
+//===============================================================
+var rollingfileWriterTests = []*fileWriterTestCase{
+ createRollingSizeFileWriterTestCase([]string{}, "log.testlog", 10, 10, 1, []string{"log.testlog"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, "log.testlog", 10, 10, 2, []string{"log.testlog", "log.testlog.1"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{"1.log.testlog"}, "log.testlog", 10, 10, 2, []string{"log.testlog", "1.log.testlog", "2.log.testlog"}, rollingNameModePrefix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{"log.testlog.1"}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.2"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.1"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{"log.testlog.9"}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.10"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{"log.testlog.a", "log.testlog.1b"}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.1", "log.testlog.a", "log.testlog.1b"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, `dir/log.testlog`, 10, 10, 1, []string{`dir/log.testlog`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, `dir/log.testlog`, 10, 10, 2, []string{`dir/log.testlog`, `dir/1.log.testlog`}, rollingNameModePrefix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{`dir/dir/log.testlog.1`}, `dir/dir/log.testlog`, 10, 10, 2, []string{`dir/dir/log.testlog`, `dir/dir/log.testlog.1`, `dir/dir/log.testlog.2`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{`dir/dir/dir/log.testlog.1`}, `dir/dir/dir/log.testlog`, 10, 1, 2, []string{`dir/dir/dir/log.testlog`, `dir/dir/dir/log.testlog.2`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, `./log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.1`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{`././././log.testlog.9`}, `log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.10`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{"dir/dir/log.testlog.a", "dir/dir/log.testlog.1b"}, "dir/dir/log.testlog", 10, 1, 2, []string{"dir/dir/log.testlog", "dir/dir/log.testlog.1", "dir/dir/log.testlog.a", "dir/dir/log.testlog.1b"}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, `././dir/log.testlog`, 10, 10, 1, []string{`dir/log.testlog`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, `././dir/log.testlog`, 10, 10, 2, []string{`dir/log.testlog`, `dir/log.testlog.1`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{`././dir/dir/log.testlog.1`}, `dir/dir/log.testlog`, 10, 10, 2, []string{`dir/dir/log.testlog`, `dir/dir/log.testlog.1`, `dir/dir/log.testlog.2`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{`././dir/dir/dir/log.testlog.1`}, `dir/dir/dir/log.testlog`, 10, 1, 2, []string{`dir/dir/dir/log.testlog`, `dir/dir/dir/log.testlog.2`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{}, `././log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.1`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{`././././log.testlog.9`}, `log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.10`}, rollingNameModePostfix, rollingArchiveNone, false, ""),
+ createRollingSizeFileWriterTestCase([]string{"././dir/dir/log.testlog.a", "././dir/dir/log.testlog.1b"}, "dir/dir/log.testlog", 10, 1, 2, []string{"dir/dir/log.testlog", "dir/dir/log.testlog.1", "dir/dir/log.testlog.a", "dir/dir/log.testlog.1b"}, rollingNameModePostfix, rollingArchiveNone, true, ""),
+ createRollingSizeFileWriterTestCase([]string{"log.testlog", "log.testlog.1"}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.2", "dir/log.testlog.1.zip"}, rollingNameModePostfix, rollingArchiveZip, true, "dir"),
+ // ====================
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/d969e13b/traffic_stats/vendor/github.com/cihub/seelog/writers_smtpwriter.go
----------------------------------------------------------------------
diff --git a/traffic_stats/vendor/github.com/cihub/seelog b/traffic_stats/vendor/github.com/cihub/seelog
deleted file mode 160000
index 175e6e3..0000000
--- a/traffic_stats/vendor/github.com/cihub/seelog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 175e6e3d439fe2e1cee7ab652b12eb546c145a13
diff --git a/traffic_stats/vendor/github.com/cihub/seelog/writers_smtpwriter.go b/traffic_stats/vendor/github.com/cihub/seelog/writers_smtpwriter.go
new file mode 100644
index 0000000..31b7943
--- /dev/null
+++ b/traffic_stats/vendor/github.com/cihub/seelog/writers_smtpwriter.go
@@ -0,0 +1,214 @@
+// 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"
+ "crypto/x509"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/smtp"
+ "path/filepath"
+ "strings"
+)
+
+const (
+ // Default subject phrase for sending emails.
+ DefaultSubjectPhrase = "Diagnostic message from server: "
+
+ // Message subject pattern composed according to RFC 5321.
+ rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n"
+)
+
+// smtpWriter is used to send emails via given SMTP-server.
+type smtpWriter struct {
+ auth smtp.Auth
+ hostName string
+ hostPort string
+ hostNameWithPort string
+ senderAddress string
+ senderName string
+ recipientAddresses []string
+ caCertDirPaths []string
+ mailHeaders []string
+ subject string
+}
+
+// NewSMTPWriter returns a new SMTP-writer.
+func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter {
+ return &smtpWriter{
+ auth: smtp.PlainAuth("", un, pwd, hn),
+ hostName: hn,
+ hostPort: hp,
+ hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp),
+ senderAddress: sa,
+ senderName: sn,
+ recipientAddresses: ras,
+ caCertDirPaths: cacdps,
+ subject: subj,
+ mailHeaders: headers,
+ }
+}
+
+func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte {
+ headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject)
+ // Build header lines if configured.
+ if headers != nil && len(headers) > 0 {
+ headerLines += strings.Join(headers, "\n")
+ headerLines += "\n"
+ }
+ return append([]byte(headerLines), body...)
+}
+
+// getTLSConfig gets paths of PEM files with certificates,
+// host server name and tries to create an appropriate TLS.Config.
+func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) {
+ if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 {
+ err = errors.New("invalid PEM file paths")
+ return
+ }
+ pemEncodedContent := []byte{}
+ var (
+ e error
+ bytes []byte
+ )
+ // Create a file-filter-by-extension, set aside non-pem files.
+ pemFilePathFilter := func(fp string) bool {
+ if filepath.Ext(fp) == ".pem" {
+ return true
+ }
+ return false
+ }
+ for _, pemFileDirPath := range pemFileDirPaths {
+ pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false)
+ if err != nil {
+ return nil, err
+ }
+
+ // Put together all the PEM files to decode them as a whole byte slice.
+ for _, pfp := range pemFilePaths {
+ if bytes, e = ioutil.ReadFile(pfp); e == nil {
+ pemEncodedContent = append(pemEncodedContent, bytes...)
+ } else {
+ return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error())
+ }
+ }
+ }
+ config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName}
+ isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent)
+ if !isAppended {
+ // Extract this into a separate error.
+ err = errors.New("invalid PEM content")
+ return
+ }
+ return
+}
+
+// SendMail accepts TLS configuration, connects to the server at addr,
+// switches to TLS if possible, authenticates with mechanism a if possible,
+// and then sends an email from address from, to addresses to, with message msg.
+func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+ c, err := smtp.Dial(addr)
+ if err != nil {
+ return err
+ }
+ // Check if the server supports STARTTLS extension.
+ if ok, _ := c.Extension("STARTTLS"); ok {
+ if err = c.StartTLS(config); err != nil {
+ return err
+ }
+ }
+ // Check if the server supports AUTH extension and use given smtp.Auth.
+ if a != nil {
+ if isSupported, _ := c.Extension("AUTH"); isSupported {
+ if err = c.Auth(a); err != nil {
+ return err
+ }
+ }
+ }
+ // Portion of code from the official smtp.SendMail function,
+ // see http://golang.org/src/pkg/net/smtp/smtp.go.
+ if err = c.Mail(from); err != nil {
+ return err
+ }
+ for _, addr := range to {
+ if err = c.Rcpt(addr); err != nil {
+ return err
+ }
+ }
+ w, err := c.Data()
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(msg)
+ if err != nil {
+ return err
+ }
+ err = w.Close()
+ if err != nil {
+ return err
+ }
+ return c.Quit()
+}
+
+// Write pushes a text message properly composed according to RFC 5321
+// to a post server, which sends it to the recipients.
+func (smtpw *smtpWriter) Write(data []byte) (int, error) {
+ var err error
+
+ if smtpw.caCertDirPaths == nil {
+ err = smtp.SendMail(
+ smtpw.hostNameWithPort,
+ smtpw.auth,
+ smtpw.senderAddress,
+ smtpw.recipientAddresses,
+ prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
+ )
+ } else {
+ config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName)
+ if e != nil {
+ return 0, e
+ }
+ err = sendMailWithTLSConfig(
+ config,
+ smtpw.hostNameWithPort,
+ smtpw.auth,
+ smtpw.senderAddress,
+ smtpw.recipientAddresses,
+ prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
+ )
+ }
+ if err != nil {
+ return 0, err
+ }
+ return len(data), nil
+}
+
+// Close closes down SMTP-connection.
+func (smtpw *smtpWriter) Close() error {
+ // Do nothing as Write method opens and closes connection automatically.
+ return nil
+}